feat(installer): build worker source over SSH
Все проверки выполнены успешно
CI / test (push) Successful in 3m13s
Docker / Build and publish worker image (push) Successful in 10m35s

Этот коммит содержится в:
Gleb Tv
2026-08-13 00:07:43 +03:00
родитель 4651deb280
Коммит bd6070ee1f
18 изменённых файлов: 2194 добавлений и 96 удалений

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

@@ -2,6 +2,88 @@
## 2026-08-12
### Source-install hardening review
- Fail-closed remote scripts: checkout, branch resolution, and build steps now
run under `set -eu` (and package/record steps chain with `&&`), so a failed
checkout or fetch can never be masked by a stale `rev-parse` or subsequent
command. The checkout step additionally refuses (`git diff --quiet` /
`--cached --quiet`) before the destructive `checkout -B`, because `-B`
silently discards local changes and would otherwise never fail on a dirty
tree. A dirty-tree checkout failure surfaces as a `check out branch` error
before the build runs; the new `TestSourceInstallSSHCheckoutFailureNotMasked`
unit test and `TestSourceInstallDirtyCheckoutPreservesStaging` Docker test
prove the previous staging binary and commit record are preserved
byte-for-byte.
- Atomic toolchain replacement: the Go toolchain is downloaded, SHA-256
verified, extracted into a same-filesystem staging dir, verified to report
the target version, and only then swapped into `ToolchainDir` with the prior
toolchain moved to a sibling `.go-backup` that is restored on swap failure.
A failed download/verify/extract/swap never destroys the prior Go.
- Record-after-build pairing: `rsmon-worker.commit` is written only after a
successful build, so the record and the staged binary always correspond to
the same commit. The build verifies `<stage>.new --version` before an atomic
`mv -f` over the previous staging binary; `GOMODCACHE` is now set alongside
`GOCACHE` inside the build dir so reruns reuse both caches.
- Origin verification: an existing checkout's `remote.origin.url` must exactly
match the configured repository before anything is fetched or built.
- Repository hardening: only `https://` clone URLs without userinfo are
accepted (`ValidateRepoURL`, enforced before dialing and again when
planning).
- Explicit charset validation for Go version and architecture overrides
(`sshinstall.ValidGoVersion` / `ValidGoArch`) before any remote mutation.
- Bounded remote execution: each remote command is capped by
`--session-timeout` (default 30m) and captured stdout is size-bounded
alongside the existing stderr bound; deploy's streaming `runRemote` keeps its
historical no-timeout behavior.
- The source installer now defaults to the remote's default branch (the public
repo publishes `master`) instead of the plan's stale `main` default, while
`--branch` still pins an explicit branch that must exist remotely. The
README quickstart no longer shows the incorrect `--branch main`.
- CLI secret flags keep their compatibility, but docs now explicitly state that
file options (`-password-file`, etc.) keep secrets out of argv and shell
history while direct flags expose them through the process list.
- The harness accepts `RSMON_TEST_DOCKER_DNS` (comma-separated) to pin
`docker run --dns` for fixture containers, so internet-facing installs are
not at the mercy of a flaky local resolver.
### Remote source-install execution (work package 3)
- Added `installer.SourceInstall` (`internal/installer/sourceinstall.go`):
executes the source-install flow through the existing SSH transport,
reusing the `deploy` command's `SSHOptions` (keys, passphrases,
passwords, sudo passwords, known-hosts, pinned fingerprints) and its
privilege path. Extracted the shared `SSHOptions` struct and a
`sudoWrap` helper so deploy and source install cannot diverge.
- Steps implemented: minimal package-prerequisite install per distro
(`apk`/`apt`/`pacman`/`dnf`, never a compiler), SHA-256-verified Go 1.26
toolchain download/extraction with an idempotent version-skip and temp-dir
cleanup, clone-or-update of the public repository (with a bounded 3-attempt
retry for transient DNS/TLS/proxy failures), resolution of the remote
default branch (a pinned branch must exist remotely), a resolved branch and
commit record at `<BuildDir>/rsmon-worker.commit`, and a staging build
(`CGO_ENABLED=0`, `-trimpath`, repository `-ldflags`) verified via
`--version`. The running service, config, and data directory are untouched
(work package 4 boundary).
- Security: every interpolated remote value is single-quoted; branch and
commit values are strictly validated; no worker token or control-plane
credential is sent; sudo passwords travel only over session stdin; remote
errors are bounded (stderr truncated in `runRemoteOutput`).
- Added unit tests for the remote scripts, option validation, branch/commit
parsing, sudo wrapping, the secrets-absent contract, and an in-process
real-SSH orchestration flow (with missing-pinned-branch, build-failure, and
detection-failure paths).
- Added `TestSourceInstallFixtures` to the Docker/OpenSSH harness: each of
Alpine, Ubuntu, and Arch installs from a clean state through the real
harness transport (prerequisite install, verified Go 1.26, clone, resolved
commit, staging build), then a rerun proves idempotency (same branch,
toolchain reuse, no temp leaks). All three resolved the public repo's
`master` at `4651deb2...` in the recorded run. `make test-ssh` timeout
raised to 60m.
- Documented the branch-resolution reality: the public repository currently
publishes `master`, and the installer records whatever the remote default
branch resolves to.
### Source-install harness and planning foundations (work packages 1-2)
- Added `internal/installer/harness`: a reusable Docker/OpenSSH test harness