somratpro commited on
Commit
bf816b7
·
1 Parent(s): 5cb194e

feat: implement DNS-over-HTTPS fallback via custom preload script to resolve connectivity issues on HuggingFace Spaces

Browse files
Files changed (4) hide show
  1. Dockerfile +1 -0
  2. README.md +3 -3
  3. dns-fix.js +108 -0
  4. start.sh +2 -2
Dockerfile CHANGED
@@ -28,6 +28,7 @@ RUN mkdir -p /home/node/app /home/node/.n8n && \
28
  WORKDIR /home/node/app
29
 
30
  COPY --chown=node:node health-server.js /home/node/app/health-server.js
 
31
  COPY --chown=node:node n8n-sync.py /home/node/app/n8n-sync.py
32
  COPY --chown=node:node setup-uptimerobot.sh /home/node/app/setup-uptimerobot.sh
33
  COPY --chown=node:node start.sh /home/node/app/start.sh
 
28
  WORKDIR /home/node/app
29
 
30
  COPY --chown=node:node health-server.js /home/node/app/health-server.js
31
+ COPY --chown=node:node dns-fix.js /home/node/app/dns-fix.js
32
  COPY --chown=node:node n8n-sync.py /home/node/app/n8n-sync.py
33
  COPY --chown=node:node setup-uptimerobot.sh /home/node/app/setup-uptimerobot.sh
34
  COPY --chown=node:node start.sh /home/node/app/start.sh
README.md CHANGED
@@ -14,7 +14,7 @@ secrets:
14
 
15
  # 🔗 Hugging8n
16
 
17
- **Self-hosted n8n workflow automation — free, no server needed.** Hugging8n runs [n8n](https://n8n.io) on HuggingFace Spaces Docker, serving a premium dashboard at `/` and the n8n editor at `/app/`.
18
 
19
  ## ✨ Features
20
 
@@ -56,7 +56,7 @@ You can customize Hugging8n using Environment Variables (Settings > Variables):
56
  ## 🔐 Authentication & Security
57
 
58
  Hugging8n uses n8n's native user management.
59
- - The first person to visit `/app/` on a fresh install becomes the **Owner**.
60
  - **Important:** If you delete the Space and haven't set up `HF_TOKEN`, your users and workflows will be lost.
61
  - **Permissions:** The startup script uses `umask 0077` to ensure all sensitive data is restricted to the node user.
62
 
@@ -69,7 +69,7 @@ Hugging8n automatically creates and maintains a private dataset in your Hugging
69
  ## 🏗️ Architecture
70
 
71
  - `/` : **Premium Dashboard** (Management & Monitoring)
72
- - `/app/` : **n8n Workflow Editor**
73
  - `/health` : **Health Check** (Used by the internal proxy and external monitors)
74
 
75
  ---
 
14
 
15
  # 🔗 Hugging8n
16
 
17
+ **Self-hosted n8n workflow automation — free, no server needed.** Hugging8n runs [n8n](https://n8n.io) on HuggingFace Spaces Docker, serving a premium dashboard at `/` and the n8n editor is accessible via the dashboard.
18
 
19
  ## ✨ Features
20
 
 
56
  ## 🔐 Authentication & Security
57
 
58
  Hugging8n uses n8n's native user management.
59
+ - The first person to access the n8n editor on a fresh install becomes the **Owner**.
60
  - **Important:** If you delete the Space and haven't set up `HF_TOKEN`, your users and workflows will be lost.
61
  - **Permissions:** The startup script uses `umask 0077` to ensure all sensitive data is restricted to the node user.
62
 
 
69
  ## 🏗️ Architecture
70
 
71
  - `/` : **Premium Dashboard** (Management & Monitoring)
72
+ - All other paths (e.g., `/home/workflows`) : Proxied to **n8n Workflow Editor**
73
  - `/health` : **Health Check** (Used by the internal proxy and external monitors)
74
 
75
  ---
dns-fix.js ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * DNS fix preload script for HF Spaces.
3
+ *
4
+ * Patches Node.js dns.lookup to:
5
+ * 1. Try system DNS first
6
+ * 2. Fall back to DNS-over-HTTPS (Cloudflare) if system DNS fails
7
+ * (This is needed because HF Spaces intercepts/blocks some domains like
8
+ * WhatsApp web or Telegram API via standard UDP DNS).
9
+ *
10
+ * Loaded via: NODE_OPTIONS="--require /opt/dns-fix.js"
11
+ */
12
+ "use strict";
13
+
14
+ const dns = require("dns");
15
+ const https = require("https");
16
+
17
+ // In-memory cache for runtime DoH resolutions
18
+ const runtimeCache = new Map(); // hostname -> { ip, expiry }
19
+
20
+ // DNS-over-HTTPS resolver
21
+ function dohResolve(hostname, callback) {
22
+ // Check runtime cache
23
+ const cached = runtimeCache.get(hostname);
24
+ if (cached && cached.expiry > Date.now()) {
25
+ return callback(null, cached.ip);
26
+ }
27
+
28
+ const url = `https://1.1.1.1/dns-query?name=${encodeURIComponent(hostname)}&type=A`;
29
+ const req = https.get(
30
+ url,
31
+ { headers: { Accept: "application/dns-json" }, timeout: 15000 },
32
+ (res) => {
33
+ let body = "";
34
+ res.on("data", (c) => (body += c));
35
+ res.on("end", () => {
36
+ try {
37
+ const data = JSON.parse(body);
38
+ const aRecords = (data.Answer || []).filter((a) => a.type === 1);
39
+ if (aRecords.length === 0) {
40
+ return callback(new Error(`DoH: no A record for ${hostname}`));
41
+ }
42
+ const ip = aRecords[0].data;
43
+ const ttl = Math.max((aRecords[0].TTL || 300) * 1000, 60000);
44
+ runtimeCache.set(hostname, { ip, expiry: Date.now() + ttl });
45
+ callback(null, ip);
46
+ } catch (e) {
47
+ callback(new Error(`DoH parse error: ${e.message}`));
48
+ }
49
+ });
50
+ }
51
+ );
52
+ req.on("error", (e) => callback(new Error(`DoH request failed: ${e.message}`)));
53
+ req.on("timeout", () => {
54
+ req.destroy();
55
+ callback(new Error("DoH request timed out"));
56
+ });
57
+ }
58
+
59
+ // Monkey-patch dns.lookup
60
+ const origLookup = dns.lookup;
61
+
62
+ dns.lookup = function patchedLookup(hostname, options, callback) {
63
+ // Normalize arguments (options is optional, can be number or object)
64
+ if (typeof options === "function") {
65
+ callback = options;
66
+ options = {};
67
+ }
68
+ if (typeof options === "number") {
69
+ options = { family: options };
70
+ }
71
+ options = options || {};
72
+
73
+ // Skip patching for localhost, IPs, and internal domains
74
+ if (
75
+ !hostname ||
76
+ hostname === "localhost" ||
77
+ hostname === "0.0.0.0" ||
78
+ hostname === "127.0.0.1" ||
79
+ hostname === "::1" ||
80
+ /^\d+\.\d+\.\d+\.\d+$/.test(hostname) ||
81
+ /^::/.test(hostname)
82
+ ) {
83
+ return origLookup.call(dns, hostname, options, callback);
84
+ }
85
+
86
+ // 1) Try system DNS first
87
+ origLookup.call(dns, hostname, options, (err, address, family) => {
88
+ if (!err && address) {
89
+ return callback(null, address, family);
90
+ }
91
+
92
+ // 2) System DNS failed with ENOTFOUND or EAI_AGAIN — fall back to DoH
93
+ if (err && (err.code === "ENOTFOUND" || err.code === "EAI_AGAIN")) {
94
+ dohResolve(hostname, (dohErr, ip) => {
95
+ if (dohErr || !ip) {
96
+ return callback(err); // Return original error
97
+ }
98
+ if (options.all) {
99
+ return callback(null, [{ address: ip, family: 4 }]);
100
+ }
101
+ callback(null, ip, 4);
102
+ });
103
+ } else {
104
+ // Other DNS errors — pass through
105
+ callback(err, address, family);
106
+ }
107
+ });
108
+ };
start.sh CHANGED
@@ -35,8 +35,8 @@ export N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS="${N8N_ENFORCE_SETTINGS_FILE_PERMIS
35
  export GENERIC_TIMEZONE="${GENERIC_TIMEZONE:-${TZ:-UTC}}"
36
  export TZ="${TZ:-$GENERIC_TIMEZONE}"
37
 
38
- # Force IPv4 resolution to prevent Node.js IPv6 timeouts (Fixes "Client network socket disconnected" for Discord/outbound requests)
39
- export NODE_OPTIONS="--dns-result-order=ipv4first"
40
 
41
  # Disable noisy or unnecessary services
42
  export N8N_PYTHON_NODES_ENABLED="${N8N_PYTHON_NODES_ENABLED:-false}"
 
35
  export GENERIC_TIMEZONE="${GENERIC_TIMEZONE:-${TZ:-UTC}}"
36
  export TZ="${TZ:-$GENERIC_TIMEZONE}"
37
 
38
+ # Force IPv4 resolution and apply custom DNS fallback for HF Spaces
39
+ export NODE_OPTIONS="--dns-result-order=ipv4first --require /home/node/app/dns-fix.js"
40
 
41
  # Disable noisy or unnecessary services
42
  export N8N_PYTHON_NODES_ENABLED="${N8N_PYTHON_NODES_ENABLED:-false}"