chmod +x setup sudo ./setup #!/usr/bin/env bash set -euo pipefail # --- helpers --- command_exists() { command -v "$1" >/dev/null 2>&1; } prompt_default() { # $1=prompt, $2=default local input read -rp "$1 [$2]: " input echo "${input:-$2}" } ensure_sudo() { if [[ $EUID -ne 0 ]]; then if command_exists sudo; then echo "Using sudo for privileged commands..." SUDO="sudo" else echo "Please run as root or install sudo." >&2 exit 1 fi else SUDO="" fi } # --- start --- echo "=== n8n + Traefik quick setup (Docker + docker compose) ===" ensure_sudo # --- gather inputs --- NEW_USER="$(prompt_default "Linux username to create/use" "Sammy")" DOMAIN_NAME="$(prompt_default "Top-level domain (e.g., example.com)" "example.com")" SUBDOMAIN="$(prompt_default "Subdomain for n8n (e.g., n8n)" "n8n")" GENERIC_TIMEZONE="$(prompt_default "Timezone (IANA, e.g., America/New_York)" "America/New_York")" SSL_EMAIL="$(prompt_default "Email for Let's Encrypt" "user@example.com")" N8N_TAG="$(prompt_default "n8n Docker tag (e.g., 1.108.2 or latest)" "1.108.2")" # quick sanity checks if ! [[ "$DOMAIN_NAME" =~ ^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$ ]]; then echo "Domain '$DOMAIN_NAME' doesn't look valid. Continue anyway? (y/N)" read -r cont [[ "${cont,,}" == "y" ]] || exit 1 fi if ! [[ "$SUBDOMAIN" =~ ^[a-zA-Z0-9-]+$ ]]; then echo "Subdomain '$SUBDOMAIN' doesn't look valid. Continue anyway? (y/N)" read -r cont [[ "${cont,,}" == "y" ]] || exit 1 fi FQDN="${SUBDOMAIN}.${DOMAIN_NAME}" # --- install Docker (if needed) --- if ! command_exists docker; then echo ">>> Installing Docker Engine & Compose plugin..." $SUDO apt-get update -y $SUDO apt-get install -y ca-certificates curl gnupg $SUDO install -m 0755 -d /etc/apt/keyrings $SUDO curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc $SUDO chmod a+r /etc/apt/keyrings/docker.asc UBUNTU_CODENAME="$( . /etc/os-release echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}" )" echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${UBUNTU_CODENAME} stable" \ | $SUDO tee /etc/apt/sources.list.d/docker.list >/dev/null $SUDO apt-get update -y $SUDO apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin else echo ">>> Docker already installed. Skipping." fi # --- create user & groups --- if id -u "$NEW_USER" >/dev/null 2>&1; then echo ">>> User '$NEW_USER' already exists. Skipping creation." else echo ">>> Creating user '$NEW_USER'..." $SUDO adduser --gecos "" "$NEW_USER" $SUDO usermod -aG sudo "$NEW_USER" fi # ensure docker group exists and add user if ! getent group docker >/dev/null; then $SUDO groupadd docker fi $SUDO usermod -aG docker "$NEW_USER" # --- create project directory owned by NEW_USER --- PROJECT_DIR="/home/${NEW_USER}/n8n-compose" echo ">>> Creating project directory at ${PROJECT_DIR}..." $SUDO mkdir -p "${PROJECT_DIR}/local-files" $SUDO chown -R "${NEW_USER}:${NEW_USER}" "/home/${NEW_USER}" # --- write .env --- ENV_PATH="${PROJECT_DIR}/.env" echo ">>> Writing ${ENV_PATH}..." $SUDO bash -c "cat > '${ENV_PATH}'" <>> Writing ${COMPOSE_PATH}..." $SUDO bash -c "cat > '${COMPOSE_PATH}'" <<'EOF' services: traefik: image: "traefik" restart: always command: - "--api.insecure=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.web.http.redirections.entryPoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true" - "--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}" - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json" ports: - "80:80" - "443:443" volumes: - traefik_data:/letsencrypt - /var/run/docker.sock:/var/run/docker.sock:ro n8n: image: docker.n8n.io/n8nio/n8n:${N8N_TAG} restart: always ports: - "127.0.0.1:5678:5678" labels: - traefik.enable=true - traefik.http.routers.n8n.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) - traefik.http.routers.n8n.tls=true - traefik.http.routers.n8n.entrypoints=web,websecure - traefik.http.routers.n8n.tls.certresolver=mytlschallenge - traefik.http.middlewares.n8n.headers.SSLRedirect=true - traefik.http.middlewares.n8n.headers.STSSeconds=315360000 - traefik.http.middlewares.n8n.headers.browserXSSFilter=true - traefik.http.middlewares.n8n.headers.contentTypeNosniff=true - traefik.http.middlewares.n8n.headers.forceSTSHeader=true - traefik.http.middlewares.n8n.headers.SSLHost=${DOMAIN_NAME} - traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true - traefik.http.middlewares.n8n.headers.STSPreload=true - traefik.http.routers.n8n.middlewares=n8n@docker - traefik.http.services.n8n.loadbalancer.server.port=5678 environment: - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME} - N8N_PORT=5678 - N8N_PROTOCOL=https - N8N_RUNNERS_ENABLED=true - NODE_ENV=production # best practice: no trailing slash - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME} - N8N_PUBLIC_URL=https://${SUBDOMAIN}.${DOMAIN_NAME} - N8N_EDITOR_BASE_URL=https://${SUBDOMAIN}.${DOMAIN_NAME} - EXPRESS_TRUST_PROXY=true - N8N_SECURE_COOKIE=true - GENERIC_TIMEZONE=${GENERIC_TIMEZONE} - TZ=${GENERIC_TIMEZONE} volumes: - n8n_data:/home/node/.n8n - ./local-files:/files volumes: n8n_data: traefik_data: EOF # substitute the dynamic N8N_TAG into the heredoc above $SUDO sed -i "s/\${N8N_TAG}/${N8N_TAG}/g" "${COMPOSE_PATH}" $SUDO chown "${NEW_USER}:${NEW_USER}" "${COMPOSE_PATH}" # --- bring up stack --- echo ">>> Bringing stack up (this may take a few minutes on first run)..." # Use sudo to avoid relying on group refresh; docker group applies next login $SUDO bash -lc "cd '${PROJECT_DIR}' && docker compose pull n8n && docker compose up -d" echo "=== Done! ===" echo "n8n should be available at: https://${FQDN}" echo echo "If DNS is via Cloudflare: ensure the record for ${FQDN} is proxied OFF (grey cloud) for the very first TLS issuance," echo "then you can turn it back ON after Traefik has a cert." echo echo "Note: '${NEW_USER}' was added to the 'docker' group. You may need to log out and back in for group changes to take effect."