netx/packaging/linux/smoke_test.sh
oliver 846aa3e680 Linux x86_64 offline release; configurable API and bundled PostgreSQL ports.
- packaging/linux: netx-<ver>-linux-x86_64.tar.gz with bundled Python 3.12,
  PostgreSQL 16 and web/dist (glibc >= 2.17). install.sh (install/update/
  reinstall), netxctl (start/stop/status/logs, set-port, set-bind,
  set-pg-port, reconfigure-db, check-update/update/auto-update, backup,
  uninstall), systemd units, nohup fallback without systemd. Privilege
  drop, detaching and tar go through the bundled Python, so bare images
  without su/runuser/setsid/tar work.
- Updates: same GitHub/Forgejo/manifest sources as Windows, sha256
  verified, pre-update pg_dump, automatic rollback when the new version
  does not come up.
- Non-loopback bind: generated bootstrap admin password and
  NETX_UME_VERIFY_TLS=true, as security_bootstrap requires.
- Build in manylinux2014 + check_bundle.sh (GLIBC <= 2.17, no missing
  libs); release-linux.yml builds on v* tags and smoke-tests offline on
  CentOS 7, Rocky 9, Ubuntu 18.04/24.04, Debian 12, openSUSE Leap 15 and
  openEuler 22.03 before attaching to the release.
- Windows: Setup DB page gets a bundled PG port field and
  /BundledPgPort=; set_pg_port.ps1 + tray "Built-in database port";
  reconfigure keeps the configured port; postgresql.conf is written
  without a BOM (PS 5.1 Set-Content -Encoding utf8 broke postgres).
- Forgejo/GitHub publish scripts include the Linux package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:09:07 +08:00

109 lines
4.9 KiB
Bash
Executable file

#!/usr/bin/env bash
# Offline install smoke test, run inside a bare distro container as root:
# docker run --rm --network none -v "$PWD/packaging/release:/pkg:ro" \
# -v "$PWD/packaging/linux/smoke_test.sh:/smoke.sh:ro" centos:7 bash /smoke.sh /pkg/netx-X-linux-x86_64.tar.gz
# Exercises: install (custom ports) -> login -> set-port -> set-pg-port -> update
# (same package, with backup) -> data survives -> stop -> uninstall --purge.
set -euo pipefail
TARBALL="$1"
PKGDIR="${2:-}" # pre-extracted package (minimal images may lack tar)
PORT1=18890 PORT2=18891 PG1=25432 PG2=25433
step() { printf '\n\033[36m### %s\033[0m\n' "$*"; }
fail() { printf '\033[31m[SMOKE FAIL] %s\033[0m\n' "$*" >&2; tail -n 60 /var/lib/netx/logs/*.log 2>/dev/null >&2 || true; exit 1; }
PY() { /opt/netx/current/python/bin/python3 -I -c "$@"; }
login() { # login <port> -> prints access token
PY "
import json, urllib.request
req = urllib.request.Request('http://127.0.0.1:$1/v1/auth/login',
data=json.dumps({'username': 'admin', 'password': open('/var/lib/netx/.env').read().split('NETX_BOOTSTRAP_ADMIN_PASSWORD=')[1].split()[0]}).encode(),
headers={'Content-Type': 'application/json'})
import time
for attempt in range(3):
try:
body = json.load(urllib.request.urlopen(req, timeout=60))
break
except Exception:
if attempt == 2:
raise
time.sleep(5)
print(body.get('access_token') or body['token'])
"
}
health() { PY "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:$1/health', timeout=5).status == 200 else 1)" 2>/dev/null; }
listening() { # listening <port> -> 0 when something accepts on 127.0.0.1:<port>
PY "import socket,sys; s=socket.socket(); s.settimeout(1); sys.exit(s.connect_ex(('127.0.0.1', $1)))"
}
step "environment: $(. /etc/os-release && echo "${PRETTY_NAME}") / $(getconf GNU_LIBC_VERSION)"
if [[ -n "${PKGDIR}" ]]; then
work="$(mktemp -d)"
cp -a "${PKGDIR}" "${work}/"
else
work="$(mktemp -d)"
tar xzf "${TARBALL}" -C "${work}"
fi
pkg="$(echo "${work}"/netx-*)"
step "install (bundled PG ${PG1}, API ${PORT1})"
"${pkg}/install.sh" --yes --no-service --db bundled --port "${PORT1}" --pg-port "${PG1}" || fail "install"
health "${PORT1}" || fail "health after install"
listening "${PG1}" || fail "PG not on ${PG1}"
token="$(login "${PORT1}")" || fail "login"
[[ -n "${token}" ]] || fail "empty token"
netxctl status || fail "status"
step "marker row survives update"
PGPASSWORD="$(grep '^NETX_DATABASE_URL=' /var/lib/netx/.env | sed 's#.*://netx:\([^@]*\)@.*#\1#')" \
/opt/netx/current/pgsql/bin/psql -h 127.0.0.1 -p "${PG1}" -U netx -d netx -v ON_ERROR_STOP=1 \
-c "CREATE TABLE IF NOT EXISTS smoke_marker(v text); INSERT INTO smoke_marker VALUES ('kept');" || fail "marker"
step "set-port ${PORT2}"
netxctl set-port "${PORT2}" || fail "set-port"
health "${PORT2}" || fail "health after set-port"
step "set-pg-port ${PG2}"
netxctl set-pg-port "${PG2}" || fail "set-pg-port"
health "${PORT2}" || fail "health after set-pg-port"
listening "${PG2}" || fail "PG not on ${PG2}"
listening "${PG1}" && fail "PG still on ${PG1}"
step "set-pg-port to a busy port is refused"
if netxctl set-pg-port "${PORT2}"; then fail "busy port accepted"; fi
health "${PORT2}" || fail "health after refused port change"
step "update with the same package (--force path via install.sh --mode update)"
"${pkg}/install.sh" --mode update --yes || fail "update"
health "${PORT2}" || fail "health after update"
ls /var/lib/netx/backups/pre-*.dump >/dev/null || fail "no pre-update dump"
out="$(PGPASSWORD="$(grep '^NETX_DATABASE_URL=' /var/lib/netx/.env | sed 's#.*://netx:\([^@]*\)@.*#\1#')" \
/opt/netx/current/pgsql/bin/psql -h 127.0.0.1 -p "${PG2}" -U netx -d netx -tAc "SELECT v FROM smoke_marker")"
[[ "${out}" == "kept" ]] || fail "marker lost: ${out}"
login "${PORT2}" >/dev/null || fail "login after update"
step "offline update via netxctl update --file (same version needs --force)"
if netxctl update --file "${TARBALL}" --yes; then fail "same-version update without --force accepted"; fi
netxctl update --file "${TARBALL}" --yes --force || fail "netxctl update --file --force"
health "${PORT2}" || fail "health after netxctl update"
step "backup"
netxctl backup /tmp/netx-backup.tar.gz || fail "backup"
netxctl_admin_list="$(/opt/netx/current/python/bin/python3 -I /opt/netx/current/packaging/linux/netx_admin.py tar-list /tmp/netx-backup.tar.gz)"
[[ "${netxctl_admin_list}" == *netx.dump* ]] || fail "backup content"
step "stop"
netxctl stop || fail "stop"
health "${PORT2}" && fail "still healthy after stop"
listening "${PG2}" && fail "PG still running after stop"
pgrep -f netx_api >/dev/null 2>&1 && fail "netx_api processes left" || true
step "start again + uninstall --purge"
netxctl start || fail "restart"
netxctl uninstall --purge --yes || fail "uninstall"
[[ ! -e /opt/netx && ! -e /var/lib/netx && ! -e /usr/bin/netxctl ]] || fail "leftovers after purge"
printf '\n\033[32m[SMOKE OK] %s\033[0m\n' "$(. /etc/os-release && echo "${PRETTY_NAME}")"