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.
144 lines
6.2 KiB
Bash
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
|