Files
proxmox-scripts/ct/webapp.sh
T
l.kirchner ddd22b175e Add webapp installer: Next.js site via self-hosted Gitea Actions runner
Host-side script that creates an unprivileged Debian 12 LXC, installs
Node.js + act_runner in host mode, and registers it against the Gitea
instance. Deploy logic lives in the repo's .gitea/workflows/deploy.yml
(deploy-as-code); the runner polls outbound, so no inbound port.
2026-06-05 02:28:20 +02:00

144 lines
6.2 KiB
Bash

#!/usr/bin/env bash
# webapp — Next.js site deployed via a self-hosted Gitea Actions runner
#
# Creates an unprivileged Debian 12 LXC that:
# - installs Node.js + a Gitea act_runner in HOST mode (no Docker, no inbound port)
# - serves the built site with `next start` on :APP_PORT (behind your proxy)
# - lets the repo's .gitea/workflows/deploy.yml build & deploy on every push
# (deploy-as-code; the runner polls Gitea outbound, so nothing is exposed)
#
# Run on a Proxmox VE host:
# bash -c "$(curl -fsSL https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/ct/webapp.sh)"
set -euo pipefail
APP="webapp"
APP_DESCRIPTION="Next.js site deployed via a self-hosted Gitea Actions runner (deploy-as-code)"
APP_PORT="${APP_PORT:-3000}"
LIB_URL="${LIB_URL:-https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/lib}"
INSTALL_SCRIPT_URL="${INSTALL_SCRIPT_URL:-https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/install/webapp-install.sh}"
# LXC defaults (Next build is memory-hungry; runner workspace + live copy need disk)
DEFAULT_HOSTNAME="web"
DEFAULT_DISK="30"
DEFAULT_CORES="2"
DEFAULT_RAM="4096"
# App / runner defaults (all overridable via env)
DEFAULT_GITEA_INSTANCE_URL="https://gitea.luki-net.org"
DEFAULT_REPO_URL="https://gitea.luki-net.org/l.kirchner/Redesign-ad2b.git"
DEFAULT_SITE_URL="https://sichere-wirtschaft.de"
DEFAULT_SANITY_DATASET="production"
DEFAULT_SANITY_API_VERSION="2026-06-02"
DEFAULT_NODE_MAJOR="22"
DEFAULT_RUNNER_VERSION="0.2.13"
DEFAULT_RUNNER_LABELS="webapp:host"
source <(curl -fsSL "$LIB_URL/build.func")
# ── app-specific prompts (host TTY; each skipped if the var is preset) ───────
prompt_app_config() {
echo
echo "── App / runner configuration ───────────────────────────────"
if [[ -z "${GITEA_INSTANCE_URL:-}" ]]; then
read -rp "Gitea instance URL [$DEFAULT_GITEA_INSTANCE_URL]: " GITEA_INSTANCE_URL
GITEA_INSTANCE_URL="${GITEA_INSTANCE_URL:-$DEFAULT_GITEA_INSTANCE_URL}"
fi
# Repo → Settings → Actions → Runners → "Create new runner" gives this token.
if [[ -z "${RUNNER_TOKEN:-}" ]]; then
read -rsp "Gitea runner registration token: " RUNNER_TOKEN; echo
fi
[[ -n "${RUNNER_TOKEN:-}" ]] || { msg_err "RUNNER_TOKEN is required (Repo → Settings → Actions → Runners)"; exit 1; }
if [[ -z "${REPO_URL:-}" ]]; then
read -rp "Website repo URL (informational) [$DEFAULT_REPO_URL]: " REPO_URL
REPO_URL="${REPO_URL:-$DEFAULT_REPO_URL}"
fi
if [[ -z "${NEXT_PUBLIC_SITE_URL:-}" ]]; then
read -rp "Public site URL [$DEFAULT_SITE_URL]: " NEXT_PUBLIC_SITE_URL
NEXT_PUBLIC_SITE_URL="${NEXT_PUBLIC_SITE_URL:-$DEFAULT_SITE_URL}"
fi
if [[ -z "${NEXT_PUBLIC_SANITY_PROJECT_ID:-}" ]]; then
read -rp "Sanity project ID: " NEXT_PUBLIC_SANITY_PROJECT_ID
fi
if [[ -z "${NEXT_PUBLIC_SANITY_DATASET:-}" ]]; then
read -rp "Sanity dataset [$DEFAULT_SANITY_DATASET]: " NEXT_PUBLIC_SANITY_DATASET
NEXT_PUBLIC_SANITY_DATASET="${NEXT_PUBLIC_SANITY_DATASET:-$DEFAULT_SANITY_DATASET}"
fi
if [[ -z "${NEXT_PUBLIC_SANITY_API_VERSION:-}" ]]; then
read -rp "Sanity API version [$DEFAULT_SANITY_API_VERSION]: " NEXT_PUBLIC_SANITY_API_VERSION
NEXT_PUBLIC_SANITY_API_VERSION="${NEXT_PUBLIC_SANITY_API_VERSION:-$DEFAULT_SANITY_API_VERSION}"
fi
if [[ -z "${NEXT_PUBLIC_CALENDLY_URL+x}" ]]; then
read -rp "Calendly URL (optional, empty for none): " NEXT_PUBLIC_CALENDLY_URL
fi
NODE_MAJOR="${NODE_MAJOR:-$DEFAULT_NODE_MAJOR}"
RUNNER_VERSION="${RUNNER_VERSION:-$DEFAULT_RUNNER_VERSION}"
RUNNER_LABELS="${RUNNER_LABELS:-$DEFAULT_RUNNER_LABELS}"
RUNNER_NAME="${RUNNER_NAME:-$CT_HOSTNAME}"
echo " → instance: $GITEA_INSTANCE_URL"
echo " → runner: $RUNNER_NAME labels: $RUNNER_LABELS (act_runner $RUNNER_VERSION, host mode)"
echo " → node: $NODE_MAJOR app port: $APP_PORT"
}
# ── push gathered config into the container for the installer to consume ─────
push_app_config() {
msg_info "Pushing deploy config into container..."
local tmpf; tmpf=$(mktemp)
cat >"$tmpf" <<EOF
GITEA_INSTANCE_URL='$GITEA_INSTANCE_URL'
RUNNER_TOKEN='$RUNNER_TOKEN'
RUNNER_NAME='$RUNNER_NAME'
RUNNER_LABELS='$RUNNER_LABELS'
RUNNER_VERSION='$RUNNER_VERSION'
REPO_URL='$REPO_URL'
APP_PORT='$APP_PORT'
NODE_MAJOR='$NODE_MAJOR'
NEXT_PUBLIC_SITE_URL='$NEXT_PUBLIC_SITE_URL'
NEXT_PUBLIC_SANITY_PROJECT_ID='${NEXT_PUBLIC_SANITY_PROJECT_ID:-}'
NEXT_PUBLIC_SANITY_DATASET='$NEXT_PUBLIC_SANITY_DATASET'
NEXT_PUBLIC_SANITY_API_VERSION='$NEXT_PUBLIC_SANITY_API_VERSION'
NEXT_PUBLIC_CALENDLY_URL='${NEXT_PUBLIC_CALENDLY_URL:-}'
EOF
pct push "$CTID" "$tmpf" /root/webapp.deploy.env --perms 600
rm -f "$tmpf"
}
# ── trailing summary ─────────────────────────────────────────────────────────
print_app_summary() {
local runner_state
runner_state=$(pct exec "$CTID" -- systemctl is-active webapp-runner.service 2>/dev/null | tr -d '\r\n')
cat <<EOF
Site (Next.js): http://$IP_CT:$APP_PORT (live after the first deploy)
→ point your existing reverse proxy at this address
Gitea Actions runner: $RUNNER_NAME [$RUNNER_LABELS] — $runner_state
Instance: $GITEA_INSTANCE_URL
Mode: host (no Docker, outbound poll — no inbound port)
Verify: $GITEA_INSTANCE_URL → repo/Settings → Actions → Runners
Deploy-as-code — commit this to the website repo:
.gitea/workflows/deploy.yml (runs-on: ${RUNNER_LABELS%%:*})
First deploy: push to the repo, or run the workflow manually
(repo → Actions → deploy → "Run workflow")
Logs: pct exec $CTID -- journalctl -u webapp -u webapp-runner -f
Notes file: /root/webapp.credentials (inside the LXC)
EOF
}
# ── orchestrate (custom: inject app config + standard bootstrap) ─────────────
trap _on_error ERR
preflight_pve
show_header "$APP" "$APP_DESCRIPTION"
prompt_lxc_config
prompt_app_config
resolve_debian_template
create_lxc
push_app_config
bootstrap_install_script "$INSTALL_SCRIPT_URL"
print_summary