ci(installer): run trusted SSH E2E matrix
Некоторые проверки не удались
CI / test (push) Successful in 3m19s
Docker / Build and publish worker image (push) Successful in 17m20s
SSH Source-Install E2E / Alpine/Ubuntu/Arch source-install E2E (push) Failing after 2m48s

Этот коммит содержится в:
Gleb Tv
2026-08-13 04:42:44 +03:00
родитель 674a7d82bf
Коммит 714dda08e5
7 изменённых файлов: 343 добавлений и 9 удалений

Просмотреть файл

@@ -2,7 +2,8 @@
## Status
In progress. Work packages 1-4 are implemented:
In progress. Work packages 1-5 are implemented (work package 5 is the
Docker/OpenSSH E2E matrix wired into CI):
- `internal/installer/harness` builds and runs real OpenSSH containers
for Alpine, Ubuntu, and Arch, waits for real network readiness, captures
@@ -246,6 +247,91 @@ for activation and is supplied with `--token`/`--token-file` or
`--env-file` (prefer the file options; direct flags expose the value
through the process list).
## Work Package 5: Alpine/Ubuntu/Arch E2E In CI
Work package 5 runs the full Docker/OpenSSH E2E matrix on pushes to
`master` and on manual `workflow_dispatch` runs. It is a separate Gitea
Actions workflow (`.github/workflows/test-ssh.yml`) so the ordinary unit
CI run stays Docker-free; the job executes `make test-ssh` through
`scripts/ci/test-ssh.sh`.
CI behavior:
- **Trust boundary and triggers.** The workflow runs *only* on pushes to
`master` (the default branch) and on manual `workflow_dispatch`. It
deliberately does **not** trigger on `pull_request`: the distro fixtures
execute the checked-out repository code inside privileged Docker
containers, so an untrusted PR must never reach the runner's Docker
surface automatically. Gitea's read-only token clamp for fork PRs does
not limit what containers can do on the runner host, so PR coverage is
left to the Docker-free unit CI (`ci.yml`) and to manual dispatch after
a human reviews the change.
- **Concurrency is scoped per ref.** `concurrency.group:
test-ssh-${{ gitea.ref }}` gives master pushes their own group (a newer
master push cancels a superseded in-flight master run instead of
stacking) and gives a manual dispatch on another branch its own group so
it never cancels the master run. Gitea Actions evaluates the expression,
and `gitea.ref` is the same documented context the repo's `docker.yml`
already uses.
- **Least privilege.** The workflow declares `permissions: contents: read`
(supported by Gitea Actions as the `GITEA_TOKEN` scope for
code/releases), so the job's token can only read the repository; the
workflow never writes, pushes, or publishes.
- **Action revisions.** The two actions this workflow uses are pinned to
immutable full commit SHAs (not moving tags): `actions/checkout@v4` ->
`11d5960a326750d5838078e36cf38b85af677262` and `actions/setup-go@v5` ->
`40f1582b2485089dde7abd97c1529aa768e1baff` (verified 2026-08-13 against
the GitHub API that each tag points to a commit object). The repo-wide
convention still leaves `ci.yml` and `docker.yml` on moving tags
(`@v4`, `@v5`, `@v3`, `@v6`); that is an accepted, documented risk: a
tag move can change behavior without a workflow diff. New workflows
should pin SHAs like this one; migrating the existing workflows is a
separate change.
- **Runner requirement.** The harness dials fixture SSH ports published on
the Docker daemon's `127.0.0.1`, so the job must share the daemon's
loopback (a host-mode runner or a job container with host networking).
`scripts/ci/test-ssh.sh` probes this with a tiny `docker run -p
127.0.0.1::22` round trip *before* the matrix and fails fast with a
clear, actionable message (including diagnostics and fix options)
instead of after a 60m go-test timeout.
- **Bounding and timeout.** The workflow sets `timeout-minutes: 90` and
the existing `make test-ssh` go-test `-timeout 60m` stays in place, so
the whole job is hard-bounded even under slow networks or downloads.
- **Cleanup.** The harness already tears down every container, network,
per-instance fixture image tag, and temp dir on success and failure.
`scripts/ci/test-ssh.sh` additionally traps `EXIT` to remove any
leftover `rsmon-worker-test-*` resource (container, network, or the
per-instance `rsmon-worker-test/<fixture>-<suffix>:local` image tag), and
the workflow adds an `if: always()` step that does the same even when the
script itself is killed. All filters are anchored to the harness's own
prefix and image repository (`name=^rsmon-worker-test-`,
`reference=rsmon-worker-test/*`), so cleanup never touches a shared base
image (`reg.rsxx.ru/library/alpine:3`, `ubuntu:24.04`, `archlinux:latest`)
or an unrelated resource.
- **Mirrors and overrides.** The Alpine fixture already uses the
`reg.rsxx.ru/library/alpine:3` mirror. Ubuntu and Arch have no mirror yet
and default to Docker Hub. The workflow wires the
`RSMON_TEST_IMAGE_ALPINE`, `RSMON_TEST_IMAGE_UBUNTU`, and
`RSMON_TEST_IMAGE_ARCH` repository variables (empty by default) so an
operator can pin a mirror or a specific distro snapshot per fixture, and
the `RSMON_TEST_DOCKER_DNS` repository variable (comma-separated
nameservers, applied as `docker run --dns ...`) to pin a resolver for
flaky CI DNS. External network (go.dev toolchain download, the public
rocketgit.ru source clone, distro package repos) is fetched live by
design.
- **Artifacts and secrets.** The workflow uploads no artifacts: the go-test
log stays in the runner's job log and nothing private (test key,
`known_hosts`, env files) is retained on the runner or published. The
fixture containers receive only the fixed `e2e-activation-test-token`
and the bundled test-only key, never a real worker token.
- **Caching.** `actions/setup-go` caches the Go module/build cache used to
compile the harness test binary (public dependencies only). Fixture and
toolchain downloads are not cached because they run inside disposable
distro containers; reruns rebuild them cleanly.
Work package 5 does not change how the source installer behaves. It only
adds a CI surface for the existing acceptance tests.
## Docker OpenSSH Test Harness
Adapt the real-network pattern from `/data/_swap/sshkeymanager`: start an
@@ -400,7 +486,7 @@ RSMON_TEST_DOCKER=1 go test -v -count=1 -timeout 30m ./internal/installer/harnes
env, data dir, and init service definition installed atomically; process
and `/healthz` verified; activation/start/health failures restore the
prior install; reruns keep exactly one service).
- [ ] 5. Add Alpine, Ubuntu, and Arch network E2E tests to CI.
- [x] 5. Add Alpine, Ubuntu, and Arch network E2E tests to CI.
- [ ] 6. Add CentOS-family support.
- [ ] 7. Plan native Windows service and macOS launchd installers separately.
@@ -419,7 +505,7 @@ RSMON_TEST_DOCKER=1 go test -v -count=1 -timeout 30m ./internal/installer/harnes
the previous installation (build/checkout, activation, start, and health
failure reruns on the Alpine fixture all restore the prior binary, env,
and running service).
- [ ] CI uses approved registry mirrors and cleans every test container/network.
- [x] CI uses approved registry mirrors and cleans every test container/network.
## Verified Test Evidence (work packages 1-4)
@@ -483,3 +569,27 @@ Recorded 2026-08-12 from `make test-ssh` (Docker Engine 29.7.1):
tests above rather than against a real init. The stub tools simulate unit
state and command flow, not real systemd/OpenRC unit semantics; a real
init-system smoke test on a booted host remains a follow-up.
Work package 5 (CI) evidence, recorded 2026-08-13:
- `.github/workflows/test-ssh.yml` runs `scripts/ci/test-ssh.sh` on pushes
to `master` and on manual `workflow_dispatch` only (never on
`pull_request`, because the fixtures execute the checked-out code inside
privileged Docker). Concurrency is scoped per ref
(`test-ssh-${{ gitea.ref }}`), the job token is `contents: read`, and
the script's loopback port-publishing probe passes on a host Docker
daemon while its `EXIT` cleanup leaves zero leftover containers,
networks, or fixture image tags.
- A full local `make test-ssh` run (the exact command the CI job executes)
passes the complete harness suite: `TestHarnessFixtures` (alpine/ubuntu/
arch), host-key mismatch and stability, teardown, `TestSourceInstallFixtures`
(work package 3 staging on all three distros), `TestSourceInstallActivationFixtures`
(work package 4 activation on all three), activation failure rollback,
`--no-start`, and the dirty-checkout preservation test. Pinned
`RSMON_TEST_DOCKER_DNS=1.1.1.1` was used because the local network's
default resolvers intermittently time out on `rocketgit.ru`.
- `TestSourceInstallDirtyCheckoutPreservesStaging` and the rollback test's
build-failure step now dirty the tracked tree deterministically (appending
a marker line to `Makefile`) instead of `git checkout master~1 -- Makefile`,
which depended on the last two commits differing and silently stopped
dirtying the tree once a commit did not touch that file.