From 824ae69722e78ea3cca0b2981172faa1fed5f5c6 Mon Sep 17 00:00:00 2001 From: n8n Date: Mon, 1 Dec 2025 20:22:15 +0100 Subject: [PATCH] Shell script for n8n pod and containers --- bin/create_pod_n8n.sh | 136 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100755 bin/create_pod_n8n.sh diff --git a/bin/create_pod_n8n.sh b/bin/create_pod_n8n.sh new file mode 100755 index 0000000..63c47e6 --- /dev/null +++ b/bin/create_pod_n8n.sh @@ -0,0 +1,136 @@ +#!/bin/bash + +# To be run by user n8n to create the pod and containers with +# n8n (main) + n8n Task Runners sidecar, bind-mounted data/files dirs, +# and systemd user services. + +set -e + +# Environment variables +POD_NAME='n8n_pod' +N8N_CTR_NAME='n8n_ctr' +RUNNERS_CTR_NAME='n8n_runners_ctr' +# Images (version must match between n8n and runners) +N8N_IMAGE='docker.io/n8nio/n8n:1.121.3' +RUNNERS_IMAGE='docker.io/n8nio/runners:1.121.3' +HOST_LOCAL_IP='127.0.0.1' +N8N_HOST_PORT='8087' +N8N_CONTAINER_PORT='5678' +BIND_DIR="$HOME/.local/share/$POD_NAME" +# Container /home/node/.n8n holds workflows, credentials, config +N8N_DATA_DIR="$BIND_DIR/data" +# Container /files for the “Read/Write Files from Disk” node +N8N_FILES_DIR="$BIND_DIR/files" +USER_SYSTEMD_DIR="$HOME/.config/systemd/user" +# Timezone +TZ_VALUE='Europe/Berlin' +GENERIC_TZ_VALUE='Europe/Berlin' +# Shared secret between n8n and runners +RUNNERS_AUTH_TOKEN='xqw97zn23vlmqxawezor87b2o2xmn873z' +# Optional JS allow-lists. +# NOTE: imports are *disabled by default*; you can relax these later. +NODE_BUILTIN_MODULES='crypto,fs,path,buffer,stream,url,util' +# e.g. "moment,uuid" once added to the runners image +NODE_EXTERNAL_MODULES='' + +# Prepare directories +mkdir -p "$N8N_DATA_DIR" "$N8N_FILES_DIR" "$USER_SYSTEMD_DIR" +# Fix permissions for n8n user (node:1000 in container) +podman unshare chown 1000:1000 "$N8N_DATA_DIR" "$N8N_FILES_DIR" + +# Create pod if not yet existing +if ! podman pod exists "$POD_NAME"; then + podman pod create -n "$POD_NAME" \ + -p "$HOST_LOCAL_IP:$N8N_HOST_PORT:$N8N_CONTAINER_PORT" + echo "Pod '$POD_NAME' created (rc=$?)" +else + echo "Pod '$POD_NAME' already exists." +fi + +# n8n main container +# Remove any old container +podman rm -f "$N8N_CTR_NAME" +# New Container +podman run -d --name "$N8N_CTR_NAME" --pod "$POD_NAME" \ + -e TZ="$TZ_VALUE" \ + -e GENERIC_TIMEZONE="$GENERIC_TZ_VALUE" \ + -e N8N_PORT="$N8N_CONTAINER_PORT" \ + -e N8N_PROTOCOL='http' \ + -e N8N_HOST='localhost' \ + -e WEBHOOK_URL="http://localhost:${N8N_HOST_PORT}" \ + -e N8N_RESTRICT_FILE_ACCESS_TO='/files' \ + -e N8N_RUNNERS_ENABLED='true' \ + -e N8N_RUNNERS_MODE='external' \ + -e N8N_RUNNERS_AUTH_TOKEN="$RUNNERS_AUTH_TOKEN" \ + -e N8N_RUNNERS_BROKER_LISTEN_ADDRESS='0.0.0.0' \ + -e N8N_NATIVE_PYTHON_RUNNER='false' \ + -e QUEUE_HEALTH_CHECK_ACTIVE='true' \ + -e N8N_METRICS='false' \ + -v "$N8N_DATA_DIR:/home/node/.n8n:Z" \ + -v "$N8N_FILES_DIR:/files:Z" \ + "$N8N_IMAGE" +echo "Container '$N8N_CTR_NAME' started (rc=$?)" + +# n8n Task Runners container (only for JS, not for Python) +# Remove any old container +podman rm -f "$RUNNERS_CTR_NAME" +# New Container +# Python runner is effectively disabled because N8N_NATIVE_PYTHON_RUNNER=false on n8n. +podman run -d --name "$RUNNERS_CTR_NAME" --pod "$POD_NAME" \ + -e TZ="$TZ_VALUE" \ + -e GENERIC_TIMEZONE="$GENERIC_TZ_VALUE" \ + -e N8N_RUNNERS_AUTH_TOKEN="$RUNNERS_AUTH_TOKEN" \ + -e N8N_RUNNERS_TASK_BROKER_URI='http://127.0.0.1:5679' \ + -e N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT='15' \ + -e NODE_FUNCTION_ALLOW_BUILTIN="$NODE_BUILTIN_MODULES" \ + -e NODE_FUNCTION_ALLOW_EXTERNAL="$NODE_EXTERNAL_MODULES" \ + "$RUNNERS_IMAGE" +echo "Container '$RUNNERS_CTR_NAME' started (rc=$?)" + +# Wait for API readiness (/health) +CHECK_URL="http://$HOST_LOCAL_IP:$N8N_HOST_PORT" +echo -n "Waiting for n8n API at $CHECK_URL ..." +for attempt in $(seq 1 30); do + if curl -fsS "$CHECK_URL" >/dev/null 2>&1; then + echo "ready." + break + fi + sleep 2 + if [ "$attempt" -eq 30 ]; then + echo "timeout error." >&2 + exit 1 + fi +done + +# Generate systemd service files +cd "$USER_SYSTEMD_DIR" +podman generate systemd --name --new --files "$POD_NAME" +echo "Generated systemd service files (rc=$?)" + +# Stop & remove live pod and containers +podman pod stop --ignore --time 15 "$POD_NAME" +podman pod rm -f --ignore "$POD_NAME" +if podman pod exists "$POD_NAME"; then + echo "ERROR: Pod $POD_NAME still exists." >&2 + exit 1 +else + echo "Stopped & removed live pod $POD_NAME and containers." +fi + +# Enable systemd user services +systemctl --user daemon-reload +# pod service (creates pod + containers) +systemctl --user enable --now "pod-${POD_NAME}.service" +systemctl --user is-enabled "pod-${POD_NAME}.service" +systemctl --user is-active "pod-${POD_NAME}.service" +echo "Enabled systemd service pod-${POD_NAME}.service (rc=$?)" +echo "To view status: systemctl --user status pod-${POD_NAME}.service" +echo "To view logs: journalctl --user -u pod-${POD_NAME}.service -f" + +# Optional: also manage containers individually (generated by podman generate systemd) +systemctl --user enable --now "container-${N8N_CTR_NAME}.service" +systemctl --user enable --now "container-${RUNNERS_CTR_NAME}.service" +echo "To view n8n logs : journalctl --user -u container-${N8N_CTR_NAME}.service -f" +echo "To view runners logs : journalctl --user -u container-${RUNNERS_CTR_NAME}.service -f" + +echo "n8n API is reachable at http://$HOST_LOCAL_IP:$N8N_HOST_PORT"