16 KiB
Deployment
Status and authorization
The release and fixed-receiver code from PR #59 is on main, but has not been
installed or exercised against the VPS. This PR's compatibility and Windows
bootstrap changes are still proposed. No release, backup, restore, or
production service operation has been performed by this change. Production
deployment remains blocked until the owner has verified host state, registry
access, protected configuration,
corpus/index readiness, RU/KY canaries, an external backup, and restore on a
separate target. See current state and the
owner evidence packet.
The existing codex account has no Docker socket access and receives no new
privilege in this change. The external receiver requires an owner-controlled
SSH entry that runs the fixed root command; no SSH key, authorized_keys
entry, sudo rule, or root alias is created here. If akyldash_vps logs in as
codex, it cannot run the root-only receiver and the client fails closed.
Do not grant general sudo or Docker access to make the flow work.
Release verification and image publication
.gitea/workflows/ci.yaml remains the required test and image-build workflow.
The release workflow runs after successful main CI, verifies the source run and
commit, and publishes digest-pinned images plus a small release descriptor.
The descriptor records both the release workflow run ID and the successful CI
source run ID. PR builds do not publish images. Production runtime secrets are
not supplied to CI.
Read-only Gitea check on 2026-11-03 for current main SHA
f19faf628e67015febac22d0ec93ce4782caf4bf: CI run #706 succeeded, release
run #708 failed at the explicit registry-credential-presence check because
REGISTRY_USERNAME and REGISTRY_PASSWORD are not configured, and handoff run
#709 was skipped. No secret values were read. Image publication therefore
remains blocked until the owner provisions the required registry secrets.
On the host, the fixed root receiver uses the installed gitea-api helper to
re-fetch and verify the exact successful CI and release runs, current main,
artifact name, checksum, manifest schema, builder pin, image policy, and commit
binding. Its binary DOWNLOAD extension accepts HTTPS and at most one
allowlisted HTTPS redirect, does not forward the token across origins, bounds
the response, and refuses to overwrite an existing file. The installer checks
the exact audited helper fixture hash before applying its patch. The helper
and source code are not installed on the VPS by this PR.
One-shot runtime binding and bootstrap compatibility
main includes a root-only, read-only status command and a fixed SSH dispatcher.
The dispatcher accepts only the literal status or deploy command in
SSH_ORIGINAL_COMMAND; it accepts no arguments and performs no shell
evaluation. Status verifies successful current-main provenance, artifact
metadata/ZIP, and host runtime compatibility before returning only a run ID,
commit, and descriptor digest in a prepared response. It fails closed on a
recovery journal, incomplete Gitea run listing, invalid release data, changed
host runtime configuration, or an already applied release. The root receiver
independently repeats release verification and the existing backup/ACK/cutover
guards.
The Windows --once mode only polls the fixed read-only status endpoint and
stores a protected local PREPARED tuple. It never sends a deploy request.
Automatic deployment uses a second task and a separate
release-auto-apply.json gate; both config files and both scheduler XML
templates default to disabled. The apply task acts only when its current
verified tuple exactly matches a tuple saved by a previous poll. An owner can
also use --apply-prepared, which requires an interactive exact-SHA
confirmation. Both deploy paths re-enter the existing root receiver and all
backup/ACK/cutover guards. Rollback remains a separate explicit owner-run
командой. Планировщик запускает задачи от интерактивного владельца с минимальными
правами, пропускает повторный запуск при активном экземпляре и не будит компьютер.
Этот PR не зарегистрировал и не включил ни одну задачу.
Сначала подготовьте bundle под обычной учётной записью из точной локальной
вершины main командой prepare_windows_release_bundle.py. Упаковщик читает
только Git objects указанного полного commit SHA, сверяет Git blob identity
исходников и создаёт небольшой ZIP с SHA-256; рабочее дерево он не читает и не
копирует. Elevated installer получает путь к ZIP, ожидаемый commit и SHA-256.
Он один раз открывает пакет с запретом записи/удаления, проверяет SHA-256 архива,
manifest и Git blob IDs в памяти, затем копирует только проверенные байты.
Дополнительно installer проверяет цепочку Git objects commit → tree → файлы.
На elevated этапе Git не запускается; .git/config, hooks и состояние checkout
не используются. Не изменяйте ZIP между подготовкой и установкой.
Упаковщик ставит всем ZIP entries тип regular file и mode 0100600; installer
проверяет тот же mode (0x8180) до чтения содержимого. Для проверки стыка
упаковщика и фактической функции mode-валидации на Windows используйте
powershell.exe -NoProfile -File infra/deploy/test_windows_release_bundle_mode.ps1 -PythonPath C:\Python313\python.exe;
git.exe должен уже разрешаться обычным PATH. Harness создаёт синтетический
Git репозиторий и временный ZIP, извлекает только эту функцию из PowerShell AST и
проверяет принятие штатного пакета и отказ после изменения mode. Он не запускает
installer, не требует elevation, не меняет ACL и не регистрирует задачи; политику
выполнения PowerShell менять для него не следует.
Проверенный bootstrap installer проверяет подпись PSF у
C:\Python313\python.exe и защищённые ACL, затем размещает файлы клиента в
SHA-версионном каталоге Program Files, не перезаписывая существующую версию.
Для новых файлов кода владельцем назначается Administrators. У кода владельцем
могут быть только SYSTEM, Administrators или TrustedInstaller; учётная запись
задачи допускается владельцем только для отдельного private state/config.
Проверка ACL файла и защищённого каталога запрещает недоверенным субъектам запись,
добавление, удаление, удаление дочерних объектов, смену прав или владельца. Для
родительских каталогов проверяются права, способные подменить защищённый путь;
разрешение создать посторонний соседний каталог подменой не считается.
Installer сохраняет существующий canonical-файл
C:\ProgramData\Akyldash\release-trigger.json, только если он выключен; второй
disabled auto-apply config создаётся только при отсутствии. Installer формирует
два disabled task XML для проверки владельцем, но не регистрирует и не включает
задачи. Launcher запускает Python с -B -E -s -S, оставляет в import path только
стандартную библиотеку и защищённый каталог клиента, затем проверяет происхождение
импортированных модулей. До импорта receiver он заменяет унаследованный command
lookup фиксированными каталогами Windows, OpenSSH и Git, очищает GIT_* и
проверяет, что ssh.exe, git.exe и powershell.exe разрешаются в заданные пути.
Для самих бинарников и их родительских каталогов проверяются reparse points,
владельцы и ACL по правилам выше. Тесты подмены Python imports и command paths
выполнены на Linux; installer и launcher на Windows не запускались. Оставляйте оба
config gate и обе задачи выключенными до фиксации владельцем обычных backup/restore
и production readiness gate. Не привязывайте Windows-хост к Gitea workflow или PR runner.
The root status/deploy helper remains the reviewed root-installed bootstrap
code; it never checks out or executes target release code. A newer app release
is eligible without manually advancing /opt/akyldash/current only when the
host's Compose file, Caddyfile, and installed release policy match the exact
bytes at the prepared commit in Gitea. File metadata is pinned to that commit;
the Git blob SHA and size are verified. Status checks before preparing the
tuple; the receiver repeats the check before creating a journal or stopping
services, and cutover checks again after the verified external backup and
immediately before switching image references. A Compose/Caddy mismatch
returns runtime_config_changed; a policy mismatch returns
release_policy_changed and requires a reviewed owner bootstrap update. No
host checkout or root helper is automatically changed. The release must still
be current main; CI/release provenance, digest, backup, canary, and rollback
checks remain required. Gitea's filtered workflow-run listing is bounded to
ten entries; if Gitea reports more entries or an inconsistent page, status
returns unavailable rather than guessing.
Automation boundary: deployment is not active
Until the owner separately activates both Windows tasks and the auto-apply
configuration after the backup/restore and production gates, the chain ends at
release publication. After that explicit one-time owner authorization, the
prepared-state poll and separate apply task can process later matching
application releases without advancing /opt/akyldash/current or approving
each commit individually. A release that changes protected runtime files still
requires a reviewed bootstrap update.
- Successful
mainCI triggers image builds and publishes digest references plus the release descriptor artifact. deploy-handoff.yamlverifies the source run and may read artifact metadata. Its job is explicitly status-only: NOT DEPLOYED. It does not download the descriptor ZIP, call the Windows client, open SSH, or invoke the root receiver. A green result means only that this status check completed.- The fixed receiver is physically started only by the separately enabled
owner-controlled auto-apply task or by an interactive owner command. The
--oncepreparation task stores a verified tuple and never sends deploy; the separate apply task requires the tuple to match a fresh status response. No repository workflow invokes either task. Both scheduler templates and both configuration gates remain disabled, and neither task has been installed or exercised.
Production CD is still not active: task/credential setup has not been verified,
and no VPS or live D: operation has been performed. This design avoids a
Windows Actions runner entirely; the owner-controlled local task is not exposed
to PR jobs. The SSH account/key must be restricted to the exact dispatcher
commands and verified host key. Do not use an unrestricted authorized_keys
entry or a persistent host-mode runner with D: and SSH access.
External backup and cutover
The fixed root release receiver accepts only a bounded protocol request over
stdin. It does not accept a remote shell command, artifact path, or image
reference from the caller. It rechecks the release and current main, takes a
shared lock, captures running image IDs, and stops the stack. It streams the
cold archive directly to the owner's Windows client at
D:\Dev\akyldash\.backups; the VPS does not hold a full archive, including
temporarily. The Windows client requires a real NTFS destination with private
ACLs and a precise local Git exclude rule. It publishes only after hash,
archive, and manifest verification, and acknowledges the exact live session
nonce and backup identity.
Only after the acknowledgement does the host pull approved digest references,
atomically replace both image refs, and start Compose with --no-build. It
checks running image IDs, /review, and non-empty Russian and Kyrgyz search
canaries. A stale main, source mismatch, invalid archive, image mismatch, or
failed health/canary check stops cutover and attempts recovery using the
pre-change references. A recovery journal blocks another operation if
automatic recovery cannot be verified.
Rollback uses the root-pinned prior external archive and creates a fresh external backup of current state before switching refs. It restores images only, not data. Old local VPS backup paths and legacy release states without a pinned external backup identity are rejected. See backup and restore and rollback.
The receiver records a durable pending recovery journal before stopping
services. A retry of the exact release already recorded in pinned state is
rejected before backup or downtime. Gitea source verification requires
status=completed and conclusion=success, and resolves current main from
the explicit production checkout. The Windows client checks the destination,
ACL, ignore rule, and full protocol archive capacity before opening SSH.
Owner bootstrap still required
After review and merge, the owner must decide whether to install the helper from a root-owned checkout and provision, without exposing values in logs:
- a read-only Gitea API token in
/etc/akyldash/gitea-api-token, root-owned mode0600; - the exact artifact redirect origin allowlist in
/etc/akyldash/gitea-download-origins, if the Gitea artifact endpoint redirects; - approved Russian and Kyrgyz search canaries in
/etc/akyldash/release-canaries.json, root-owned mode0600; - a root-only SSH path to
/usr/local/sbin/akyldash-release-deployand/usr/local/sbin/akyldash-release-rollbackusing the existing owner key and verified host key. - a dedicated SSH alias
akyldash_releasebacked by a key restricted to/usr/local/sbin/akyldash-release-dispatch; its forced command must preserve exactSSH_ORIGINAL_COMMANDmatching and grant no shell, forwarding, PTY, or arbitrary command access; - a protected installation of the Windows client under
C:\Program Files\Akyldash, with both config files and both Task Scheduler XML templates reviewed. Keep the preparation task/config separate from the auto-apply task/config; the auto-apply pair remains disabled until the owner explicitly accepts unattended production deployment.
The installer intentionally does not create credentials, SSH entries, canary values, registry permissions, or a new privilege rule. Existing files with unexpected owner, mode, or content cause installation to stop for owner review. Do not run the installer or receiver from CI. The owner's first use must be a separate-target restore check and a reviewed dry run; this PR does not authorize a production cutover.
The Windows client uses the existing OpenSSH host-key checking. Its configured alias must reach a root-controlled endpoint capable of invoking the fixed receiver; no new key or sudo permission is requested. Windows ACL and NTFS checks, SSH host configuration, artifact redirect origin, registry pull access, and every production health gate remain unverified until the owner checks them.