From 8007433d5f0e2e8b9a4ae47f4894dda14efbe172 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Tue, 24 Feb 2026 14:02:04 +0100 Subject: [PATCH] feat(ops): systemd units + smoke test --- DEPLOYMENT.md | 16 ++++ docs/admin/opensuse-operator-kit.md | 24 ++++-- scripts/smoke-test.sh | 122 ++++++++++++++++++++++++++++ systemd/likwid-demo.service | 15 ++++ systemd/likwid-prod.service | 15 ++++ 5 files changed, 184 insertions(+), 8 deletions(-) create mode 100755 scripts/smoke-test.sh create mode 100644 systemd/likwid-demo.service create mode 100644 systemd/likwid-prod.service diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 2c4b13a..199f1aa 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -68,6 +68,22 @@ For VPS deployments, it is recommended to bind demo ports to localhost and put a - Point `API_BASE` at your public domain (for example `https://openlikwid.org`) - Serve the demo via the reverse proxy on `80/443` +## Start on boot (systemd --user) + +This repo includes `systemd --user` unit templates: + +- `systemd/likwid-demo.service` +- `systemd/likwid-prod.service` + +These units assume the repo is checked out at `~/likwid` for the target user, and they run `podman compose` using the env files under `compose/`. + +## Smoke test + +An operator smoke test script is provided: + +- `scripts/smoke-test.sh demo` +- `scripts/smoke-test.sh prod` + ## Demo Instance Details ### Demo Accounts diff --git a/docs/admin/opensuse-operator-kit.md b/docs/admin/opensuse-operator-kit.md index a937b92..c4388d5 100644 --- a/docs/admin/opensuse-operator-kit.md +++ b/docs/admin/opensuse-operator-kit.md @@ -44,26 +44,26 @@ If your Podman build does not provide `podman compose`, install the compose inte git clone https://codeberg.org/likwid/likwid.git ~/likwid ``` -2. Create the production env file: +1. Create the production env file: ```bash cp ~/likwid/compose/.env.production.example ~/likwid/compose/.env.production ``` -3. Edit `~/likwid/compose/.env.production`: +1. Edit `~/likwid/compose/.env.production`: - `POSTGRES_PASSWORD` - `JWT_SECRET` - `API_BASE` (should be your public URL, e.g. `https://your.domain`) -4. Start services: +1. Start services: ```bash cd ~/likwid podman compose --env-file compose/.env.production -f compose/production.yml up -d --build ``` -5. Create the first admin and complete setup: +1. Create the first admin and complete setup: - Register the first user at `/register` (first user becomes platform admin) - Complete `/setup` @@ -139,10 +139,12 @@ Podman is most reliable on openSUSE when managed as a rootless user service. sudo loginctl enable-linger deploy ``` -2. Create a systemd user unit: +1. Create a systemd user unit: - File: `~/.config/systemd/user/likwid-demo.service` +- Template: `systemd/likwid-demo.service` (from this repo) + ```ini [Unit] Description=Likwid demo (podman compose) @@ -153,7 +155,7 @@ After=network-online.target Type=oneshot RemainAfterExit=yes WorkingDirectory=%h/likwid -ExecStart=/usr/bin/podman compose --env-file compose/.env.demo -f compose/demo.yml -f compose/demo.vps.override.yml up -d --build +ExecStart=/usr/bin/podman compose --env-file compose/.env.demo -f compose/demo.yml -f compose/demo.vps.override.yml up -d ExecStop=/usr/bin/podman compose --env-file compose/.env.demo -f compose/demo.yml -f compose/demo.vps.override.yml down TimeoutStartSec=0 @@ -161,17 +163,23 @@ TimeoutStartSec=0 WantedBy=default.target ``` -3. Enable and start it: +1. Enable and start it: ```bash systemctl --user daemon-reload systemctl --user enable --now likwid-demo.service ``` -4. Inspect service logs: +1. Inspect service logs: ```bash journalctl --user -u likwid-demo.service -f ``` For production, create a separate unit (for example `likwid-prod.service`) with the production env file and compose file. + +## Smoke test + +After deploy/update, run: + +- `./scripts/smoke-test.sh demo` diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh new file mode 100755 index 0000000..0645a19 --- /dev/null +++ b/scripts/smoke-test.sh @@ -0,0 +1,122 @@ +#!/bin/bash +set -euo pipefail + +MODE="${1:-}" + +if [ -z "$MODE" ] || { [ "$MODE" != "demo" ] && [ "$MODE" != "prod" ] && [ "$MODE" != "production" ]; }; then + echo "Usage: ./scripts/smoke-test.sh " >&2 + exit 2 +fi + +if [ "$MODE" = "production" ]; then + MODE="prod" +fi + +if ! command -v curl >/dev/null 2>&1; then + echo "curl is required" >&2 + exit 1 +fi + +if ! command -v podman >/dev/null 2>&1; then + echo "podman is required" >&2 + exit 1 +fi + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [ "$MODE" = "demo" ]; then + ENV_FILE="$ROOT_DIR/compose/.env.demo" + BACKEND_CONTAINER="likwid-demo-backend" + FRONTEND_CONTAINER="likwid-demo-frontend" + DB_CONTAINER="likwid-demo-db" +else + ENV_FILE="$ROOT_DIR/compose/.env.production" + BACKEND_CONTAINER="likwid-prod-backend" + FRONTEND_CONTAINER="likwid-prod-frontend" + DB_CONTAINER="likwid-prod-db" +fi + +if [ ! -f "$ENV_FILE" ]; then + echo "Missing env file: $ENV_FILE" >&2 + exit 1 +fi + +set -a +# shellcheck disable=SC1090 +source <(sed 's/\r$//' "$ENV_FILE") +set +a + +BACKEND_PORT="${BACKEND_PORT:-3000}" +FRONTEND_PORT="${FRONTEND_PORT:-4321}" +POSTGRES_USER="${POSTGRES_USER:-likwid}" +POSTGRES_DB="${POSTGRES_DB:-likwid_prod}" + +if [ "$MODE" = "demo" ]; then + BACKEND_PORT="${BACKEND_PORT:-3001}" + FRONTEND_PORT="${FRONTEND_PORT:-4322}" + POSTGRES_USER="${POSTGRES_USER:-likwid_demo}" + POSTGRES_DB="${POSTGRES_DB:-likwid_demo}" +fi + +BACKEND_HEALTH_URL="http://127.0.0.1:${BACKEND_PORT}/health" +FRONTEND_URL="http://127.0.0.1:${FRONTEND_PORT}/" + +fail() { + echo "FAIL: $1" >&2 + exit 1 +} + +ok() { + echo "OK: $1" +} + +echo "=== Likwid smoke test ($MODE) ===" + +echo "[1/4] Containers present" +podman container exists "$BACKEND_CONTAINER" || fail "Missing container: $BACKEND_CONTAINER" +podman container exists "$FRONTEND_CONTAINER" || fail "Missing container: $FRONTEND_CONTAINER" +podman container exists "$DB_CONTAINER" || fail "Missing container: $DB_CONTAINER" + +backend_running="$(podman inspect -f '{{.State.Running}}' "$BACKEND_CONTAINER" 2>/dev/null || true)" +frontend_running="$(podman inspect -f '{{.State.Running}}' "$FRONTEND_CONTAINER" 2>/dev/null || true)" +db_running="$(podman inspect -f '{{.State.Running}}' "$DB_CONTAINER" 2>/dev/null || true)" + +[ "$backend_running" = "true" ] || fail "$BACKEND_CONTAINER is not running" +[ "$frontend_running" = "true" ] || fail "$FRONTEND_CONTAINER is not running" +[ "$db_running" = "true" ] || fail "$DB_CONTAINER is not running" + +ok "containers running" + +echo "[2/4] Backend health: $BACKEND_HEALTH_URL" +health_json="$(curl -fsS "$BACKEND_HEALTH_URL")" || fail "backend health check failed" +echo "$health_json" +ok "backend /health" + +echo "[3/4] Frontend reachable: $FRONTEND_URL" +http_code="$(curl -fsS -o /dev/null -w "%{http_code}" "$FRONTEND_URL" || true)" +case "$http_code" in + 2*|3*) ok "frontend HTTP $http_code";; + *) fail "frontend not reachable (HTTP $http_code)";; +esac + +echo "[4/4] Database reachable + migrations" +podman exec "$DB_CONTAINER" pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null || fail "pg_isready failed" + +migration_table_check="$(podman exec "$DB_CONTAINER" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -tAc "SELECT to_regclass('_sqlx_migrations');" | tr -d '[:space:]')" +if [ "$migration_table_check" != "_sqlx_migrations" ]; then + fail "_sqlx_migrations table not found" +fi + +failed_count="$(podman exec "$DB_CONTAINER" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -tAc "SELECT COUNT(*) FROM _sqlx_migrations WHERE success = false;" | tr -d '[:space:]')" +if [ "$failed_count" != "0" ]; then + fail "found failed migrations: $failed_count" +fi + +installed_count="$(podman exec "$DB_CONTAINER" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -tAc "SELECT COUNT(*) FROM _sqlx_migrations;" | tr -d '[:space:]')" +if [ -z "$installed_count" ] || [ "$installed_count" -lt 1 ]; then + fail "no migrations recorded" +fi + +ok "db reachable, migrations applied ($installed_count)" + +echo "=== Smoke test passed ===" diff --git a/systemd/likwid-demo.service b/systemd/likwid-demo.service new file mode 100644 index 0000000..bd894e8 --- /dev/null +++ b/systemd/likwid-demo.service @@ -0,0 +1,15 @@ +[Unit] +Description=Likwid demo (podman compose) +Wants=network-online.target +After=network-online.target + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=%h/likwid +ExecStart=/usr/bin/podman compose --env-file compose/.env.demo -f compose/demo.yml -f compose/demo.vps.override.yml up -d +ExecStop=/usr/bin/podman compose --env-file compose/.env.demo -f compose/demo.yml -f compose/demo.vps.override.yml down +TimeoutStartSec=0 + +[Install] +WantedBy=default.target diff --git a/systemd/likwid-prod.service b/systemd/likwid-prod.service new file mode 100644 index 0000000..3087255 --- /dev/null +++ b/systemd/likwid-prod.service @@ -0,0 +1,15 @@ +[Unit] +Description=Likwid production (podman compose) +Wants=network-online.target +After=network-online.target + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=%h/likwid +ExecStart=/usr/bin/podman compose --env-file compose/.env.production -f compose/production.yml up -d +ExecStop=/usr/bin/podman compose --env-file compose/.env.production -f compose/production.yml down +TimeoutStartSec=0 + +[Install] +WantedBy=default.target