mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-03 02:26:43 +09:00
247 lines
10 KiB
YAML
247 lines
10 KiB
YAML
name: Scripts CI
|
|
|
|
# Static + unit checks for the shell scripts that install/update/uninstall
|
|
# ServerKit on a server. These ran nowhere before — so a bug like the 1.7.0
|
|
# updater silently dying on `set -euo pipefail` was only ever found on a real
|
|
# box. shellcheck catches that whole class statically; the test_*.sh suites
|
|
# exercise the install/update/uninstall + lib logic against fixtures with no
|
|
# server, on every supported distro family.
|
|
#
|
|
# The unit suites *source* the scripts, though — the top-level run block
|
|
# (exactly where the 2026-07-02 update abort lived) executes in none of them.
|
|
# The update-e2e job closes that gap with a REAL install + REAL update on the
|
|
# runner VM, so the code path a user's box actually runs is gated on every PR.
|
|
|
|
on:
|
|
push:
|
|
branches: [dev, main]
|
|
paths:
|
|
- 'scripts/**'
|
|
- 'templates/**'
|
|
- 'install.sh'
|
|
- 'uninstall.sh'
|
|
- 'serverkit'
|
|
- '.github/workflows/scripts-ci.yml'
|
|
pull_request:
|
|
branches: [dev, main]
|
|
paths:
|
|
- 'scripts/**'
|
|
- 'templates/**'
|
|
- 'install.sh'
|
|
- 'uninstall.sh'
|
|
- 'serverkit'
|
|
- '.github/workflows/scripts-ci.yml'
|
|
workflow_call:
|
|
|
|
jobs:
|
|
shell:
|
|
name: lint + unit
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Syntax check (bash -n)
|
|
run: |
|
|
status=0
|
|
for f in serverkit install.sh uninstall.sh scripts/*.sh scripts/lib/*.sh scripts/test/*.sh; do
|
|
[ -f "$f" ] || continue
|
|
if bash -n "$f"; then
|
|
echo "ok $f"
|
|
else
|
|
echo "FAIL $f"; status=1
|
|
fi
|
|
done
|
|
exit $status
|
|
|
|
- name: ShellCheck (error severity — installer/updater/uninstaller surface)
|
|
run: |
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq shellcheck
|
|
# Errors fail the job. The CLI + installer carry pre-existing advisory
|
|
# warnings (unused color vars, etc.), so they are gated at error level.
|
|
shellcheck --severity=error \
|
|
scripts/update.sh scripts/install.sh install.sh uninstall.sh serverkit \
|
|
scripts/lib/*.sh scripts/test/*.sh
|
|
|
|
- name: ShellCheck (warning severity — new libs must stay clean)
|
|
run: |
|
|
# The lib/ abstraction was written warning-clean; keep it that way so
|
|
# quality doesn't rot. -x follows `source`d files.
|
|
shellcheck --severity=warning -x scripts/lib/*.sh
|
|
|
|
- name: Unit tests (update + install + lib + cli + agent install)
|
|
run: |
|
|
bash scripts/test/test_update.sh
|
|
bash scripts/test/test_install.sh
|
|
bash scripts/test/test_lib.sh
|
|
bash scripts/test/test_cli.sh
|
|
bash scripts/test/test_agent_install.sh
|
|
bash scripts/test/test_stage.sh
|
|
|
|
- name: nginx -t smoke (shipped vhosts parse on a real nginx)
|
|
run: |
|
|
# The panel frontend is served straight from these vhosts (static SPA +
|
|
# /api + /socket.io proxy). Parse them with the real nginx so a broken
|
|
# directive fails here, not with a 502 on someone's server. The smoke
|
|
# script stages each vhost against nginx and skips if nginx is absent —
|
|
# so install it first to make sure the step actually runs.
|
|
sudo apt-get install -y -qq nginx openssl
|
|
bash scripts/test/test_nginx_conf.sh
|
|
|
|
cross-distro:
|
|
name: unit (${{ matrix.image }})
|
|
runs-on: ubuntu-latest
|
|
# ServerKit targets several Linux families; the scripts' logic (symlink
|
|
# resolution, locking, refresh_config, deployment/OS/pkg/init detection,
|
|
# the uninstall matrix) must behave the same on Debian-, RHEL-, and
|
|
# SUSE-derived hosts, not just the Ubuntu runner. Run the unit suites inside
|
|
# each distro's container so a portability regression fails here instead of
|
|
# on someone's server.
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
image:
|
|
- 'ubuntu:22.04'
|
|
- 'ubuntu:24.04'
|
|
- 'debian:12'
|
|
- 'rockylinux:9'
|
|
- 'almalinux:9'
|
|
- 'fedora:40'
|
|
- 'opensuse/leap:15.5'
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: bash -n + unit suites on ${{ matrix.image }}
|
|
run: |
|
|
docker run --rm -v "$PWD:/src" -w /src "${{ matrix.image }}" sh -c '
|
|
set -e
|
|
# bash ships in every image here; the suites stub the real tools
|
|
# (curl/docker/systemd/flask) so nothing else is required. Best-effort
|
|
# bash install for any stripped-down variant — never fatal (EL9, for
|
|
# one, has a curl/curl-minimal conflict we deliberately do not touch).
|
|
command -v bash >/dev/null 2>&1 || {
|
|
{ command -v apt-get >/dev/null 2>&1 && apt-get update -qq && apt-get install -y -qq bash; } ||
|
|
{ command -v dnf >/dev/null 2>&1 && dnf install -y -q bash; } ||
|
|
{ command -v zypper >/dev/null 2>&1 && zypper --non-interactive install bash; } || true
|
|
}
|
|
echo "bash $(bash --version | head -1)"
|
|
for f in serverkit install.sh uninstall.sh scripts/*.sh scripts/lib/*.sh scripts/test/*.sh; do
|
|
[ -f "$f" ] || continue
|
|
bash -n "$f" && echo "ok $f"
|
|
done
|
|
bash scripts/test/test_update.sh
|
|
bash scripts/test/test_install.sh
|
|
bash scripts/test/test_lib.sh
|
|
bash scripts/test/test_cli.sh
|
|
bash scripts/test/test_agent_install.sh
|
|
bash scripts/test/test_stage.sh
|
|
'
|
|
|
|
update-e2e:
|
|
name: e2e real install + update
|
|
# The unit suites source the scripts and test functions in isolation — the
|
|
# top-level run block never executes there, and that run block is exactly
|
|
# where the 2026-07-02 update abort lived. This job is the missing gate: a
|
|
# REAL install (the default non-interactive, no-domain, plain-HTTP path)
|
|
# followed by a REAL run of THIS branch's updater, on the runner VM (full
|
|
# systemd + docker — the same shape as a user's box). --dry-run is
|
|
# deliberately NOT used: dry-run returns early from the very functions that
|
|
# have broken. This job mutates the runner (/opt/serverkit, systemd units,
|
|
# nginx), so it must stay independent — never fold it into the unit job.
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Install ServerKit (non-interactive, no domain, plain HTTP)
|
|
run: |
|
|
# `sudo env`, not `... | sudo bash`: sudo resets the environment, so
|
|
# the switches must ride on the env command. PATH is forwarded so the
|
|
# runner's preinstalled node/npm/docker toolchain stays visible to
|
|
# root. No PANEL_DOMAIN + non-tty stdin is the documented no-prompt
|
|
# default; SERVERKIT_SKIP_SSL keeps the run HTTP-only (no certbot
|
|
# attempt on a runner with no DNS). install.sh copes with whatever
|
|
# web-server state the runner image ships — it is built for real,
|
|
# already-populated boxes.
|
|
sudo env PATH="$PATH" SERVERKIT_SKIP_SSL=1 bash install.sh
|
|
|
|
- name: Panel healthy after install
|
|
run: |
|
|
# Same probe install.sh and the VM harness gate on.
|
|
for i in $(seq 1 30); do
|
|
curl -fsS http://127.0.0.1:5000/api/v1/system/health >/dev/null 2>&1 && {
|
|
echo "backend healthy after install (probe $i)"; exit 0; }
|
|
sleep 2
|
|
done
|
|
echo "backend never became healthy after install" >&2
|
|
exit 1
|
|
|
|
- name: Real update with THIS branch's updater (--force)
|
|
run: |
|
|
# SERVERKIT_NO_SELF_UPDATE=1 stops the bootstrap from re-execing into
|
|
# the updater published on the target ref — the object under test is
|
|
# scripts/update.sh from this checkout. --force skips the version
|
|
# gate so the full deploy → migrate → switch → health path always
|
|
# executes.
|
|
sudo env PATH="$PATH" SERVERKIT_NO_SELF_UPDATE=1 bash scripts/update.sh --force
|
|
|
|
- name: Panel healthy after update
|
|
run: |
|
|
for i in $(seq 1 45); do
|
|
curl -fsS http://127.0.0.1:5000/api/v1/system/health >/dev/null 2>&1 && {
|
|
echo "backend healthy after update (probe $i)"; exit 0; }
|
|
sleep 2
|
|
done
|
|
echo "backend never became healthy after update" >&2
|
|
exit 1
|
|
|
|
- name: Version gate skips a redundant re-run
|
|
run: |
|
|
# A second run without --force should hit the version gate and skip.
|
|
# The message check is advisory (the remote may have advanced between
|
|
# the two runs, making a second full update legitimate) — only the
|
|
# exit code gates.
|
|
out="$(sudo env PATH="$PATH" SERVERKIT_NO_SELF_UPDATE=1 bash scripts/update.sh)"
|
|
printf '%s\n' "$out"
|
|
printf '%s' "$out" | grep -qi "already up to date" \
|
|
|| echo "::warning::second run did not report 'Already up to date' (remote may have advanced)"
|
|
|
|
- name: Dump install/update state
|
|
if: failure()
|
|
run: |
|
|
set +e
|
|
echo '===== /var/log/serverkit (updater logs) ====='
|
|
sudo sh -c 'for f in /var/log/serverkit/*.log; do
|
|
[ -f "$f" ] || continue
|
|
echo "----- $f (last 300 lines) -----"
|
|
tail -n 300 "$f"
|
|
done'
|
|
echo '===== systemd ====='
|
|
sudo systemctl status serverkit --no-pager -l
|
|
sudo journalctl -u serverkit --no-pager -n 200
|
|
echo '===== nginx ====='
|
|
sudo systemctl status nginx --no-pager -l
|
|
sudo nginx -t
|
|
sudo tail -n 100 /var/log/nginx/error.log
|
|
echo '===== layout ====='
|
|
ls -la /opt/ /opt/serverkit/
|
|
true
|
|
|
|
shellcheck-advisory:
|
|
name: shellcheck full ruleset (advisory)
|
|
# Everything shellcheck can say about the two scripts users actually pipe
|
|
# to bash, at maximum sensitivity. Deliberately non-gating: --enable=all
|
|
# carries style opinions the codebase does not follow, so this exists to be
|
|
# read when touching install.sh / scripts/update.sh — not chased to green.
|
|
# The gating checks stay in the lint + unit job above.
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: ShellCheck --enable=all (advisory — never fails the build)
|
|
continue-on-error: true
|
|
run: |
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq shellcheck
|
|
shellcheck --enable=all install.sh scripts/update.sh
|