Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- Dockerfile +48 -0
- space-variables.env +36 -0
- worker.js +53 -0
Dockerfile
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# n8n self-hosted image (Hugging Face Spaces / any Docker host)
|
| 2 |
+
# HF Spaces runs the container as uid 1000 (= "node" user in node images),
|
| 3 |
+
# so we run as node and keep all writable data in /home/node.
|
| 4 |
+
FROM node:24-alpine
|
| 5 |
+
|
| 6 |
+
USER root
|
| 7 |
+
|
| 8 |
+
RUN apk add --no-cache \
|
| 9 |
+
git python3 py3-pip make g++ build-base \
|
| 10 |
+
cairo-dev pango-dev chromium postgresql-client yt-dlp \
|
| 11 |
+
tini curl
|
| 12 |
+
|
| 13 |
+
# Pin a version that really exists. If the build fails with "No matching version",
|
| 14 |
+
# change this to n8n@latest (or run: npm view n8n versions --json to see valid ones).
|
| 15 |
+
RUN npm install -g n8n@2.42.6
|
| 16 |
+
|
| 17 |
+
# Writable data folder for uid 1000
|
| 18 |
+
RUN mkdir -p /home/node/.n8n && chown -R node:node /home/node
|
| 19 |
+
|
| 20 |
+
ENV NODE_OPTIONS="--dns-result-order=ipv4first" \
|
| 21 |
+
PUPPETEER_SKIP_DOWNLOAD=true \
|
| 22 |
+
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \
|
| 23 |
+
N8N_RUNNERS_ENABLED=true \
|
| 24 |
+
N8N_PORT=7860 \
|
| 25 |
+
N8N_LISTEN_ADDRESS=0.0.0.0 \
|
| 26 |
+
N8N_USER_FOLDER=/home/node \
|
| 27 |
+
HOME=/home/node \
|
| 28 |
+
N8N_PROTOCOL=https \
|
| 29 |
+
N8N_PROXY_HOPS=1 \
|
| 30 |
+
N8N_DEFAULT_BINARY_DATA_MODE=filesystem \
|
| 31 |
+
N8N_DIAGNOSTICS_ENABLED=false \
|
| 32 |
+
N8N_PERSONALIZATION_ENABLED=false \
|
| 33 |
+
EXECUTIONS_DATA_PRUNE=true \
|
| 34 |
+
EXECUTIONS_DATA_MAX_AGE=168 \
|
| 35 |
+
GENERIC_TIMEZONE=Asia/Kolkata \
|
| 36 |
+
TZ=Asia/Kolkata \
|
| 37 |
+
NODE_FUNCTION_ALLOW_BUILTIN=* \
|
| 38 |
+
NODE_FUNCTION_ALLOW_EXTERNAL=*
|
| 39 |
+
|
| 40 |
+
USER node
|
| 41 |
+
WORKDIR /home/node
|
| 42 |
+
EXPOSE 7860
|
| 43 |
+
|
| 44 |
+
HEALTHCHECK --interval=60s --timeout=10s --start-period=90s --retries=3 \
|
| 45 |
+
CMD curl -fs http://127.0.0.1:7860/healthz || exit 1
|
| 46 |
+
|
| 47 |
+
ENTRYPOINT ["/sbin/tini", "--"]
|
| 48 |
+
CMD ["n8n", "start"]
|
space-variables.env
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# Hugging Face Space > Settings > Variables and secrets
|
| 3 |
+
# (Secrets = hidden values. Variables = normal values.)
|
| 4 |
+
# Replace <user> and <space> with your HF username and Space name.
|
| 5 |
+
# ============================================================
|
| 6 |
+
|
| 7 |
+
# ---- Variables ----
|
| 8 |
+
N8N_HOST=<user>-<space>.hf.space
|
| 9 |
+
WEBHOOK_URL=https://<user>-<space>.hf.space/
|
| 10 |
+
N8N_EDITOR_BASE_URL=https://<user>-<space>.hf.space/
|
| 11 |
+
|
| 12 |
+
# ---- Secrets (IMPORTANT) ----
|
| 13 |
+
# Generate once: openssl rand -hex 32 and NEVER change it later,
|
| 14 |
+
# otherwise all saved credentials (Telegram, Google) become unreadable.
|
| 15 |
+
N8N_ENCRYPTION_KEY=<64-char-random-hex>
|
| 16 |
+
|
| 17 |
+
# ---- Database (so workflows/credentials survive restarts) ----
|
| 18 |
+
# HF disk is wiped on every restart. Use your Supabase / any Postgres:
|
| 19 |
+
DB_TYPE=postgresdb
|
| 20 |
+
DB_POSTGRESDB_HOST=<db-host>
|
| 21 |
+
DB_POSTGRESDB_PORT=5432
|
| 22 |
+
DB_POSTGRESDB_DATABASE=postgres
|
| 23 |
+
DB_POSTGRESDB_USER=<db-user>
|
| 24 |
+
DB_POSTGRESDB_PASSWORD=<db-password>
|
| 25 |
+
DB_POSTGRESDB_SCHEMA=n8n
|
| 26 |
+
DB_POSTGRESDB_SSL_ENABLED=true
|
| 27 |
+
DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED=false
|
| 28 |
+
|
| 29 |
+
# ============================================================
|
| 30 |
+
# Space README.md header must contain:
|
| 31 |
+
# ---
|
| 32 |
+
# title: n8n
|
| 33 |
+
# sdk: docker
|
| 34 |
+
# app_port: 7860
|
| 35 |
+
# ---
|
| 36 |
+
# ============================================================
|
worker.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Cloudflare Worker: Telegram Bot API proxy
|
| 2 |
+
// Deploy: Cloudflare Dashboard > Workers & Pages > Create > Hello World > Edit code > paste > Deploy
|
| 3 |
+
// Then in n8n Telegram credential set Base URL = https://<your-worker>.workers.dev (no trailing slash)
|
| 4 |
+
|
| 5 |
+
const TELEGRAM_API = 'https://api.telegram.org';
|
| 6 |
+
|
| 7 |
+
export default {
|
| 8 |
+
async fetch(request) {
|
| 9 |
+
const url = new URL(request.url);
|
| 10 |
+
|
| 11 |
+
// Health check: open the worker URL in a browser, you should see this text
|
| 12 |
+
if (url.pathname === '/' || url.pathname === '/health') {
|
| 13 |
+
return new Response('Telegram proxy OK', {
|
| 14 |
+
status: 200,
|
| 15 |
+
headers: { 'content-type': 'text/plain' },
|
| 16 |
+
});
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
// Only allow real Telegram Bot API paths: /bot<TOKEN>/method and /file/bot<TOKEN>/path
|
| 20 |
+
if (!/^\/(file\/)?bot[^/]+\//.test(url.pathname)) {
|
| 21 |
+
return new Response('Not allowed', { status: 403 });
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
const target = new URL(TELEGRAM_API + url.pathname + url.search);
|
| 25 |
+
|
| 26 |
+
// Rebuild the request so Host/CF headers don't leak to Telegram
|
| 27 |
+
const headers = new Headers(request.headers);
|
| 28 |
+
headers.delete('host');
|
| 29 |
+
headers.delete('cf-connecting-ip');
|
| 30 |
+
headers.delete('x-forwarded-for');
|
| 31 |
+
headers.delete('x-real-ip');
|
| 32 |
+
|
| 33 |
+
try {
|
| 34 |
+
const upstream = await fetch(target.toString(), {
|
| 35 |
+
method: request.method,
|
| 36 |
+
headers,
|
| 37 |
+
body: ['GET', 'HEAD'].includes(request.method) ? undefined : request.body,
|
| 38 |
+
redirect: 'follow',
|
| 39 |
+
});
|
| 40 |
+
|
| 41 |
+
return new Response(upstream.body, {
|
| 42 |
+
status: upstream.status,
|
| 43 |
+
statusText: upstream.statusText,
|
| 44 |
+
headers: upstream.headers,
|
| 45 |
+
});
|
| 46 |
+
} catch (err) {
|
| 47 |
+
return new Response(JSON.stringify({ ok: false, error: String(err) }), {
|
| 48 |
+
status: 502,
|
| 49 |
+
headers: { 'content-type': 'application/json' },
|
| 50 |
+
});
|
| 51 |
+
}
|
| 52 |
+
},
|
| 53 |
+
};
|