Files
CurrenciCombo/scripts/deployment/install.sh
Devin AI 361776ab2e
Some checks failed
CI / Frontend Lint (pull_request) Failing after 7s
CI / Frontend Type Check (pull_request) Failing after 6s
CI / Frontend Build (pull_request) Failing after 8s
CI / Frontend E2E Tests (pull_request) Failing after 8s
CI / Orchestrator Build (pull_request) Failing after 7s
CI / Orchestrator Unit Tests (pull_request) Failing after 6s
CI / Orchestrator E2E (Testcontainers) (pull_request) Has been skipped
CI / Contracts Compile (pull_request) Failing after 6s
CI / Contracts Test (pull_request) Failing after 7s
Code Quality / SonarQube Analysis (pull_request) Failing after 19s
Code Quality / Code Quality Checks (pull_request) Failing after 6s
Security Scan / Dependency Vulnerability Scan (pull_request) Failing after 4s
Security Scan / OWASP ZAP Scan (pull_request) Failing after 5s
PR AA: Phoenix / systemd deployment scaffolding (migrate Phoenix off Next.js stub)
Closes the gap between Gitea main (b48eb2a, Vite portal + Node
orchestrator, 29 PRs merged, 167 tests) and what's actually serving
curucombo.xn--vov0g.com (Next.js 'ISO-20022 Combo Flow' app from an
unpushed local b118b2b checkout). After this PR is merged and the
runbook in scripts/deployment/README.md is followed on CT 8604, the
Phoenix deployment will serve d-bis/CurrenciCombo main.

Artifacts (all under scripts/deployment/):
- systemd/currencicombo-orchestrator.service  - Node orchestrator,
  EnvironmentFile=/etc/currencicombo/orchestrator.env, full systemd
  hardening (ProtectSystem=strict, PrivateTmp, no caps).
- systemd/currencicombo-webapp.service        - nginx serving Vite
  SPA on :3000 via RuntimeDirectory=/run/currencicombo-webapp.
- webapp-nginx.conf                            - self-contained nginx
  config; intentionally 421s on /api/* and /events/* so an NPMplus
  misconfig fails loudly instead of silently returning index.html.
- .env.prod.example                            - template for
  /etc/currencicombo/orchestrator.env. Documents every EXT-* blocker
  env var 1:1 with the Proxmox repo's check-external-dependencies.sh.
- install.sh                                   - idempotent host setup:
  user, dirs, nginx, fresh Postgres role/DB (--force-recreate-db to
  wipe), Redis autodetect, env file with auto-generated
  EVENT_SIGNING_SECRET + 3 API keys, systemd units enabled but not
  started. --dry-run supported.
- deploy-currencicombo-8604.sh                 - build-and-swap deploy
  driver (the script deploy-targets.json / phoenix-deploy-api calls):
  git fetch/reset, orchestrator tsc build, portal vite build with
  VITE_ORCHESTRATOR_URL baked in, migrations, timestamped backup,
  systemctl stop, rsync, systemctl start, smoke /ready + portal /,
  grep EXT-* from journalctl. --ref, --dry-run, --skip-migrate,
  --skip-build, --rollback.
- README.md                                    - architecture diagram,
  first-time setup (8 steps), NPMplus ingress rule table, subsequent-
  deploy one-liner, rollback, troubleshooting table, cutover-from-
  pre-existing-Next.js sequence, explicit list of Proxmox-side
  follow-ups.

Target-agnostic: no IP / hostname / VLAN hardcoded. The only file that
embeds the public hostname is README.md (for documentation) and the
default VITE_ORCHESTRATOR_URL in deploy-currencicombo-8604.sh (which
is overridable via env).

Single-origin NPMplus routing (confirmed with user):
  curucombo.\xe6\x9b\xbc\xe6\x9d\x8e.com/api/*     -> 10.160.0.14:8080  (orchestrator)
  curucombo.\xe6\x9b\xbc\xe6\x9d\x8e.com/events/*  -> 10.160.0.14:8080  (SSE)
  curucombo.\xe6\x9b\xbc\xe6\x9d\x8e.com/*         -> 10.160.0.14:3000  (Vite SPA)

Verified on this box (headless):
- shellcheck --severity=warning: clean on both scripts.
- bash -n: clean on both scripts.
- systemd-analyze verify: both unit files parse cleanly (only complaint
  is /usr/sbin/nginx not being executable, expected -- nginx is
  installed at deploy time).
- install.sh --dry-run: fails fast with the expected FATAL on hosts
  without psql (build box). On CT 8604 with Postgres+Redis already
  installed, it walks through every step.
- deploy-currencicombo-8604.sh --help: prints the usage.

No runtime code changes. Non-UI. Complements PR #30 (docker-compose
sandbox) which remains the local-dev path.

Proxmox-side follow-up (separate commit on /home/intlc/projects/proxmox
after this PR merges and cutover runs cleanly):
- Update phoenix-deploy-api/deploy-targets.json to point at
  scripts/deployment/deploy-currencicombo-8604.sh.
- Retire the inaccurate "Next.js webapp with ignoreBuildErrors"
  language in EXTERNAL_DEPENDENCY_BLOCKERS.md.

Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
2026-04-22 23:05:18 +00:00

204 lines
8.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# install.sh — idempotent first-time setup for CurrenciCombo on a systemd host.
#
# Intended to run ONCE per host as root (or with sudo). Running it again is
# safe: it will skip already-present artifacts and warn on conflicts.
#
# What this does:
# 1. Creates the `currencicombo` system user and /opt/currencicombo tree.
# 2. Installs nginx (Debian/Ubuntu or Alpine) if not present.
# 3. Ensures a local Postgres is running and creates a fresh
# `currencicombo` role + DB (refuses to touch an existing one unless
# --force-recreate is passed).
# 4. Ensures a local Redis is running.
# 5. Writes /etc/currencicombo/orchestrator.env from .env.prod.example,
# auto-populating EVENT_SIGNING_SECRET and ORCHESTRATOR_API_KEYS with
# fresh randoms the first time.
# 6. Installs /etc/currencicombo/webapp-nginx.conf.
# 7. Installs the two systemd units and runs `systemctl daemon-reload`.
# 8. Enables (does NOT start) both units. First start happens via
# scripts/deployment/deploy-currencicombo-8604.sh after the first
# successful build.
#
# This script is target-agnostic. It has no hardcoded IP / hostname /
# VLAN. The NPMplus ingress in front of it is configured separately —
# see scripts/deployment/README.md.
set -euo pipefail
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
APP_USER="currencicombo"
APP_HOME="/opt/currencicombo"
ETC_DIR="/etc/currencicombo"
LOG_DIR="/var/log/currencicombo"
REPO_DIR="/var/lib/currencicombo/repo"
ENV_FILE="${ETC_DIR}/orchestrator.env"
NGINX_FILE="${ETC_DIR}/webapp-nginx.conf"
SYSTEMD_DIR="/etc/systemd/system"
FORCE_RECREATE_DB=0
DRY_RUN=0
SKIP_NGINX_INSTALL=0
log() { printf '[install] %s\n' "$*" >&2; }
warn() { printf '[install][WARN] %s\n' "$*" >&2; }
die() { printf '[install][FATAL] %s\n' "$*" >&2; exit 1; }
run() { if [[ "${DRY_RUN}" -eq 1 ]]; then printf '[install][dry-run] %s\n' "$*" >&2; else eval "$*"; fi; }
usage() {
cat <<'USAGE'
Usage: sudo ./install.sh [--force-recreate-db] [--skip-nginx-install] [--dry-run]
--force-recreate-db DROP and recreate the currencicombo Postgres role
and DB even if they already exist. DESTRUCTIVE.
--skip-nginx-install Do not apt/apk install nginx (use if you already
have a custom nginx build in place).
--dry-run Print the commands that would run, don't run them.
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--force-recreate-db) FORCE_RECREATE_DB=1; shift ;;
--skip-nginx-install) SKIP_NGINX_INSTALL=1; shift ;;
--dry-run) DRY_RUN=1; shift ;;
-h|--help) usage; exit 0 ;;
*) die "unknown arg: $1" ;;
esac
done
[[ "$EUID" -eq 0 ]] || die "must run as root (sudo)"
# ----------------------------------------------------------------------
# 1. User + tree
# ----------------------------------------------------------------------
if id "${APP_USER}" >/dev/null 2>&1; then
log "user ${APP_USER} already exists"
else
log "creating system user ${APP_USER}"
run useradd --system --home-dir "${APP_HOME}" --shell /usr/sbin/nologin --user-group "${APP_USER}"
fi
for d in "${APP_HOME}" "${APP_HOME}/orchestrator" "${APP_HOME}/webapp" \
"${APP_HOME}/webapp/dist" "${ETC_DIR}" "${LOG_DIR}" "${REPO_DIR}"; do
run install -d -o "${APP_USER}" -g "${APP_USER}" -m 0755 "$d"
done
run chown "${APP_USER}:${APP_USER}" "${APP_HOME}" "${LOG_DIR}" "${REPO_DIR}"
run chmod 0750 "${ETC_DIR}"
# ----------------------------------------------------------------------
# 2. nginx (required by currencicombo-webapp.service)
# ----------------------------------------------------------------------
if [[ "${SKIP_NGINX_INSTALL}" -eq 0 ]]; then
if command -v nginx >/dev/null 2>&1; then
log "nginx already installed ($(nginx -v 2>&1 | head -1))"
elif command -v apt-get >/dev/null 2>&1; then
log "installing nginx via apt"
run 'DEBIAN_FRONTEND=noninteractive apt-get update -q'
run 'DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nginx-light'
# We use our own nginx.conf via -c, so disable the distro site.
run systemctl disable --now nginx 2>/dev/null || true
elif command -v apk >/dev/null 2>&1; then
log "installing nginx via apk"
run apk add --no-cache nginx
run rc-update del nginx 2>/dev/null || true
else
die "no apt or apk available — install nginx manually or re-run with --skip-nginx-install"
fi
fi
[[ -f /etc/nginx/mime.types ]] || warn "/etc/nginx/mime.types missing; webapp-nginx.conf may fail"
# ----------------------------------------------------------------------
# 3. Postgres role + DB
# ----------------------------------------------------------------------
if ! command -v psql >/dev/null 2>&1; then
die "psql not on PATH — install Postgres on this host (e.g. apt install postgresql) before running install.sh"
fi
# Use the OS `postgres` superuser for DDL.
pg_role_exists() {
sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${APP_USER}';" 2>/dev/null | grep -q 1
}
pg_db_exists() {
sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${APP_USER}';" 2>/dev/null | grep -q 1
}
if pg_role_exists; then
if [[ "${FORCE_RECREATE_DB}" -eq 1 ]]; then
log "dropping existing role/DB (--force-recreate-db)"
run "sudo -u postgres psql -c 'DROP DATABASE IF EXISTS ${APP_USER};'"
run "sudo -u postgres psql -c 'DROP ROLE IF EXISTS ${APP_USER};'"
else
warn "Postgres role ${APP_USER} already exists — skipping role/DB creation. Re-run with --force-recreate-db to wipe."
fi
fi
if ! pg_role_exists; then
log "creating Postgres role ${APP_USER}"
run "sudo -u postgres psql -c \"CREATE ROLE ${APP_USER} LOGIN;\""
fi
if ! pg_db_exists; then
log "creating Postgres database ${APP_USER}"
run "sudo -u postgres psql -c \"CREATE DATABASE ${APP_USER} OWNER ${APP_USER};\""
fi
# Peer auth from the currencicombo OS user → currencicombo DB role "just works"
# on Debian-style pg_hba (local all all peer). No password needed.
# ----------------------------------------------------------------------
# 4. Redis
# ----------------------------------------------------------------------
if systemctl list-unit-files | grep -q '^redis-server\.service'; then
run systemctl enable --now redis-server
elif systemctl list-unit-files | grep -q '^redis\.service'; then
run systemctl enable --now redis
elif command -v redis-cli >/dev/null 2>&1; then
warn "redis-cli present but no redis-server.service / redis.service unit — assuming external Redis"
else
warn "redis not detected; orchestrator will fall back to in-process event bus. Install redis for multi-replica support."
fi
# ----------------------------------------------------------------------
# 5. orchestrator.env
# ----------------------------------------------------------------------
if [[ -f "${ENV_FILE}" ]]; then
log "${ENV_FILE} already exists — leaving alone"
else
log "writing ${ENV_FILE}"
install -o "${APP_USER}" -g "${APP_USER}" -m 0640 "${SCRIPT_DIR}/.env.prod.example" "${ENV_FILE}"
# Auto-fill the two REQUIRED secrets so first boot doesn't crash.
SECRET="$(openssl rand -hex 32)"
INIT_KEY="$(openssl rand -hex 24)"
SETT_KEY="$(openssl rand -hex 24)"
AUD_KEY="$(openssl rand -hex 24)"
run "sed -i 's|^EVENT_SIGNING_SECRET=.*|EVENT_SIGNING_SECRET=${SECRET}|' '${ENV_FILE}'"
run "sed -i 's|^ORCHESTRATOR_API_KEYS=.*|ORCHESTRATOR_API_KEYS=${INIT_KEY}:initiator,${SETT_KEY}:settler,${AUD_KEY}:auditor|' '${ENV_FILE}'"
log " generated EVENT_SIGNING_SECRET (64 hex)"
log " generated 3 API keys (initiator/settler/auditor) — grep ${ENV_FILE}"
fi
# ----------------------------------------------------------------------
# 6. webapp-nginx.conf
# ----------------------------------------------------------------------
run install -o "${APP_USER}" -g "${APP_USER}" -m 0644 \
"${SCRIPT_DIR}/webapp-nginx.conf" "${NGINX_FILE}"
# ----------------------------------------------------------------------
# 7. systemd units
# ----------------------------------------------------------------------
run install -o root -g root -m 0644 \
"${SCRIPT_DIR}/systemd/currencicombo-orchestrator.service" \
"${SYSTEMD_DIR}/currencicombo-orchestrator.service"
run install -o root -g root -m 0644 \
"${SCRIPT_DIR}/systemd/currencicombo-webapp.service" \
"${SYSTEMD_DIR}/currencicombo-webapp.service"
run systemctl daemon-reload
# ----------------------------------------------------------------------
# 8. Enable (but do NOT start yet — no build exists)
# ----------------------------------------------------------------------
run systemctl enable currencicombo-orchestrator.service
run systemctl enable currencicombo-webapp.service
log "install complete."
log " next: run scripts/deployment/deploy-currencicombo-8604.sh as root to build + start."