#!/usr/bin/env bash # webapp installer — runs inside the LXC, called by ct/webapp.sh # # Sets up a Next.js site deployed by a self-hosted Gitea Actions runner: # - Node.js (NodeSource) + git + rsync + sudo # - act_runner in HOST mode (no Docker), registered to the Gitea instance, # running as the unprivileged `webapp` user and polling Gitea outbound # - a systemd service running `next start` from /opt/webapp/current # - a narrow sudoers rule so the runner may only restart that one service # # The actual build/deploy logic lives in the repo's .gitea/workflows/deploy.yml # (deploy-as-code). The runner checks out, builds, syncs the result into # /opt/webapp/current and restarts webapp.service. set -euo pipefail APP="webapp" LIB_URL="${LIB_URL:-https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/lib}" source <(curl -fsSL "$LIB_URL/install.func") [[ "$EUID" -eq 0 ]] || { msg_err "Must run as root"; exit 1; } # ── load deploy config pushed in by the host script ────────────────────────── CONF="/root/webapp.deploy.env" [[ -f "$CONF" ]] || { msg_err "$CONF not found (host bootstrap incomplete)"; exit 1; } set -a; . "$CONF"; set +a : "${GITEA_INSTANCE_URL:?missing GITEA_INSTANCE_URL}" : "${RUNNER_TOKEN:?missing RUNNER_TOKEN}" APP_PORT="${APP_PORT:-3000}" NODE_MAJOR="${NODE_MAJOR:-22}" RUNNER_VERSION="${RUNNER_VERSION:-0.2.13}" RUNNER_LABELS="${RUNNER_LABELS:-webapp:host}" RUNNER_NAME="${RUNNER_NAME:-$(hostname)}" APP_USER="webapp" APP_HOME="/opt/webapp" RUNNER_DIR="$APP_HOME/runner" CURRENT_DIR="$APP_HOME/current" CONF_DIR="/etc/webapp" ENV_FILE="$CONF_DIR/env" run_user() { runuser -u "$APP_USER" -- env HOME="$APP_HOME" "$@"; } # ── packages: git + rsync + sudo ; Node.js via NodeSource ──────────────────── setup_base_apt git rsync sudo NODE_HAVE="$(command -v node >/dev/null 2>&1 && node -v | sed -E 's/^v([0-9]+).*/\1/' || echo 0)" if [[ "$NODE_HAVE" != "$NODE_MAJOR" ]]; then msg_info "Installing Node.js ${NODE_MAJOR}.x (NodeSource)..." curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - >/dev/null apt-get install -y -qq nodejs >/dev/null msg_ok "Node $(node -v) installed" else msg_warn "Node $(node -v) already present, skipping" fi # ── act_runner binary ───────────────────────────────────────────────────────── if [[ ! -x /usr/local/bin/act_runner ]]; then ARCH="$(dpkg --print-architecture)" case "$ARCH" in amd64|arm64) ;; *) msg_err "unsupported arch: $ARCH"; exit 1 ;; esac msg_info "Downloading act_runner $RUNNER_VERSION ($ARCH)..." curl -fsSL "https://dl.gitea.com/act_runner/${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-linux-${ARCH}" \ -o /usr/local/bin/act_runner chmod +x /usr/local/bin/act_runner msg_ok "act_runner $(/usr/local/bin/act_runner --version 2>/dev/null | head -n1)" else msg_warn "act_runner already present, skipping download" fi # ── user + dirs ────────────────────────────────────────────────────────────── create_system_user "$APP_USER" "$APP_HOME" mkdir -p "$RUNNER_DIR" "$CURRENT_DIR" "$CONF_DIR" chown -R "$APP_USER:$APP_USER" "$APP_HOME" # ── env file: NEXT_PUBLIC_* are inlined at build time AND read at runtime ──── # (kept on the host so the repo carries no environment-specific config; the # workflow sources this file before `next build`.) cat >"$ENV_FILE" </etc/sudoers.d/webapp-deploy </dev/null # ── systemd units ───────────────────────────────────────────────────────────── # webapp.service: serves the built site from the live copy. Enabled (so it # starts on boot) but not started now — the first workflow run populates # /opt/webapp/current and starts it via the sudoers-allowed restart. cat >/etc/systemd/system/webapp.service </etc/systemd/system/webapp-runner.service </dev/null 2>&1 systemctl enable --now webapp-runner.service msg_ok "systemd units installed (runner started; webapp.service enabled, starts on first deploy)" # ── notes / summary file ────────────────────────────────────────────────────── CRED_FILE="/root/webapp.credentials" cat >"$CRED_FILE" <} Gitea instance: $GITEA_INSTANCE_URL Runner name / labels: $RUNNER_NAME [$RUNNER_LABELS] (act_runner $RUNNER_VERSION, host mode) Site (local): http://127.0.0.1:$APP_PORT (live after first deploy) Live copy: $CURRENT_DIR Site env (build+run): $ENV_FILE Deploy is driven by the repo workflow .gitea/workflows/deploy.yml First deploy: push to the repo, OR repo -> Actions -> deploy -> "Run workflow" Manual restart: sudo systemctl restart webapp.service Logs: journalctl -u webapp -u webapp-runner -f Prerequisites on the Gitea side: - Actions enabled on the instance and on the repo (repo -> Settings -> Actions) - The LXC needs outbound HTTPS to $GITEA_INSTANCE_URL and to github.com (the latter only to fetch actions/checkout, unless you self-host actions) EOF chmod 600 "$CRED_FILE" # registration token already consumed → drop the bootstrap env file shred -u "$CONF" 2>/dev/null || rm -f "$CONF" apt_cleanup msg_ok "$APP installation finished"