-
Notifications
You must be signed in to change notification settings - Fork 5
TASK SVM2-7062 Add n8n installation script #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hekshar
wants to merge
1
commit into
master
Choose a base branch
from
task-ibezuglov-add-n8n-SVM2-7062
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "applications-slug": "n8n", | ||
| "application-name": "n8n", | ||
| "version": "latest", | ||
| "os": "ubuntu2404", | ||
| "imageURL": "https://images.prod.solus.io/solusvm2-ubuntu-24.04-docker.qcow2", | ||
| "login-type": "INFO", | ||
| "icon": "n8n", | ||
| "cloudinitversion": "v2", | ||
| "longDescription": "Workflow automation platform with 400+ integrations. Self-hosted on Docker with a pre-generated self-signed TLS certificate.", | ||
| "shortDescription": "Workflow automation (self-hosted)", | ||
| "supportLink": "https://docs.n8n.io/", | ||
| "costs": "0", | ||
| "minCPU": "2", | ||
| "minMemoryMB": "4096", | ||
| "minSpaceGB": "20" | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| #!/bin/bash | ||
| # Copyright 2026. WebPros International GmbH. All rights reserved. | ||
| # | ||
| # n8n application installer for SolusVM 2. | ||
| # | ||
| # Runs on a fresh VM provisioned from solusvm2-ubuntu-24.04-docker.qcow2. | ||
| # Idempotent via /etc/n8n-installed sentinel. | ||
| # | ||
| # Ships with a self-signed TLS certificate bound to the VM primary IP so the | ||
| # default experience is HTTPS. The browser will show a one-time warning that | ||
| # the user accepts with "Advanced -> Proceed" (the certificate has a matching | ||
| # IP SAN, so the "Proceed" option is available). Admins with a real domain can | ||
| # replace /opt/n8n/certs/{fullchain,privkey}.pem, update N8N_HOST/WEBHOOK_URL, | ||
| # and restart the service. | ||
| # | ||
| # Usage: | ||
| # cloud-init: curl -fsSL <raw-url>/setup | bash | ||
| # manual / test: sudo bash setup | ||
| # | ||
| # Env overrides: | ||
| # N8N_VERSION default: latest (pin to a tag like 1.80.0 for reproducible builds) | ||
| # N8N_PORT default: 5678 | ||
| # HOST_IP default: first address from `hostname -I` | ||
| # TLS_DAYS default: 3650 (self-signed validity) | ||
| set -euo pipefail | ||
|
|
||
| LOG_TAG="[n8n-setup]" | ||
| log() { printf '%s %s\n' "${LOG_TAG}" "$*"; } | ||
|
|
||
| SENTINEL=/etc/n8n-installed | ||
| N8N_DIR=/opt/n8n | ||
| CERT_DIR="${N8N_DIR}/certs" | ||
| N8N_VERSION="${N8N_VERSION:-latest}" | ||
| N8N_PORT="${N8N_PORT:-5678}" | ||
| TLS_DAYS="${TLS_DAYS:-3650}" | ||
|
|
||
| # UID that the n8n container runs as (image default is `node` = 1000). | ||
| N8N_UID=1000 | ||
| N8N_GID=1000 | ||
|
|
||
| if [[ -f "$SENTINEL" ]]; then | ||
| log "Already installed (sentinel: $SENTINEL). Nothing to do." | ||
| exit 0 | ||
| fi | ||
|
|
||
| if [[ $EUID -ne 0 ]]; then | ||
| echo "${LOG_TAG} Must run as root" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| for bin in docker openssl curl; do | ||
| if ! command -v "$bin" &>/dev/null; then | ||
| echo "${LOG_TAG} Required binary missing: $bin" >&2 | ||
| exit 1 | ||
| fi | ||
| done | ||
|
|
||
| START=$(date +%s) | ||
|
|
||
| log "Detecting host IP" | ||
| HOST_IP="${HOST_IP:-$(hostname -I | awk '{print $1}')}" | ||
| if [[ -z "$HOST_IP" ]]; then | ||
| echo "${LOG_TAG} Unable to detect host IP, set HOST_IP env" >&2 | ||
| exit 1 | ||
| fi | ||
| log "Host IP: $HOST_IP" | ||
|
|
||
| log "Pulling n8n image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}" | ||
| PULL_START=$(date +%s) | ||
| docker pull "docker.n8n.io/n8nio/n8n:${N8N_VERSION}" | ||
| log "Image pulled in $(($(date +%s) - PULL_START))s" | ||
|
|
||
| # Resolve the actual version label from the image so the log and .env record | ||
| # what was installed, not just the moving "latest" tag. | ||
| RESOLVED_VERSION="$(docker inspect --format '{{index .Config.Labels "org.opencontainers.image.version"}}' \ | ||
| "docker.n8n.io/n8nio/n8n:${N8N_VERSION}" 2>/dev/null || true)" | ||
| if [[ -z "$RESOLVED_VERSION" ]]; then | ||
| RESOLVED_VERSION="${N8N_VERSION}" | ||
| fi | ||
| log "Resolved n8n version: ${RESOLVED_VERSION}" | ||
|
|
||
| log "Creating $N8N_DIR" | ||
| mkdir -p "$N8N_DIR" | ||
| chmod 0755 "$N8N_DIR" | ||
|
|
||
| log "Generating self-signed TLS certificate for IP ${HOST_IP} (valid ${TLS_DAYS}d)" | ||
| mkdir -p "$CERT_DIR" | ||
| umask 077 | ||
| openssl req -x509 -nodes \ | ||
| -newkey rsa:4096 \ | ||
| -days "${TLS_DAYS}" \ | ||
| -keyout "${CERT_DIR}/privkey.pem" \ | ||
| -out "${CERT_DIR}/fullchain.pem" \ | ||
| -subj "/CN=${HOST_IP}" \ | ||
| -addext "subjectAltName=IP:${HOST_IP}" \ | ||
| -addext "basicConstraints=critical,CA:FALSE" \ | ||
| -addext "keyUsage=critical,digitalSignature,keyEncipherment" \ | ||
| -addext "extendedKeyUsage=serverAuth" 2>/dev/null | ||
| umask 022 | ||
| # n8n inside the container reads as UID 1000; grant that UID access but no one else. | ||
| chown -R "${N8N_UID}:${N8N_GID}" "$CERT_DIR" | ||
| chmod 0700 "$CERT_DIR" | ||
| chmod 0600 "${CERT_DIR}/privkey.pem" | ||
| chmod 0644 "${CERT_DIR}/fullchain.pem" | ||
|
|
||
| log "Generating secrets" | ||
| ENC_KEY=$(openssl rand -hex 32) | ||
| JWT_SECRET=$(openssl rand -hex 32) | ||
|
|
||
| log "Writing $N8N_DIR/.env" | ||
| umask 077 | ||
| cat > "$N8N_DIR/.env" <<EOF | ||
| N8N_VERSION=${N8N_VERSION} | ||
| N8N_RESOLVED_VERSION=${RESOLVED_VERSION} | ||
| N8N_PORT=${N8N_PORT} | ||
| N8N_ENCRYPTION_KEY=${ENC_KEY} | ||
| N8N_USER_MANAGEMENT_JWT_SECRET=${JWT_SECRET} | ||
| N8N_HOST=${HOST_IP} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should it be 0.0.0.0? what if the VM will have 2 IPs? |
||
| N8N_PROTOCOL=https | ||
| N8N_SSL_KEY=/certs/privkey.pem | ||
| N8N_SSL_CERT=/certs/fullchain.pem | ||
| WEBHOOK_URL=https://${HOST_IP}:${N8N_PORT}/ | ||
| GENERIC_TIMEZONE=UTC | ||
| EOF | ||
| chmod 0600 "$N8N_DIR/.env" | ||
| umask 022 | ||
|
|
||
| log "Writing $N8N_DIR/docker-compose.yml" | ||
| cat > "$N8N_DIR/docker-compose.yml" <<'COMPOSE' | ||
| services: | ||
| n8n: | ||
| image: docker.n8n.io/n8nio/n8n:${N8N_VERSION} | ||
| container_name: n8n | ||
| restart: unless-stopped | ||
| env_file: .env | ||
| ports: | ||
| - "${N8N_PORT}:5678" | ||
| volumes: | ||
| - n8n_data:/home/node/.n8n | ||
| - ./certs:/certs:ro | ||
| healthcheck: | ||
| test: ["CMD-SHELL", "wget --no-check-certificate -qO- https://localhost:5678/healthz || exit 1"] | ||
| interval: 10s | ||
| timeout: 5s | ||
| retries: 12 | ||
|
|
||
| volumes: | ||
| n8n_data: | ||
| COMPOSE | ||
|
|
||
| log "Writing /etc/systemd/system/n8n.service" | ||
| cat > /etc/systemd/system/n8n.service <<EOF | ||
| [Unit] | ||
| Description=n8n workflow automation (Docker Compose) | ||
| After=docker.service network-online.target | ||
| Requires=docker.service | ||
| Wants=network-online.target | ||
|
|
||
| [Service] | ||
| Type=oneshot | ||
| RemainAfterExit=yes | ||
| WorkingDirectory=${N8N_DIR} | ||
| ExecStart=/usr/bin/docker compose up -d --remove-orphans | ||
| ExecStop=/usr/bin/docker compose down | ||
|
|
||
| [Install] | ||
| WantedBy=multi-user.target | ||
| EOF | ||
|
|
||
| log "Enabling and starting n8n.service" | ||
| systemctl daemon-reload | ||
| systemctl enable --now n8n.service | ||
|
|
||
| log "Waiting for n8n to answer on /healthz (up to 120s)" | ||
| WAIT_START=$(date +%s) | ||
| ATTEMPTS=60 | ||
| for i in $(seq 1 $ATTEMPTS); do | ||
| if curl -fsSk "https://localhost:${N8N_PORT}/healthz" >/dev/null 2>&1; then | ||
| log "n8n healthz OK after $(($(date +%s) - WAIT_START))s" | ||
| break | ||
| fi | ||
| if [[ $i -eq $ATTEMPTS ]]; then | ||
| echo "${LOG_TAG} n8n did not become healthy in $((ATTEMPTS * 2))s" >&2 | ||
| systemctl status n8n.service --no-pager || true | ||
| (cd "$N8N_DIR" && docker compose logs --tail=80) || true | ||
| exit 1 | ||
| fi | ||
| sleep 2 | ||
| done | ||
|
|
||
| touch "$SENTINEL" | ||
|
|
||
| ELAPSED=$(($(date +%s) - START)) | ||
| log "=== Installation complete in ${ELAPSED}s ===" | ||
| log "n8n URL: https://${HOST_IP}:${N8N_PORT}/" | ||
| log "Certificate is self-signed; accept the browser warning once (Advanced -> Proceed)." | ||
| log "Container status:" | ||
| docker ps --filter name=n8n --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| #cloud-config | ||
| runcmd: | ||
| - [curl, -fsSL, "https://raw.githubusercontent.com/solusio/applications/master/n8n/setup", -o, "/root/setup.sh"] | ||
| - [chmod, +x, "/root/setup.sh"] | ||
| - /bin/bash /root/setup.sh 2>&1 | while IFS= read -r line; do printf "%s %s\n" "$(date)" "$line"; done >> /var/log/n8n-install.log | ||
| - rm -f /root/setup.sh |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shall we add outputURL here?
as done in nextcloud template https://github.com/solusio/applications/blob/master/nextcloud/application.json#L15 ?