Compare commits

...

6 commits

Author SHA1 Message Date
ochenstarik-ui
930e6ee5e7 docs(agents): record stage1 evidence and independent rereviews
Some checks failed
CI / build-and-test (pull_request) Failing after 5s
2026-10-19 13:18:19 +07:00
ochenstarik-ui
c5ede46bd1 docs(deploy): verify graceful proxy reload and staged activation 2026-10-19 13:16:01 +07:00
ochenstarik-ui
ca28be6ae6 Adapt VPS runbook to existing containerized Caddy and HAProxy 2026-10-19 13:06:30 +07:00
ochenstarik-ui
8284eb5d00 Merge remote-tracking branch 'forgejo/main' into codex/stage1-completion 2026-10-19 12:53:53 +07:00
52fb7587d7 Merge pull request 'docs(agents): R5 — VPS uses containerized Caddy and HAProxy' (#3) from claude/r5-vps-containers into main
Some checks are pending
CI / build-and-test (push) Waiting to run
2026-10-19 05:50:44 +00:00
ochenstarik-ui
6c36e0a866 docs(agents): R5 — VPS runs Caddy and HAProxy in containers
Some checks failed
CI / build-and-test (pull_request) Failing after 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:50:36 +07:00
29 changed files with 1115 additions and 122 deletions

View file

@ -0,0 +1,48 @@
# Auditor1 R5 re-review
## Scope and revision
- Base SHA: `ca28be6ae629d3d67936475ea5896b6f70f6e1e4`
- Reviewed SHA: `c5ede46bd1c31b1e23434538fdb8a1cd925751a6`
- Scope: re-review of prior auditor1 findings in the HAProxy no-restart reload procedure and active/source backup integrity, plus staged HTTP-01 → Caddy auth → TLS route ordering.
- Resulting code SHA: no code changes by reviewer.
- Files changed by this review: unique assignment and report only; existing `AGENTS.md` modification preserved.
## Prior findings
### CLOSED — MEDIUM: HAProxy no-restart reload path was unspecified
The runbook now has a concrete, gated master-worker path in `docs/STAGE1_DEPLOYMENT_RUNBOOK.md:414-477`. It first requires PID 1 to be `haproxy`, an actual `-W`/`-Ws` argument, and manual confirmation that every live `-f` path matches the validated config set (`:416-425`). The function checks PID 1 and the master-worker argument again immediately before signaling (`:435-440`), sends USR2 specifically to the confirmed PID 1 (`:441-442`), waits for a new HAProxy child worker PID (`:443-451`), and requires unchanged container ID, start time, restart count, and running state (`:452-455`). Unknown process models stop without sending a signal (`:416-420`). Staged activation and failure/rollback instructions are now explicit (`:459-477`, `:518-525`).
Evidence: source inspection at those lines. The coordinator reports having tested the exact function against an official HAProxy 3.0 fixture: worker handoff occurred with ID/StartedAt/RestartCount unchanged, and non-master PID 1 was rejected before signal. That fixture result was not independently rerun in this review. The live VPS process model remains UNVERIFIED, so this closes the runbook gap only; operation on the VPS remains conditional on its documented preflight.
### CLOSED — LOW: directory-tree backups lacked completion/integrity checks
The directory-mount branch now checks that all four active/source backup trees are nonempty, compares each source tree to its saved copy, and compares each active tree copy against its source copy before edits (`docs/STAGE1_DEPLOYMENT_RUNBOOK.md:303-326`). The single-file path also compares each host-source backup to its source (`:289-300`). The root shell is instructed to use `set -euo pipefail` (`:148-155`), so a failed copy command aborts before the later steps; explicit mismatch checks stop with a non-content error.
Evidence: source inspection at those lines. The coordinator reports directory backups and diff checks reviewed; no VPS backup commands were executed in this review.
## Remaining finding
### LOW — When adding a new HTTP-01-only route, the final config never routes ordinary HTTP requests for the hostname to Caddy
The new conditional route in `deploy/randomayzer.haproxy.cfg:18-27` matches both the requested host and only `/.well-known/acme-challenge/`. The runbook says to add this challenge-only route when the hostname is not already sent to Caddy, then to add Caddy auth and later the TLS SNI route (`docs/STAGE1_DEPLOYMENT_RUNBOOK.md:375-391`, `:459-477`). It never adds or enables a general HTTP host route after the Caddy site and TLS route are ready. In that topology, ordinary `http://random.ochenstarik.ru/` traffic continues to HAProxy's previous catch-all and cannot receive Caddy's automatic HTTP-to-HTTPS redirect. This is conditional on the HTTP host not already being routed to Caddy; the runbook's local/domain route baselines should include an explicit HTTP check for Randomayzer after activation.
Evidence: the official Caddy Automatic HTTPS documentation says it creates HTTP-to-HTTPS redirects on port 80, while the HAProxy example routes only the challenge path to Caddy: [Caddy Automatic HTTPS](https://caddyserver.com/docs/automatic-https) and the cited source lines. This was not tested against the live topology. A post-TLS stage can route the full host to Caddy after the authenticated site is loaded, preserving the challenge path and other hosts; if broad HTTP forwarding is not intended, document the deliberate behavior instead.
## Staged ordering assessment
The runbook now orders a needed HTTP-01 challenge-only route first, Caddy's Basic Auth site second, and the new TLS SNI route third (`docs/STAGE1_DEPLOYMENT_RUNBOOK.md:375-391`). The HTTP-01 route is limited to the challenge prefix and preserves port 80, while the TLS SNI route remains absent until after Caddy has loaded the protected site. The ordering is sound for avoiding a new public TLS route before the auth config is active. The remaining LOW finding above concerns ordinary HTTP requests after activation, not the ordering of challenge/auth/TLS stages.
## Commands and evidence
- `git rev-parse HEAD` — `c5ede46bd1c31b1e23434538fdb8a1cd925751a6`.
- `git diff --check ca28be6ae629d3d67936475ea5896b6f70f6e1e4 c5ede46bd1c31b1e23434538fdb8a1cd925751a6` — exit 0.
- Source diff inspected with `git diff --unified=5 ca28be6ae629d3d67936475ea5896b6f70f6e1e4 c5ede46bd1c31b1e23434538fdb8a1cd925751a6 -- deploy/randomayzer.haproxy.cfg docs/STAGE1_DEPLOYMENT_RUNBOOK.md`.
- No Docker, container, VPS, application test, or full-gate commands were run. Coordinator-reported checks: official HAProxy 3.0 fixture behavior and config validation passed; 25 Bash blocks passed syntax checks. These are not claimed as independently run.
## UNVERIFIED and blockers
- VPS PID 1, actual HAProxy arguments and complete `-f` config set, mounts, routing rules, Caddy admin endpoint, and HTTP path to Caddy remain UNVERIFIED. The new reload command must not run unless the live preflight proves its preconditions.
- DNS, public certificate issuance, external HTTP/HTTPS behavior, login, WireGuard peer identity, firewall isolation, and preservation of sites on the actual VPS remain UNVERIFIED.
- No R5 phase closure or deployment readiness is claimed.

View file

@ -0,0 +1,51 @@
# Auditor1 R5 runbook review
## Scope and revision
- Base SHA: `8284eb5d006943d42133eb665c99dcbc3133d6a6`
- Reviewed SHA: `ca28be6ae629d3d67936475ea5896b6f70f6e1e4`
- Review scope: immutable R5 delta, especially `deploy/randomayzer.haproxy.cfg`, `deploy/randomayzer.Caddyfile`, `docs/STAGE1_DEPLOYMENT_RUNBOOK.md`, and `agents/codex/notes/validate-r5-templates.sh`.
- Resulting code SHA: not applicable; reviewer made no production or test changes.
- Changed files: this report and its assignment only; pre-existing `AGENTS.md` modification left untouched.
## Findings
### MEDIUM — HAProxy no-restart reload procedure is not executable from this runbook
`docs/STAGE1_DEPLOYMENT_RUNBOOK.md:396-403` deliberately leaves out the HAProxy reload command, and requires the operator to independently discover and prove a safe native reload method. The rollback at `:449-453` likewise requires “both confirmed reload commands.” The task's acceptance explicitly requires reloading without restarting the shared proxy. Until the operator has separately established the process model and worked out the command, this runbook cannot perform or roll back that accepted operation; it correctly blocks rather than guessing, but does not yet supply an executable conditional path.
Reproducer/evidence: inspect the runbook at those lines; there is no HAProxy command or procedure to establish the target process and verify graceful worker handoff. `git diff --check 8284eb5d006943d42133eb665c99dcbc3133d6a6 ca28be6ae629d3d67936475ea5896b6f70f6e1e4` exited 0, but does not address this operational gap. No live VPS process model was inspected.
### LOW — Directory-tree backup copies have no explicit completion/integrity check
For directory mounts, `docs/STAGE1_DEPLOYMENT_RUNBOOK.md:289-304` copies both active trees and both host source trees, but unlike the single-file path at `:284-286`, it has no explicit check that all four copies completed or that the saved trees match their source. The later hash guidance at `:306-307` is general and does not provide a tree-backup verification command. A partial `docker cp`/`cp -a` can therefore leave an incomplete private backup before edits proceed, reducing rollback confidence.
Reproducer/evidence: follow the directory-mount backup block; after its four copy commands it only runs `chmod`. No `diff`, manifest/hash, or copy-result gate verifies the backup trees. `bash -n agents/codex/notes/validate-r5-templates.sh` exited 0; the validator is a syntax/config fixture and does not exercise VPS backup commands.
## Reviewed controls that appear correctly represented
- The runbook gates work on confirmed domain/VPS/WireGuard facts and says to stop for unknown topology (`docs/STAGE1_DEPLOYMENT_RUNBOOK.md` R5 preflight and sections 6.1-6.2).
- It requires root context for `/root` backups, avoids printing `.Config.Env`, backs up both active configs and host sources for file mounts, restricts backup permissions, compares active/source content, and preserves single-file bind-mount inode during edits (`:260-332`). The separate directory-tree integrity gap is above.
- HAProxy SNI routing is conditional on confirmed TCP TLS passthrough; the new explicit hostname rule is to follow existing explicit routes and precede catch-all. Port 80 is conditional on existing HTTP routing and must preserve the ACME challenge path unmodified and target Caddy's cleartext HTTP backend (`:352-361`).
- Caddy and HAProxy are validated inside their running containers before and after edits (`:310-323`, `:363-381`). Caddy reload is concrete and gated on its active admin endpoint; container ID/uptime checks and prior-site baseline checks are specified (`:383-415`).
- Rollback limits restoration to changed files, preserves mounted-file inode, validates both configs, and explicitly prohibits restarting/recreating the shared proxy (`:440-456`).
- The local fixture script is syntactically valid and scoped to Docker network none. It was not executed in this review, per coordinator instruction to avoid duplicating the validator run.
## Commands and results
- `git rev-parse HEAD` — `ca28be6ae629d3d67936475ea5896b6f70f6e1e4`.
- `git diff --check 8284eb5d006943d42133eb665c99dcbc3133d6a6 ca28be6ae629d3d67936475ea5896b6f70f6e1e4` — exit 0.
- `bash -n agents/codex/notes/validate-r5-templates.sh` — exit 0.
- No application tests/build, Docker template validator, Docker commands, or VPS commands were run by this reviewer. Root owns the full gates and validator.
## UNVERIFIED and blockers
- Actual VPS container arguments, mounts, active config paths, HAProxy routing order, HAProxy process model/reload mechanism, Caddy admin endpoint, and source/active inode relationship were not observable from this local review.
- Public DNS, live TLS/ACME issuance, external auth/login, WireGuard peer identity, firewall isolation, and preservation of existing public sites remain UNVERIFIED.
- Parent reported a local fixture experiment with a `haproxy -W -db` master-worker process and USR2 signal; that is not evidence of the actual VPS process model and was not used to close the finding.
- No project phase or deployment is declared closed.
## Remaining non-blocking debt
- Add a conditional HAProxy reload branch only when the active PID 1/process model and exact config set are inspected, then specify observable graceful-handoff and unchanged-container checks plus failure/rollback handling.
- Add explicit verification for all active/source directory-tree backup copies before permitting edits.

View file

@ -0,0 +1,62 @@
# Independent rereview — cce70c0
Base SHA: `17faecd962c2cf40e0a3f6ec4434811cfffdd312`
Reviewed SHA: `cce70c0b8feb263cc9209332c8c50a44bc97428a`
Result SHA: `cce70c0b8feb263cc9209332c8c50a44bc97428a` (review only; no source changes)
## Prior findings and status
### auditor1 MEDIUM — live smoke could accept mock-provider data: CLOSED at this SHA
The previous reproduction set `USE_VK_MOCK=true` in the production environment while a real service token existed, then omitted the optional restricted-post case. This no longer yields live-smoke evidence:
- [`ProviderFactory`](/srv/projects/Agent%20projects/randomayzer/src/providers/factory.ts:10) now throws in `NODE_ENV=production` when the mock flag is true, before returning the mock provider. The production Compose service also sets `USE_VK_MOCK: "false"` explicitly at [compose.production.yml](/srv/projects/Agent%20projects/randomayzer/compose.production.yml:33), overriding the env-file value.
- The preview and import responses report `providerMode` from the actual `VkProvider` instance at [preview route](/srv/projects/Agent%20projects/randomayzer/src/app/api/posts/preview/route.ts:32) and [participants route](/srv/projects/Agent%20projects/randomayzer/src/app/api/giveaways/%5Bid%5D/participants/route.ts:99). The token is not included in that field or response.
- The smoke script requires `success` and `providerMode === LIVE` for preview; G/H additionally require a safe integer `totalCount > 0` at [vk-smoke.sh](/srv/projects/Agent%20projects/randomayzer/tools/vk-smoke.sh:94). Thus MOCK and empty imports are failures.
- The regression suite passed: production mock refusal even with a service token; mock instance reports MOCK despite SERVICE auth metadata; real provider reports LIVE without exposing a marker token; and actual shell smoke subprocess fixtures reject MOCK and zero-count imports while accepting LIVE nonempty imports. The tests also assert the cookie marker is absent from output.
Closure is limited to the false-positive code path and regression evidence. No live VK run was performed.
### auditor2 HIGH — malformed likes/comments could be silently omitted or collapsed: CLOSED at this SHA
The `VkProvider` validates both payload envelopes and each returned participant record before returning any list. Likes require complete count/items, object items, positive safe-integer IDs, valid optional profile field types, and unique user IDs across pages. Comments require complete count/items, valid profile lists and IDs, and nonzero safe-integer author IDs; negative community author IDs are explicitly excluded as nonparticipants. Malformed responses throw `VkIncompleteDataError`.
The import route does not call `GiveawayStore.updateParticipants` until provider fetch, enrichment, and filtering all complete. The focused route regression loops through 25 malformed likes/comments payloads and checks HTTP 502, `VK_INCOMPLETE_DATA`, and preservation of the prior participant list for each. A separate test confirms valid negative community comment authors are ignored. Focused suites passed 13 R3 tests including these cases.
Closure is limited to malformed-item validation and no-partial-save evidence. External VK response schemas were not verified against current official documentation or a live response.
## Remaining LOW finding
### LOW — COMMUNITY metadata still advertises admin detection without a manager-list implementation: OPEN / unchanged
[`resolveEffectiveCapabilities`](/srv/projects/Agent%20projects/randomayzer/src/providers/vk/vk-capabilities.ts:34) still sets `adminDetection: true` for COMMUNITY auth and removes the explanatory note. This commit does not change that code. The prior auditor noted that the participant import route validates against the static capability object, which keeps the current server-side exclusion guard false; the inaccurate dynamic metadata remains a preview/API contract issue and should stay tracked separately.
## New findings
CRITICAL: none.
HIGH: none in the rereviewed fixes.
MEDIUM: none in the rereviewed fixes.
LOW: the unchanged COMMUNITY capability-metadata finding above.
## Commands actually run
- Initial `npx vitest run tests/vk-smoke-provider-mode.test.ts tests/vk-r3-safety.test.ts` overlapped the coordinator's `npm ci`; Vitest failed to load PostCSS because `dlv` was temporarily unavailable. A second attempt during the same install window reported `vitest: not found`. Per coordinator instruction, these transient install-state errors were not treated as source findings.
- After the coordinator confirmed `npm ci`/Prisma generation had completed: `npx vitest run tests/vk-smoke-provider-mode.test.ts tests/vk-r3-safety.test.ts` — exit 0; 2 files, 19 tests passed (13 R3 safety, 6 provider-mode/smoke tests).
- `bash -n tools/vk-smoke.sh` — exit 0.
- `node --check tools/production-transport.cjs` — exit 0.
- `node --check tools/serve-production.cjs` — exit 0.
- `git diff --check 17faecd962c2cf40e0a3f6ec4434811cfffdd312..cce70c0b8feb263cc9209332c8c50a44bc97428a` — exit 0.
The coordinator separately reported `npm ci`, Prisma generation, and full `npm test` exit 0 with 380/380 tests at this SHA; I did not run those commands. Lint/build/audit, real PostgreSQL, and Docker remain coordinator-owned.
## UNVERIFIED and blockers
- Actual live VK behavior, consent, token scopes, and current official payload contracts remain UNVERIFIED.
- R4 production Docker startup and backup/restore drill remain unverified by this rereview.
- R5 DNS, TLS/basic-auth, live domain login, Docker-preserved peer address, firewall persistence, and internet/LAN/WireGuard isolation remain operator acceptance blockers.
- No phase closure or release readiness is claimed.
Files changed by this rereview: `agents/codex/inbox/2026-10-19-auditor1-rereview-cce70c0.md`, `agents/codex/done/2026-10-19-auditor1-rereview-cce70c0.md`, and individual memory worklog `/srv/projects/AI-Memory/01_PROJECTS/randomayzer/worklog/2026-10-19_auditor1_cce70c0-rereview.md`. No production code or tests changed; no commit/push was made. Pre-existing `AGENTS.md` modification was preserved.

View file

@ -0,0 +1,61 @@
# Auditor2 independent re-review — malformed VK data and live smoke
Reviewed SHA: `cce70c0b8feb263cc9209332c8c50a44bc97428a`
Comparison SHA: `17faecd962c2cf40e0a3f6ec4434811cfffdd312`
Base SHA: `6382cb8e0473ba9ea5ba7a3f94f34e8ab0d4ec60`
Result SHA: `cce70c0b8feb263cc9209332c8c50a44bc97428a` (review only; no code changes).
## Conclusion
The previous **HIGH** finding for malformed VK likes/comments participant data is **CLOSED** at the reviewed SHA. The implementation now rejects malformed records before the import route persists any result, and focused route regressions confirm that prior stored participants survive all included malformed cases.
The follow-up live-smoke false-success issue is also addressed: smoke checks require the actual provider marker `LIVE`; G/H additionally require a positive safe-integer participant count. Production refuses `USE_VK_MOCK=true` and the production Compose profile explicitly sets it false. Focused adversarial fixtures cover mock provider, empty live imports, and nonempty live imports.
The COMMUNITY admin capability discrepancy remains a **pre-existing, nonblocking debt**, not a finding introduced by this stage. I independently confirm that classification for current server routes: preview passes `fetchPost` only `organizerId` (no explicit COMMUNITY auth), and the resolver's automatic/service/user selection returns only SERVICE or USER. Giveaway detail derives only SERVICE/USER. Participant/snapshot enforcement reads the provider's static capability, which remains `adminDetection: false`.
No CRITICAL, HIGH, or MEDIUM findings remain from these reviewed issues. This report does not declare the phase closed.
## Re-reviewed issues and evidence
### Prior HIGH — CLOSED: malformed likes/comments responses
At [vk-provider.ts](/srv/projects/Agent%20projects/randomayzer/src/providers/vk/vk-provider.ts:34), profile records now require a plain object with a positive safe-integer user ID and correctly typed returned fields. [Response validation](/srv/projects/Agent%20projects/randomayzer/src/providers/vk/vk-provider.ts:49) requires a response object, an items array, a nonnegative safe-integer count, and object-shaped items. Likes also reject duplicate user IDs at lines 297–303. Comments reject malformed/zero/non-integer/unsafe author IDs at lines 355–359 and validate returned profile arrays and IDs.
The earlier concrete failures are blocked: a missing `id` cannot become the participant ID `"undefined"`; a string or unsafe ID is rejected; and malformed comment author IDs are no longer silently omitted while the reported count is accepted. Negative integer comment author IDs remain deliberately excluded as community authors, matching prior behavior and now covered by a focused test.
At [participants route](/srv/projects/Agent%20projects/randomayzer/src/app/api/giveaways/%5Bid%5D/participants/route.ts:86), fetch/enrichment must finish before `updateParticipants` at line 97. The route-level regression at [vk-r3-safety.test.ts](/srv/projects/Agent%20projects/randomayzer/tests/vk-r3-safety.test.ts:200) iterates 25 malformed likes/comments cases, expects `502 VK_INCOMPLETE_DATA`, and checks the previous participant list remains unchanged after each case.
### Smoke false-success — CLOSED
[ProviderFactory](/srv/projects/Agent%20projects/randomayzer/src/providers/factory.ts:10) throws in production if mock mode is explicitly enabled, even when a service token exists. Production Compose sets `USE_VK_MOCK: "false"` at [compose.production.yml](/srv/projects/Agent%20projects/randomayzer/compose.production.yml:36).
Preview and participant-import responses identify the actual provider instance as `LIVE` or `MOCK`. [vk-smoke.sh](/srv/projects/Agent%20projects/randomayzer/tools/vk-smoke.sh:94) requires `providerMode === "LIVE"`; G/H also require `totalCount` to be a safe integer greater than zero. Its test fixture proves MOCK with reported participants fails before creating/importing a giveaway, LIVE with zero participants fails G/H, and LIVE with nonempty participants passes D/G/H: [vk-smoke-provider-mode.test.ts](/srv/projects/Agent%20projects/randomayzer/tests/vk-smoke-provider-mode.test.ts:66).
### Pre-existing LOW metadata discrepancy — nonblocking, not stage1-introduced
`resolveEffectiveCapabilities` still sets `adminDetection: true` for COMMUNITY auth at [vk-capabilities.ts](/srv/projects/Agent%20projects/randomayzer/src/providers/vk/vk-capabilities.ts:34), though no manager-list lookup exists. The exact behavior is present in base SHA `6382cb8e0473ba9ea5ba7a3f94f34e8ab0d4ec60`. Current routes do not supply or resolve COMMUNITY auth: preview calls `fetchPost(url, { organizerId })`; its automatic resolver chooses SERVICE or USER; giveaway details likewise derive only SERVICE/USER. Import and snapshot checks use `provider.capabilities`, whose static `adminDetection` remains false, as enforced by [giveaway-schemas.ts](/srv/projects/Agent%20projects/randomayzer/src/core/validation/giveaway-schemas.ts:89). Thus this metadata branch is unreachable through current routes and cannot enable admin exclusion. I agree it is pre-existing nonblocking debt for this phase, not an open stage1 finding.
## Commands run by this review
- `git show -s --format='%H%n%P%n%s' cce70c0b8feb263cc9209332c8c50a44bc97428a` — confirmed fixed commit parent is `17faecd962c2cf40e0a3f6ec4434811cfffdd312`.
- `git diff 17faecd962c2cf40e0a3f6ec4434811cfffdd312 cce70c0b8feb263cc9209332c8c50a44bc97428a -- <reviewed files>` — reviewed only the immutable commit delta.
- `USE_VK_MOCK=true npx vitest run tests/vk-r3-safety.test.ts tests/vk-smoke-provider-mode.test.ts` — exit 0; 2 files, 19 tests passed (13 R3 safety, 6 smoke provider-mode).
- `bash -n tools/vk-smoke.sh` — exit 0.
- `git diff --check 17faecd962c2cf40e0a3f6ec4434811cfffdd312 cce70c0b8feb263cc9209332c8c50a44bc97428a` — exit 0; no whitespace errors.
Coordinator separately reported raw `npm test` at this SHA passed 380/380 across 63 files; I did not independently rerun the full suite, install, Prisma generation, lint, build, audit, or Docker gates. Live VK and target-host acceptance remain operator-dependent and unverified.
## UNVERIFIED / remaining blockers
- Actual live VK payload contracts and stable offset-pagination behavior remain **UNVERIFIED**; this review does not assert external VK method contracts. The existing documentation discloses that changing VK data during offset pagination cannot be proven point-in-time consistent.
- Live VK consent, real credentials, and production A–H smoke remain unverified.
- Actual VPS/WireGuard socket peer preservation, firewall persistence, TLS/basic-auth, and LAN/internet isolation require operator checks on target hosts.
- Coordinator's full lint/build/audit/Docker acceptance is outside this rereview; no phase-closure claim is made.
## Files changed by this rereview session
- `agents/codex/inbox/2026-10-19-auditor2-high-rereview.md` — task record.
- `agents/codex/done/2026-10-19-auditor2-high-rereview.md` — this report.
- `/srv/projects/AI-Memory/01_PROJECTS/randomayzer/worklog/2026-10-19_auditor2_high-rereview.md` — individual memory worklog.
No production source/tests were changed, and no commit, push, container mutation, or communication with auditor1 was made.

View file

@ -0,0 +1,44 @@
# Result: independent R5 follow-up review
Base SHA: `ca28be6ae629d3d67936475ea5896b6f70f6e1e4`
Reviewed SHA: `c5ede46bd1c31b1e23434538fdb8a1cd925751a6`
Result SHA: `c5ede46bd1c31b1e23434538fdb8a1cd925751a6` (review only; no source changes)
Files changed by this reviewer: this report and its inbox task record only.
## Review result
The prior **MEDIUM is CLOSED for the runbook/documentation deliverable**. The prior **LOW is CLOSED**. I found no new CRITICAL, HIGH, or MEDIUM finding in the reviewed delta.
The prior MEDIUM was that the runbook had no executable HAProxy no-restart reload procedure. At `docs/STAGE1_DEPLOYMENT_RUNBOOK.md:414-457`, the revised procedure first requires PID 1 to be `haproxy`, an exact `-W`/`-Ws` argument, and manual confirmation that its `-f` paths match the complete validated config set (`:416-425`). It stops without signalling if those checks fail. The reload function repeats the PID/mode check, snapshots Docker ID/start time/restart count, requires existing worker PIDs, sends USR2 specifically to the confirmed container master, waits for a new worker PID, and verifies the container remains running with unchanged identity/start/restart count (`:435-455`). The operator invokes it only after config/source gates (`:459-464`). This is a concrete, conditionally safe procedure, not an instruction to guess at or alter the deployed process model.
That signal choice agrees with the [official HAProxy 3.0 Management Guide](https://docs.haproxy.org/3.0/management.html), which documents `-W` as master-worker mode and SIGUSR2 to the master as a reload mechanism. The coordinator reports it separately exercised the exact function against an official HAProxy 3.0 image: worker handoff occurred with Docker ID, StartedAt, and RestartCount unchanged, while a non-master PID 1 was rejected before a signal. I did not rerun that container test.
The prior LOW was the ordering that could route TLS requests to Caddy before Caddy loaded its site configuration. It is closed by the explicit activation order at `docs/STAGE1_DEPLOYMENT_RUNBOOK.md:374-392`: when needed, first add and reload only the HTTP-01 challenge route; next validate and reload Caddy with Basic Auth; only after that succeeds add and reload the TLS SNI route. The HTTP route is constrained to the exact Host and `/.well-known/acme-challenge/` prefix in `deploy/randomayzer.haproxy.cfg` (lines 18-26), so this staging does not expose the new TLS route prematurely.
The follow-up also adds explicit source/active config-tree backup comparisons at `docs/STAGE1_DEPLOYMENT_RUNBOOK.md:318-325`, complementing the single-file and inode checks. Rollback now restores and reloads Caddy before using the same gated HAProxy handoff procedure (`:509-525`).
## Commands actually run
- `git rev-parse HEAD` — `c5ede46bd1c31b1e23434538fdb8a1cd925751a6`.
- `git status --short` — showed pre-existing `AGENTS.md` modification and shared task artifacts; no source changes by this reviewer.
- `git diff --stat ca28be6ae629d3d67936475ea5896b6f70f6e1e4 c5ede46bd1c31b1e23434538fdb8a1cd925751a6` — scoped follow-up change summary.
- `git diff --no-ext-diff --unified=15 ca28be6ae629d3d67936475ea5896b6f70f6e1e4 c5ede46bd1c31b1e23434538fdb8a1cd925751a6 -- docs/STAGE1_DEPLOYMENT_RUNBOOK.md deploy/randomayzer.haproxy.cfg` — inspected the review delta.
- `git show c5ede46bd1c31b1e23434538fdb8a1cd925751a6:docs/STAGE1_DEPLOYMENT_RUNBOOK.md | nl -ba | sed -n '140,550p'` — inspected final line references.
- `git show c5ede46bd1c31b1e23434538fdb8a1cd925751a6:docs/STAGE1_DEPLOYMENT_RUNBOOK.md | nl -ba | sed -n '275,325p;374,480p;509,528p'` — verified cited reload, ordering, tree-compare and rollback locations.
- `git diff --check ca28be6ae629d3d67936475ea5896b6f70f6e1e4 c5ede46bd1c31b1e23434538fdb8a1cd925751a6` — exit 0.
- Read the official HAProxy 3.0 Management Guide at `https://docs.haproxy.org/3.0/management.html` for master-worker/SIGUSR2 semantics.
Coordinator reports Caddy/HAProxy config validation and syntax checks for 25 Bash blocks passed. Those checks were not rerun by this reviewer. No full application gates, Docker test, live VPS command, or container mutation was performed by me.
## UNVERIFIED and remaining blockers
- Actual VPS PID 1, HAProxy argv/config paths, mounts, active/source equality, master-worker mode, and successful live handoff remain UNVERIFIED. The runbook explicitly stops before editing/signalling unless those preconditions are established on the VPS.
- Actual Caddy admin endpoint, routing topology, HTTP-01 issuance/renewal, DNS/WireGuard/firewall preconditions, and existing-site baselines remain UNVERIFIED. No live acceptance or phase closure is claimed.
- No remaining code-review blocker in the two prior findings; live operation remains gated on the documented operator preconditions and acceptance checks.
## Severity status
- CRITICAL: none.
- HIGH: none.
- MEDIUM: prior no-command finding CLOSED for the runbook; deploy-time process model remains UNVERIFIED.
- LOW: prior activation-order window CLOSED by the staged HTTP-01, Caddy, then TLS route sequence.

View file

@ -0,0 +1,52 @@
# Result: independent R5 review
Base SHA: `8284eb5d006943d42133eb665c99dcbc3133d6a6`
Reviewed SHA: `ca28be6ae629d3d67936475ea5896b6f70f6e1e4`
Result SHA: `ca28be6ae629d3d67936475ea5896b6f70f6e1e4` (review only; no source changes)
Files changed by this reviewer: this report and the task record only; no production or test files.
## Scope and evidence
Reviewed the immutable `8284eb5..ca28be6` diff, the updated stage-one owner manifest, and the Caddy/HAProxy templates, removed nginx template, R5 runbook, and local syntax validator. I did not inspect live VPS configuration, alter containers, or run the validator/full gates. The current checkout HEAD matched the reviewed SHA during review. `AGENTS.md` had a pre-existing local modification and was left untouched.
The runbook does a careful job of distinguishing observed configuration from examples. It requires operator discovery of exact proxy image/version, process arguments, mounts and include files, port 443 mode/SNI ordering, port 80 HTTP-01 path, and source/active configuration identity before editing. It backs up both active proxy files and host source; it also gives separate full-tree backups for directory mounts. Single-file inode preservation, post-edit active/source comparison, validation before reload, a two-protocol baseline for every operator-enumerated site, and scoped rollback are documented. The HAProxy snippets are additive, preserve existing route/default order, and use separate TLS passthrough and cleartext Caddy backends. The Caddy 2.8 site disables TLS-ALPN to require HTTP-01 and does not add access logging. The obsolete nginx alternative is removed.
## Findings
### MEDIUM — R5 runbook cannot execute the HAProxy apply step without a separate procedure
`docs/STAGE1_DEPLOYMENT_RUNBOOK.md:396-403` deliberately leaves the HAProxy reload command unspecified and directs the operator to find a deployment-specific mechanism, otherwise to stop. This is a sound safety guard against guessing at a shared proxy’s process model, but it means this deliverable does not itself provide the requested executable no-restart apply path even for the owner-confirmed Docker/HAProxy 3.0 deployment. The operator must research and supply a process-specific procedure before R5 can proceed; the current text correctly calls that an operational blocker. Parent reported an isolated official HAProxy 3.0 fixture where USR2 preserved the container identity/start time/restart count while replacing worker PIDs, but this does not prove the actual VPS runs master-worker mode or that its PID1/config mechanism accepts that signal. Do not transfer that fixture result to production.
### LOW — HAProxy route is reloaded before Caddy’s new site configuration
`docs/STAGE1_DEPLOYMENT_RUNBOOK.md:405-408` instructs applying the HAProxy route first and reloading Caddy second. During that interval, new TLS traffic for the hostname can be sent to a Caddy process that has not loaded the site block yet, producing a transient certificate or routing failure. A safer sequence is to reload Caddy first and then expose the new HAProxy SNI route, with validation/hash gates retained between steps. This is a narrow new-host availability window; it does not imply harm to existing sites.
No CRITICAL or HIGH findings in the reviewed R5 delta.
## Commands actually run
- `git rev-parse HEAD` — `ca28be6ae629d3d67936475ea5896b6f70f6e1e4`.
- `git status --short` — showed the pre-existing `AGENTS.md` modification and task-record files; no source edits from this review.
- `git show --stat --oneline --no-renames 8284eb5d006943d42133eb665c99dcbc3133d6a6..ca28be6ae629d3d67936475ea5896b6f70f6e1e4` — reviewed R5 delta summary.
- `git show ca28be6...:docs/STAGE1_DEPLOYMENT_RUNBOOK.md | nl -ba | sed -n '140,470p'` — inspected runbook and cited line references.
- `git show ca28be6...:deploy/randomayzer.haproxy.cfg | nl -ba` and `git show ca28be6...:deploy/randomayzer.Caddyfile | nl -ba` — inspected proxy snippets.
- `git show ca28be6...:agents/codex/notes/validate-r5-templates.sh | nl -ba` — inspected the local syntax-validator implementation; did not execute it.
- `git diff --check 8284eb5d006943d42133eb665c99dcbc3133d6a6 ca28be6ae629d3d67936475ea5896b6f70f6e1e4` — exit 0.
- `bash -n <(git show ca28be6ae629d3d67936475ea5896b6f70f6e1e4:agents/codex/notes/validate-r5-templates.sh)` — exit 0.
Coordinator reports that target-version Caddy/HAProxy validation and Docker R5 validator passed, and app gates plus PostgreSQL integration passed at identical runtime code. These are coordinator-reported results, not commands run by this reviewer. This review did not run npm tests, lint, build, the Docker validator, or any VPS operation.
## UNVERIFIED
- Actual VPS container IDs, image patch versions, entrypoints, config/include paths, mount sources, inodes and active/source equality.
- Actual HAProxy mode, TLS inspector, SNI precedence, catch-all ordering, cleartext port 80 route, HTTP-01 reachability, and graceful reload mechanism.
- Actual Caddy admin endpoint/reload availability, certificate issuance/renewal, DNS, WireGuard peer identity, firewall policy, login/callback behavior, and all-site baseline health.
- Directory-mount include trees are backed up, but the runbook does not give a concrete recursive active/source tree comparison command after edits; it requires the operator to compare the full set. This is non-blocking documentation debt if that comparison is completed before reload.
## Remaining blockers and non-blocking debt
- The MEDIUM runbook gap blocks live R5 application until a conditional, inspection-gated HAProxy reload procedure is documented or an operator supplies and records the verified deployment-specific method.
- The LOW reload-order issue should be corrected before live use.
- Actual topology and live acceptance remain the operator’s responsibility and unverified. No phase-closed or production-ready determination is made here.
Final verdict: the R5 configuration examples and safeguards are well-scoped, but the current runbook is not yet an executable end-to-end procedure for the HAProxy apply step. No live environment facts were inferred.

View file

@ -0,0 +1,19 @@
# R5 reload and activation repair
Base SHA: ca28be6ae629d3d67936475ea5896b6f70f6e1e4.
Result SHA: accompanying commit (resolved via git log -- agents/codex/done/2026-10-19-r5-reload-fix.md).
Changed files: deploy/randomayzer.haproxy.cfg; docs/STAGE1_DEPLOYMENT_RUNBOOK.md; this report and associated inbox/review artifacts.
- Conditional executable HAProxy USR2 reload requires PID1 haproxy with -W/-Ws, complete validated config and worker handoff; unchanged container ID/start/restart count checked. Unknown topology stops.
- HTTP-01-only preparation precedes Caddy Basic Auth activation; TLS SNI route follows it.
- Whole-tree backups require nonempty copies and diff equality; rollback preserves unrelated edits.
Actual verification:
- bash agents/codex/notes/validate-r5-templates.sh: exit 0, Caddy 2.8.4 and HAProxy 3.0.30 configuration valid in isolated fake fixtures.
- bash -n on all 25 runbook bash blocks: exit 0.
- Exact runbook reload function, official HAProxy 3.0 fixture: exit 0, new worker observed and container identity/start/restart count unchanged.
- Same function against non-master PID1 sleep: rejected without changing container identity/start/restart count or running state.
- Temporary fixtures removed after checks; no VPS mutation.
UNVERIFIED: actual VPS process model, config mounts/admin API, active backend names, DNS/TLS, WireGuard peer, existing-site baselines and live acceptance. These remain operator gates, not completed deployment.
Blockers: independent rereview pending at report creation. Non-blocking debt: Caddy formatting and explicit forwarded-header warnings in local validation.

View file

@ -0,0 +1,48 @@
# Result — coder2 review of amended R5 proxy deployment
Base SHA: `8284eb5d006943d42133eb665c99dcbc3133d6a6`.
Reviewed/result SHA: `8284eb5d006943d42133eb665c99dcbc3133d6a6` (no commit; shared branch remains uncommitted).
## Scope
Reviewed the amended R5 assignment, Caddy 2.8 site fragment, HAProxy 3.0 additive snippet, operator runbook, and `agents/codex/notes/validate-r5-templates.sh`. I did not execute remote VPS/home-server operations, start/stop containers, modify app source/tests, or alter deployment templates.
The runbook now captures before/after responses for every listed current hostname on both HTTP and HTTPS, explicitly requires a concrete local listener address, and stops if a baseline request cannot connect. It requires a root shell for the VPS backup/Docker steps, compares active-container copies with host sources before edits, records and checks single-file mount device/inode, compares the active files with host source after edits, and uses HAProxy `-c -V` with an accurate success expectation. Existing instructions keep HTTP and TLS backend routes distinct, preserve the HTTP-01 token path, require SNI routing before catch-all, gate reload on actual process/config mechanism, and restore only the two changed configurations before rechecking all sites.
## Files changed by this review
- `agents/codex/inbox/TASK-2026-10-19-coder2-r5-review.md`
- `docs/STAGE1_DEPLOYMENT_RUNBOOK.md`
- `agents/codex/done/2026-10-19-r5-template-coder2-review.md`
The Caddy and HAProxy templates were reviewed but not edited.
## Verification
Commands I ran:
- `python3` extraction of all fenced `bash` blocks from `docs/STAGE1_DEPLOYMENT_RUNBOOK.md`, piping each block to `bash -n` — exit 0; 21 blocks parsed.
- `git diff --check -- docs/STAGE1_DEPLOYMENT_RUNBOOK.md deploy/randomayzer.Caddyfile deploy/randomayzer.haproxy.cfg deploy/randomayzer.nginx.conf` — exit 0.
- `git rev-parse HEAD` — `8284eb5d006943d42133eb665c99dcbc3133d6a6`.
Coordinator-provided validation evidence (not rerun by me because this assignment forbids container changes): `agents/codex/notes/r5-template-validation.log` records Caddy `Valid configuration`, HAProxy `Configuration file is valid`, and the local syntax validator PASS for Caddy 2.8 / HAProxy 3.0 fake fixtures. The Caddy warnings note formatting and an explicit `X-Forwarded-For` overwrite; the overwrite is intentional for this trust boundary. These fixture checks do not establish live VPS configuration compatibility.
## Findings
- CRITICAL: none.
- HIGH: none.
- MEDIUM: none.
- LOW: none open in my reviewed scope.
This is a scoped runbook review, not the independent R5 security review or live operator acceptance.
## UNVERIFIED
- Whether the actual VPS has the expected container names, exact Caddy/HAProxy patch versions, mounts, includes, listener addresses, routing modes, Caddy admin endpoint, and HAProxy graceful reload mechanism.
- Whether all active hostnames and their expected baseline results are correctly enumerated by the operator before change.
- Whether `10.77.0.1` is the confirmed VPS peer, the DNS record points to the VPS, HTTP-01 reaches Caddy unchanged, and the home firewall rules persist safely.
- Certificate issuance, Basic Auth response, domain VK login, and other-site behavior after a real reload.
## Remaining blockers / non-blocking tech debt
R5 cannot be accepted until the operator verifies the unknown topology and reload mechanisms, executes the documented backups/validation, and records successful before/after checks for every existing hostname. The parent-run local fixture validator assembles routes before its fixture catch-all to syntax-check them; it does not test ordering in the actual HAProxy config, which must be confirmed during operator inspection and insertion.

View file

@ -0,0 +1,33 @@
# Result — R5 runbook for existing proxy containers
Base SHA: `8284eb5d006943d42133eb665c99dcbc3133d6a6`
Result SHA: `8284eb5d006943d42133eb665c99dcbc3133d6a6` (working-tree edits only; no commit)
## Files changed
- `deploy/randomayzer.Caddyfile` — Caddy 2.8 site snippet, Basic Auth fallback, 1 MB request body limit, no access log, safe header overrides, no second proxy/listener.
- `deploy/randomayzer.haproxy.cfg` — additive SNI/HTTP routing examples with separate TLS and cleartext Caddy backends and explicit preservation of existing routes/default behavior.
- `deploy/randomayzer.nginx.conf` — removed obsolete nginx alternative, which does not match the confirmed Docker Caddy/HAProxy setup.
- `docs/STAGE1_DEPLOYMENT_RUNBOOK.md` — R5 operator procedure for actual container topology, redacted inspection, private backups, in-place source updates and inode/hash checks, pre/post validation, no-restart reload gates, other-site baselines, external checks, and rollback. Historical R4 loopback steps remain in place.
- `agents/codex/inbox/2026-10-19-r5-vps-container-runbook.md`
- `agents/codex/notes/2026-10-19-r5-vps-container-runbook.md`
- `agents/codex/done/2026-10-19-r5-vps-container-runbook.md`
## Verification actually run
- `docker run --rm --entrypoint caddy caddy:2.8-alpine version` — exit 0, local image reports `v2.8.4`.
- `docker run --rm --entrypoint haproxy haproxy:3.0-alpine -vv` — exit 0, local image reports `3.0.30-47d806b`.
- `docker run --rm -i --entrypoint caddy caddy:2.8-alpine validate --config /dev/stdin --adapter caddyfile` with the proposed site block and an artificial bcrypt hash — exit 0, `Valid configuration`; formatting and explicit X-Forwarded-For warnings only.
- `python3` temporary-fixture harness running `docker run --rm -v <tempdir>:/tmp/config:ro --entrypoint haproxy haproxy:3.0-alpine -c -f /tmp/config/haproxy.cfg` — exit 0. The fixture includes TCP SNI routing plus a distinct HTTP/cleartext backend; it is not the unknown active VPS config.
- `git diff --check -- docs/STAGE1_DEPLOYMENT_RUNBOOK.md deploy/randomayzer.Caddyfile deploy/randomayzer.nginx.conf` — exit 0.
- Python trailing-whitespace scan on the new HAProxy example and task inbox — no trailing whitespace.
Parent separately reported target-image validation of these templates; evidence files are under `agents/codex/notes/r5-*`.
No npm, Prisma or application test/lint/build gates were run for this documentation/config-template task. No remote machine was accessed or changed.
Findings: no new CRITICAL/HIGH/MEDIUM/LOW finding from these local template checks. The runbook intentionally leaves live R5 blocked until actual inspection confirms the host source/config paths, active SNI/HTTP routing, safe HAProxy reload mechanism, Caddy admin endpoint, correct DNS and WG peer. No HAProxy signal/restart is guessed.
UNVERIFIED: exact deployed patch versions; actual HAProxy topology/backend names/master-worker/control socket/reload procedure; active mounted config source paths and inode semantics; port-80 path to Caddy; whether Authelia policy is usable for this vhost; DNS target and peer identity; live issuance/renewal, Basic Auth/VK callback, other-site health and network isolation. No production claim is made.
Remaining blockers: operator/owner must perform the gated inspection and live acceptance. Non-blocking tech debt: none identified in this scoped task.

View file

@ -0,0 +1,157 @@
# Stage1 — результат локальной реализации и проверки
Исходный base SHA: 6382cb8e0473ba9ea5ba7a3f94f34e8ab0d4ec60.
Обновление задания: 52fb7587d7dda5ae3d1c5e9ecef96b7f48d147a3; включено merge-коммитом 8284eb5d006943d42133eb665c99dcbc3133d6a6.
Result / проверяемый implementation SHA: c5ede46bd1c31b1e23434538fdb8a1cd925751a6.
Production source, tests, dependencies, Prisma, Docker/Compose и tools совпадают с independently reviewed cce70c0b8feb263cc9209332c8c50a44bc97428a (git diff --exit-code: 0). После него менялись manifest, R5 proxy templates, документация и review artifacts.
Ветка: codex/stage1-completion. Финальный artifacts commit определять через git log -1 -- agents/codex/done/2026-10-19-stage1-completion.md. Этот отчёт фиксирует точный SHA результата кода выше, не самоссылочный SHA.
Main не изменялся. Локальная пользовательская правка AGENTS.md сохранена и не включена в коммиты.
## Реализация по актуальному заданию
- R1: next и eslint-config-next 16.3.2 → 16.3.8 (MIT), sharp 0.35.3 → 0.35.5 (Apache-2.0), source-map-js 1.2.1 → 1.2.2 (BSD-3-Clause). Production audit: 0.
- R2: PostgreSQL 16 на 127.0.0.1:55439; добавлена историческая initial migration перед двумя существующими миграциями. Первоначальный P3018/42P01 воспроизведён. Повторный draw возвращает ConflictError. Владелец прямо разрешил 32 случайных байта seed / 64 hex и узкое обновление seed assertions; authorization записана в inbox. Deterministic proof format, snapshot hash и HMAC_SHA256_FY_V1 не менялись.
- R3: неполная пагинация, malformed participant records и неполные subscription data вызывают явную ошибку до сохранения; prior participants сохраняются. Unsupported manager/repost capabilities объяснены. Smoke A–H требует реальный LIVE provider и непустые импорты, MOCK в production запрещён; ручные шаги и I–M сохранены.
- R4: явный local-single-user профиль только loopback, строгий HTTPS/Secure для остальных origin; CSRF привязан к canonical origin. Production entrypoint очищает forwarded headers по реальному socket peer. App 127.0.0.1:3100, PG без host ports, node/postgres users и CPU/RAM limits; backup/timer/clean restore, update/rollback и SSH tunnel в runbook.
- R5 по обновлению владельца: additive Caddy 2.8 / HAProxy 3.0 templates для krona-reverse-proxy и krona-proxy-mux; inspect mounts/process topology, fail-stop backups, validations, условный USR2 reload с worker/container evidence, Caddy auth до TLS route, other-site baselines и rollback. Существующие VPS/Forgejo/fleet/authelia/WireGuard конфигурации не менялись. Authelia integration не заявлена.
## Фактически выполненные проверки
Production code gate выполнен на cce70c0, после чего runtime-файлы не менялись:
| Команда | Результат |
|---|---|
| npm ci | exit 0; полный dev-inclusive tree сообщает 19 advisories |
| npx prisma generate | exit 0, Prisma Client 5.22.0 |
| npm test | exit 0, 63 файла / 380 тестов |
| npx tsc --noEmit | exit 0 |
| npm run lint | exit 0, 0 errors / 6 существующих img warnings |
| npm run build | exit 0, Next production build |
| npm audit --omit=dev | exit 0, found 0 vulnerabilities |
| docker compose up -d --wait | isolated PG16 healthy, loopback55439 |
| DATABASE_URL=<disposable PG16 URL> npx prisma migrate deploy | exit 0 на чистой randomayzer_stage1_final, применены 3 миграции |
| DATABASE_URL=<disposable PG16 URL> npm run test:integration | exit 0, 12/12, включая 10 concurrent locks → ровно 1 успех |
| npx prisma migrate diff --from-url <disposable PG16 URL> --to-schema-datamodel prisma/schema.prisma --exit-code | exit 0, No difference detected |
| docker compose --env-file <ignored 0600 fake env> -f compose.production.yml build app | exit 0 |
| тот же compose up -d --wait postgres / app и run --rm --no-deps app npx prisma migrate deploy | healthy production stack / 3 migrations |
| RANDOMAYZER_ENV_FILE=<fake env> tools/db-backup-daily.sh <private backup directory> | exit 0, clean temporary restore: 3 migrations + 1 fake giveaway, обе PASS строки |
| production HTTP smoke | anonymous session и PKCE/canonical localhost callback при поддельных Host/X-Forwarded-* прошли; не живой VK |
| bash agents/codex/notes/validate-r5-templates.sh | exit 0, Caddy2.8.4 / HAProxy3.0.30 validate; fake fixtures, network none, без опубликованных портов |
| bash -n для всех 25 bash-блоков runbook | exit 0 |
| точная reload_haproxy_without_restart из runbook в isolated HAProxy3.0 fixture | exit 0: новый worker, Id/StartedAt/RestartCount неизменны |
| та же функция с PID1 sleep | отвергнута, контейнер остался running без изменения identity/start/restart count |
| git diff --check | exit 0 |
Actual production image: sha256:8d6a8b2250cf813d078e24dfc79c743be43ec86533a3d24af87ac9d1ab4ed274.
Проверены runtime binding 127.0.0.1:3100 / DB ports {}, users node/postgres, app1GiB/1.5CPU, PG512MiB/1CPU.
Логи локально: agents/codex/notes/fix-final-*.log, final-schema-diff.log, r5-reload-fix-template-validation.log и r5-runbook-reload-function.log; ignored, не включают реальные credentials и не упакованы в PR.
Все временные app/PG/validator/reload контейнеры остановлены/удалены; удалены только два проверенно созданных в этой сессии throwaway DB volumes. Другие сервисы/volumes не трогали.
Ранее один raw npm test упал по timeout существующего live-network security.test.ts. Неизменённый focused повтор 7/7 и финальный raw полный прогон 380/380 прошли. Тесты/таймауты не ослаблялись. Начальные 10/12 и 11/12 integration failures устранены кодом и разрешённым seed contract. Неудачные попытки fixture setup и review во время npm ci отражены в промежуточных отчётах; окончательные проверки выше выполнены после исправления среды.
## Независимое review
Запрошенный цикл: coder1 → coder2 → auditor1 + auditor2 → исправления → оба re-review → coordinator gates.
- HIGH malformed likes/comments и MEDIUM ложный live-smoke на MOCK: CLOSED обоими независимыми re-review на cce70c0. Новые regression fixtures: 25 malformed route cases сохраняют prior state; smoke subprocess rejects MOCK / zero count.
- R5 MEDIUM отсутствующий executable HAProxy reload и LOW activation order / backup integrity: исправлены на c5ede46bd1c31b1e23434538fdb8a1cd925751a6; CLOSED по двум независимым re-review: 2026-10-19-auditor1-r5-rereview-c5ede46.md и 2026-10-19-auditor2-r5-rereview-c5ede46.md. Проверка только runbook; реальные VPS preconditions остаются открыты.
- CRITICAL: не найдено независимыми аудиторами в рассмотренном scope.
- LOW pre-existing COMMUNITY adminDetection metadata: остаётся OPEN как non-blocking debt; текущие маршруты SERVICE/USER и static server guard не позволяют включить неподдерживаемую проверку. Независимая классификация auditor2 в 2026-10-19-auditor2-high-rereview.md.
- LOW conditional HTTP redirect: если нужен новый challenge-only маршрут80, обычные HTTP-запросы не попадут в Caddy automatic redirect. Coordinator классифицирует как non-blocking: исходная приёмка требует HTTPS/HTTP-01, обычный HTTP redirect не заявлен. Проверять приложение только по HTTPS; при желании general HTTP route — отдельное операторское изменение после активного Caddy auth, с validate/reload/baselines. Finding не объявляется CLOSED.
- 6 lint warnings и dev-only advisories остаются non-blocking debt, не скрыты production audit.
## UNVERIFIED / оставшиеся внешние blockers
- Живой VK: credentials/consent, localhost redirect acceptance, актуальные official API contracts/scopes/limits, реальный A–H и ручные I–M. Без реальных ключей эти результаты не заявляются.
- Offset pagination на меняющихся внешних данных не доказывает point-in-time snapshot.
- DNS локально отдавал 185.26.122.80 вместо целевого178.212.13.102. Владелец подтверждает identity peer10.77.0.1; локальные interface10.77.0.3/route наблюдались.
- Actual VPS mount paths, active backends/process flags/Caddy admin API, установленный patch version, TLS certificate, proxy401, VK domain login, forwarded socket peer preservation и firewall persistence/isolation интернет/LAN/tunnel — проверяет оператор по runbook с отчётом agents/claude/done/.
- Нет live VPS изменений, выпуска сертификата или изменения DNS/firewall/tunnel здесь. Deployment templates не считаются принятым deployment.
Phase CLOSED / release-ready / production-ready не объявляются. Локальная реализация передаётся на PR review; внешние acceptance gates остаются открыты.
## Изменённые файлы относительно обновлённого main
Список ниже отражает implementation/review snapshot c5ede46bd1c31b1e23434538fdb8a1cd925751a6; данный итоговый отчёт и последующие review-only artifacts добавляются artifacts-коммитом.
```text
.dockerignore
Dockerfile
agents/codex/done/2026-10-19-auditor1-r5-review-ca28be6.md
agents/codex/done/2026-10-19-auditor1-rereview-cce70c0.md
agents/codex/done/2026-10-19-auditor2-high-rereview.md
agents/codex/done/2026-10-19-auditor2-high-vk-items.md
agents/codex/done/2026-10-19-auditor2-r5-review.md
agents/codex/done/2026-10-19-auditor2.md
agents/codex/done/2026-10-19-r5-reload-fix.md
agents/codex/done/2026-10-19-r5-template-coder2-review.md
agents/codex/done/2026-10-19-r5-vps-container-runbook.md
agents/codex/done/2026-10-19-smoke-mock-fix.md
agents/codex/done/TASK-2026-10-19-auditor1-independent-review.md
agents/codex/done/TASK-2026-10-19-coder2-review.md
agents/codex/done/TASK-2026-10-19-r1-r3.md
agents/codex/inbox/2026-10-19-auditor1-independent-review.md
agents/codex/inbox/2026-10-19-auditor1-r5-rereview-ca28be6.md
agents/codex/inbox/2026-10-19-auditor1-rereview-cce70c0.md
agents/codex/inbox/2026-10-19-auditor2-high-rereview.md
agents/codex/inbox/2026-10-19-auditor2-high-vk-items.md
agents/codex/inbox/2026-10-19-auditor2-r5-review.md
agents/codex/inbox/2026-10-19-auditor2.md
agents/codex/inbox/2026-10-19-r5-reload-fix.md
agents/codex/inbox/2026-10-19-r5-vps-container-runbook.md
agents/codex/inbox/2026-10-19-seed-owner-authorization.md
agents/codex/inbox/2026-10-19-smoke-mock-fix.md
agents/codex/inbox/2026-10-19-stage1-r2-assignment.md
agents/codex/inbox/2026-10-19-stage1-r4-r5-assignment.md
agents/codex/inbox/TASK-2026-10-19-coder2-r5-review.md
agents/codex/inbox/TASK-2026-10-19-coder2-review.md
agents/codex/inbox/TASK-2026-10-19-r1-dependencies.md
agents/codex/inbox/TASK-2026-10-19-r3-vk-safety.md
agents/codex/notes/.gitignore
agents/codex/notes/2026-10-19-auditor2-high-vk-items.md
agents/codex/notes/2026-10-19-r5-vps-container-runbook.md
agents/codex/notes/2026-10-19-stage1-baseline.md
agents/codex/notes/TASK-2026-10-19-r1-r3-notes.md
agents/codex/notes/stage1-pr-body.md
agents/codex/notes/validate-r5-templates.sh
compose.domain.yml
compose.production.yml
deploy/randomayzer-backup.service
deploy/randomayzer-backup.timer
deploy/randomayzer.Caddyfile
deploy/randomayzer.haproxy.cfg
docker-compose.yml
docs/PRODUCTION_GUARDS.md
docs/STAGE1_DEPLOYMENT_RUNBOOK.md
docs/VK_REAL_SMOKE_TEST.md
package-lock.json
package.json
prisma/migrations/20260818000000_initial/migration.sql
prisma/migrations/migration_lock.toml
src/app/api/giveaways/[id]/participants/route.ts
src/app/api/posts/preview/route.ts
src/app/giveaways/new/page.tsx
src/core/errors/http-errors.ts
src/core/randomizer/hasher.ts
src/core/validation/giveaway-schemas.ts
src/integrations/vk/vk-client.ts
src/integrations/vk/vk-errors.ts
src/lib/auth/app-config.ts
src/lib/auth/csrf-guard.ts
src/lib/auth/session.ts
src/lib/repository/prisma-repository.ts
src/providers/factory.ts
src/providers/vk/vk-capabilities.ts
src/providers/vk/vk-provider.ts
tests/deployment-profile.test.ts
tests/production-transport.test.ts
tests/randomizer.test.ts
tests/seed-precommit-gate.test.ts
tests/vk-r3-safety.test.ts
tests/vk-smoke-provider-mode.test.ts
tools/db-backup-daily.sh
tools/db-backup.sh
tools/db-restore-check.sh
tools/production-transport.cjs
tools/serve-production.cjs
tools/vk-smoke.sh
```

View file

@ -0,0 +1,7 @@
# Auditor1 assignment: R5 reload and backup re-review
- Base SHA: `ca28be6ae629d3d67936475ea5896b6f70f6e1e4`
- Reviewed SHA: `c5ede46bd1c31b1e23434538fdb8a1cd925751a6`
- Scope: re-review the two findings in `agents/codex/done/2026-10-19-auditor1-r5-review-ca28be6.md`, including the HAProxy reload procedure, backup integrity checks, staged HTTP-01/Caddy-auth/TLS ordering, and any concrete remaining blocker in that diff.
- Read-only: do not edit production source/tests, run Docker/VPS/container commands, commit, or push.
- Report: write a unique auditor1 report under `agents/codex/done` with exact SHA, findings marked CLOSED/OPEN, evidence and commands, UNVERIFIED items, and blockers.

View file

@ -0,0 +1,7 @@
# Task
Base SHA: 8284eb5
Reviewed SHA: ca28be6ae629d3d67936475ea5896b6f70f6e1e4
Scope: Independent review of the owner-updated R5 deployment delta and runbook for the existing Caddy 2.8 plus HAProxy 3.0 Docker topology.
Do not change: Production/source/test code, templates, containers, or host configuration. No commit/push/delegation; do not rerun the coordinator's named Docker validator. Preserve pre-existing AGENTS.md modification.
Definition of Done: Concrete findings with severity/path/line/reproducer; exact SHA; commands/results; UNVERIFIED topology/evidence and blockers.
Required verification: Read-only source/runbook review and safe syntax checks only. No VPS or container access.

View file

@ -0,0 +1,7 @@
# Task
Base SHA: 17faecd962c2cf40e0a3f6ec4434811cfffdd312
Reviewed SHA: cce70c0b8feb263cc9209332c8c50a44bc97428a
Scope: Independent rereview of the fixes for the auditor1 smoke-provider finding and auditor2 malformed VK items finding, against the immutable fix commit.
Do not change: Production code/tests or prior review reports; no commit/push, no containers, no delegation. Preserve pre-existing AGENTS.md modification.
Definition of Done: Independently trace the fixes and regression tests; classify prior findings CLOSED or OPEN only with source/test evidence; record any new findings, blockers, UNVERIFIED claims, exact SHA and commands.
Required verification: Focused tests only; root owns final full gates.

View file

@ -0,0 +1,3 @@
# Auditor2 rereview — malformed VK participant data fix
Review immutable commit `cce70c0b8feb263cc9209332c8c50a44bc97428a` against prior reviewed commit `17faecd962c2cf40e0a3f6ec4434811cfffdd312`. Reassess auditor2 HIGH finding about malformed likes/comments records and partial participant persistence; inspect/reproduce focused regression tests. Independently assess whether COMMUNITY admin capability metadata discrepancy is pre-existing and nonblocking under current routes/server guards. Read-only: no production/test edits, commit/push, or container mutation. Do not communicate conclusions with auditor1 before report freeze. Write separate report under agents/codex/done and individual memory worklog; exact reviewed SHA, evidence, commands, severity and CLOSED/OPEN status. Do not declare phase closed; root owns final gates and Docker verification.

View file

@ -0,0 +1,6 @@
# Task
Base SHA: `ca28be6ae629d3d67936475ea5896b6f70f6e1e4`
Scope: Independently rereview the R5 follow-up at immutable `c5ede46bd1c31b1e23434538fdb8a1cd925751a6`, focusing on whether the HAProxy reload is executable and safely gated to a verified process model, and whether activation ordering closes the prior LOW window.
Do not change: production source/configs, tests, containers, remote VPS, commits, or pushes. Read only; write only this task record and a unique done report.
Definition of Done: Report exact reviewed SHA, findings and CLOSED/OPEN assessment for prior MEDIUM and LOW with file/line evidence, actual commands run, UNVERIFIED blockers, and remaining debt. Do not claim phase closure.
Required verification: Inspect immutable diff and current report scope. Do not rerun full gates or perform live operations.

View file

@ -0,0 +1,3 @@
# Auditor2 independent R5 review
Review immutable SHA `ca28be6ae629d3d67936475ea5896b6f70f6e1e4` against `8284eb5` as assigned by coordinator. Read updated owner manifest and scrutinize HAProxy/Caddy templates, removed nginx alternative, actual VPS runbook, and local reproducible validator for route preservation, backups/config-tree/inode provenance, TLS 443 vs HTTP 80 ACME routing, SNI priority, syntax/config validation, graceful reload gating, rollback, and all-site HTTP/HTTPS baselines. No production/source edits, commit/push, VPS access/restarts or container mutations. Do not assume actual topology; unknowns remain UNVERIFIED. Do not rerun full app gates or the Docker validator already run by coordinator. Write unique task/report and individual memory worklog, with exact SHA, severity/evidence, UNVERIFIED blockers, then freeze.

View file

@ -0,0 +1,5 @@
# R5 independent-review repair
Base/reviewed SHA: ca28be6ae629d3d67936475ea5896b6f70f6e1e4.
Scope: executable conditional HAProxy master-worker reload with process/container evidence and rollback; fail-closed whole-tree backups; explicit HTTP-01/Caddy-auth/TLS-route staging. No actual VPS edits. Preserve existing unrelated routes and require actual runtime/mount preconditions.
Evidence already observed in isolated official HAProxy3.0 fixture: PID1 haproxy -W -db -f..., USR2 changed worker generation while container ID/start/restart count remained unchanged. Actual VPS process model remains UNVERIFIED.
DoD: syntax and fixture checks, independent rereview, explicit unknown-state stop branches.

View file

@ -0,0 +1,7 @@
# Assignment — R5 runbook for the actual VPS proxy containers
Base SHA: `8284eb5d006943d42133eb665c99dcbc3133d6a6`
Scope: only deploy templates and `docs/STAGE1_DEPLOYMENT_RUNBOOK.md`. Update the R5 procedure for the reported target containers `krona-proxy-mux` (`haproxy:3.0-alpine`) and `krona-reverse-proxy` (`caddy:2.8-alpine`). Add safe in-place additive SNI and site examples, preserve unrelated sites/default routes and the HTTP-01 path, include private backups, version-appropriate validation, non-restart reload only after inspection confirms the actual mechanism, before/after other-site baselines, and a concrete rollback. Use Basic Auth as the supported fallback; Authelia remains optional only if the existing policy is verified. Unknown mount/config paths, routing mode, reload mechanism, DNS/peer identity and live acceptance remain `UNVERIFIED` and must stop the operator until observed. No real remote changes.
Do not modify application source/tests, existing review reports, compose/auth/deploy app settings, unrelated services or existing user changes. No commit/delegation. Record result and evidence in this agent's `agents/codex/{notes,done}`.

View file

@ -95,7 +95,20 @@ WireGuard → домашний сервер → приложение.** Проб
Сделать:
1. **Домашний сервер.** Приложение дополнительно слушает `10.77.0.3:<порт>` (не `0.0.0.0`).
На хосте firewall разрешает этот порт только с `10.77.0.1`. БД по-прежнему не публикуется.
2. **VPS.** Фрагмент конфигурации reverse proxy под существующий на VPS веб-сервер
2. **VPS — уточнение по факту (Claude, 19.10).** На VPS всё в Docker:
- TLS держит контейнер `krona-reverse-proxy` (`caddy:2.8-alpine`);
- перед ним `krona-proxy-mux` (`haproxy:3.0-alpine`), по-видимому, маршрутизирует 443 по SNI;
- есть `authelia`;
- Forgejo — `krona-git-forgejo`;
- там же production fleet-hub (`fleet-*`), krona pilot и business-platform.
Нужно: маршрут `random.ochenstarik.ru` в HAProxy (если он раскидывает по SNI) и блок сайта
в Caddyfile контейнера `krona-reverse-proxy`. Перед правкой — бэкап обоих конфигов, `caddy validate`
и `haproxy -c` внутри контейнеров, reload без рестарта. После reload проверить, что `git.ochenstarik.ru`
и остальные сайты отвечают как прежде. Можно ли закрыть сайт через Authelia вместо basic auth —
по месту. Чужие блоки не трогать.
Исходный пункт ниже читать с учётом этого:
Фрагмент конфигурации reverse proxy под существующий на VPS веб-сервер
(nginx или Caddy — определить по runbook, не ставить второй на 443):
- TLS для `random.ochenstarik.ru` (Let's Encrypt, HTTP-01 на VPS);
- `proxy_pass http://10.77.0.3:<порт>`, заголовки `X-Forwarded-*` и `Host`;

View file

@ -0,0 +1,6 @@
# Task
Base SHA: 8284eb5d006943d42133eb665c99dcbc3133d6a6
Scope: Review the amended R5 Caddy 2.8 / HAProxy 3.0 templates, operator runbook, and reproducible local syntax validator. Fix concrete defects only in the R5 runbook/templates and write an individual review report.
Do not change: application source/tests, compose/auth settings, prior review reports, AGENTS.md, existing unrelated services, remote VPS/home-server state, containers, commits, or delegation.
Definition of Done: Templates/runbook provide safe backups, source and active mount checks, correct separate TLS 443 and HTTP 80 routes, HTTP-01 preservation, safe no-restart validation/reload gates, before/after checks for every existing website, and rollback; unknown facts remain operator blockers.
Required verification: review validator evidence, run read-only syntax/diff checks, record exact base/result SHA, changes, findings, UNVERIFIED items, and blockers.

View file

@ -6,3 +6,5 @@
stage1-baseline-schema.prisma
backups/
r5-fixtures/

View file

@ -0,0 +1,15 @@
# Notes — R5 Docker proxy runbook
Base SHA: `8284eb5d006943d42133eb665c99dcbc3133d6a6`. No commit made; current HEAD remains unchanged.
Scope stayed limited to proxy templates and the deployment runbook. Updated R5 to the reported proxy containers (`krona-proxy-mux`, HAProxy 3.0; `krona-reverse-proxy`, Caddy 2.8), with inspections of command/mounts that omit environment, config backup, same-inode edits/checks, actual config validation, reload gates, host/passthrough and cleartext backends kept separate, other-site before/after baselines, Basic Auth only, and rollback. Exact VPS paths, HAProxy routing/process model/reload, Caddy admin endpoint, DNS and peer identity remain gated UNVERIFIED facts.
Local image checks run:
- `docker run --rm --entrypoint caddy caddy:2.8-alpine version` — `v2.8.4`.
- `docker run --rm --entrypoint haproxy haproxy:3.0-alpine -vv` — `3.0.30-47d806b`.
- Caddy 2.8.4 `validate --config /dev/stdin --adapter caddyfile` against the proposed site block with an artificial hash — exit 0 / `Valid configuration`; two warnings were formatting and the explicit X-Forwarded-For override.
- HAProxy 3.0.30 `-c -f` against a temporary assembled mode-tcp SNI frontend plus separate mode-http/cleartext frontend fixture — exit 0. This fixture is not the VPS active config.
- `git diff --check -- docs/STAGE1_DEPLOYMENT_RUNBOOK.md deploy/randomayzer.Caddyfile deploy/randomayzer.nginx.conf` — exit 0.
- Python trailing-whitespace scan on the new HAProxy snippet and inbox assignment — no trailing whitespace.
No remote VPS/home-server changes, npm/app gates, Docker production stack changes, or actual TLS/VK acceptance were performed. Parent independently reported that it validated the submitted Caddy snippet and assembled HAProxy snippets with the cached target images; see `agents/codex/notes/r5-*`.

View file

@ -37,3 +37,17 @@ Independent review cycle 1 (immutable 17faecd962c2cf40e0a3f6ec4434811cfffdd312):
- auditor2 HIGH: malformed likes/comments accepted or silently omitted before complete save; coder1 assigned strict element validation + route preservation PoC regressions.
- auditor2 LOW: COMMUNITY effective adminDetection metadata remains true although no manager implementation. This predates our patch; actual static server validation disables manager exclusion, route currently uses SERVICE/USER. Classify as non-blocking pre-existing debt, do not change old tests/expand scope silently.
- Final gates at 17faecd: npm ci/generate/test372/372/tsc/lint(0errors6warnings)/build/audit0 all exit0. Fresh DB randomayzer_stage1_final migration3 applied, integration12/12, schema diff0. Follow-up fixes require new gates/rereview; prior passes do not close findings.
2026-10-19 owner amendment fetched during final verification:
- Forgejo main advanced to52fb7587d7dda5ae3d1c5e9ecef96b7f48d147a3; GitHub main still6382cb8 at fetch.
- Only change: R5 task now confirms containerized Caddy2.8 krona-reverse-proxy and HAProxy3.0 krona-proxy-mux, requires backups/validation/reload without restart and other-site checks.
- Merged updated manifest into our branch, HEAD8284eb5d006943d42133eb665c99dcbc3133d6a6; production code unchanged from independently re-reviewed cce70c0.
- Coder1 preparing updated R5 templates/runbook; coder2 and independent review to follow. No VPS changes.
- Final cce70c0 production image sha256:8d6a8b2250cf813d078e24dfc79c743be43ec86533a3d24af87ac9d1ab4ed274 up healthy; actual loopback3100 binding, DBports{}, users/limits confirmed. Repeated HTTP PKCE/canonical callback and nonempty backup clean-restore passed. Deleted only our final fake giveaway/user marker after drill.
Final verification cleanup:
- Final production and integration test stacks stopped with their respective compose down commands.
- Before teardown, production test DB counts were User0/Giveaway0/UserCredential0/Session0; volume creation timestamp and project label proved it was created in this session with FAKE credentials.
- Removed only those two owned throwaway test volumes (randomayzer-database, randomayzer-integration-pgdata) after checking their Compose project labels. This prevents a future real deployment inheriting the fake bootstrap database password. No global Docker prune.
- getent ahostsv4 random.ochenstarik.ru returned185.26.122.80 via local resolver; DNS-to178.212.13.102 remains an external blocker. Local krona-wg address10.77.0.3/24 and route to10.77.0.1 were observed; peer's VPS identity still requires owner confirmation.
- Reproducible R5 validator agents/codex/notes/validate-r5-templates.sh returned exit0 for Caddy2.8.4 and HAProxy3.0.30 assembled configs. Initial nonroot Caddy fixture setup failed with dropped file capabilities/empty fake hash; validator now adds only NET_BIND_SERVICE to satisfy the binary filecap and asserts fake hash generation. No ports or network are exposed by validators.

View file

@ -1,7 +1,12 @@
Stage1 R1–R5 implementation from the owner-supplied inbox manifest. Updates vulnerable production dependencies, adds a clean PostgreSQL bootstrap path and fixes repository duplicate-draw conflict handling, rejects incomplete VK participant/subscription data, and provides a private live-smoke script plus explicit loopback/domain deployment profiles.
Неполные и malformed ответы VK теперь останавливают импорт до сохранения участников; live-smoke отвергает MOCK и пустые результаты. Обновлены уязвимые production dependencies, исправлены clean PostgreSQL bootstrap и duplicate-draw conflict. Добавлены loopback production-профиль, socket-based proxy header sanitization, backup/restore scripts и runbook для существующих Caddy/HAProxy контейнеров VPS с условным reload без restart.
The owner explicitly authorized 32-byte random seeds and the narrow seed assertion changes. Deterministic proof/hash/HMAC algorithms remain unchanged.
Учтено обновление задания из main52fb758. Владелец прямо разрешил 32-byte seed и узкие seed assertions; proof/hash/HMAC алгоритмы сохранены. Main автором PR не вливается.
Local production stack and clean backup restore have been exercised with fake marker credentials. Real VK consent/scopes and VPS DNS/TLS/basic-auth/WireGuard/firewall acceptance require the operator; they are not claimed as complete. Main is not merged by this PR author.
Проверки production code cce70c0 (runtime-файлы далее не менялись): npm ci, prisma generate, npm test380/380, tsc, lint0errors/6warnings, build; npm audit --omit=dev0. PostgreSQL16: clean3migrations, integration12/12, schema diff0. Production Docker healthy; clean restore с3migrations/1fakegiveaway прошёл. Caddy2.8.4/HAProxy3.0.30 configs valid;25 shell blocks syntax pass; exact reload function показала worker handoff без изменения container identity/start/restart count и отказ при неподдерживаемом PID1.
Final validation and independent review evidence will be recorded in agents/codex/done/2026-10-19-stage1-completion.md.
Два независимых reviewer закрыли найденные HIGH/MEDIUM после исправлений: malformed VK items, ложный live-smoke и отсутствующий executable reload. Открытые non-blocking LOW: pre-existing COMMUNITY capability metadata и отсутствие ordinary HTTP redirect при challenge-only маршруте. Dev-inclusive audit19 advisories; production audit0.
Живой VK, DNS/TLS, proxy401, domain login и firewall/WireGuard isolation не проверены на VPS: это выполняет Claude/владелец по runbook. Phase CLOSED/release-ready не объявляются.
Полный evidence, SHA, список файлов и ограничения: agents/codex/done/2026-10-19-stage1-completion.md.
Операторский runbook: docs/STAGE1_DEPLOYMENT_RUNBOOK.md.

View file

@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Reproducible local syntax check. Fake credentials only; no published ports or VPS access.
set -euo pipefail
root=$(cd "$(dirname "$0")/../../.." && pwd)
cd "$root"
fixtures="$root/agents/codex/notes/r5-fixtures"
mkdir -p "$fixtures"
chmod 755 "$fixtures"
docker run --rm --name randomayzer-caddy-hash-fixture --network none --user 65534:65534 --cap-drop ALL --cap-add NET_BIND_SERVICE --security-opt no-new-privileges:true caddy:2.8-alpine caddy hash-password --plaintext FAKE_R5_CONFIG_TEST_ONLY > "$fixtures/fake-bcrypt.txt"
python3 - <<'PY'
from pathlib import Path
root=Path('agents/codex/notes/r5-fixtures')
h=(root/'fake-bcrypt.txt').read_text().strip()
assert h.startswith('$2'), 'Fake hash generation failed'
s=Path('deploy/randomayzer.Caddyfile').read_text()
assert s.count('owner BASIC_AUTH_HASH') == 1
p=root/'Caddyfile';p.write_text(s.replace('owner BASIC_AUTH_HASH','owner '+h));p.chmod(0o644)
s=Path('deploy/randomayzer.haproxy.cfg').read_text()
tls='\n'.join(x for x in s.splitlines() if x.startswith(('acl ','use_backend '))).replace('CADDY_TLS_BACKEND_FROM_ACTIVE_CONFIG','fixture_caddy_tls')
http='\n'.join(x[2:] for x in s.splitlines() if x.startswith(('# acl randomayzer_http','# use_backend CADDY_HTTP'))).replace('CADDY_HTTP_BACKEND_FROM_ACTIVE_CONFIG','fixture_caddy_http')
assert 'use_backend fixture_caddy_tls' in tls
assert 'use_backend fixture_caddy_http' in http
config='''global
master-worker
defaults
mode tcp
timeout connect 5s
timeout client 10s
timeout server 10s
frontend fixture_tls
bind :8443
tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 }
'''+''.join(' '+x+'\n' for x in tls.splitlines())+''' default_backend fixture_other_tls
frontend fixture_http
mode http
bind :8080
'''+''.join(' '+x+'\n' for x in http.splitlines())+''' default_backend fixture_other_http
backend fixture_caddy_tls
mode tcp
server caddy 127.0.0.1:9443
backend fixture_other_tls
mode tcp
server other 127.0.0.1:9444
backend fixture_caddy_http
mode http
server caddy 127.0.0.1:9080
backend fixture_other_http
mode http
server other 127.0.0.1:9081
'''
p=root/'haproxy.cfg';p.write_text(config);p.chmod(0o644)
PY
docker run --rm --name randomayzer-caddy28-validate --network none --user 65534:65534 --cap-drop ALL --cap-add NET_BIND_SERVICE --security-opt no-new-privileges:true -e XDG_DATA_HOME=/tmp/caddy-data -e XDG_CONFIG_HOME=/tmp/caddy-config -v "$fixtures/Caddyfile:/etc/caddy/Caddyfile:ro" caddy:2.8-alpine caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
docker run --rm --name randomayzer-haproxy30-validate --network none --cap-drop ALL --security-opt no-new-privileges:true -v "$fixtures/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro" haproxy:3.0-alpine haproxy -c -V -f /usr/local/etc/haproxy/haproxy.cfg
printf 'PASS local Caddy 2.8 / HAProxy 3.0 template syntax (fake fixture, not live VPS acceptance)\n'

View file

@ -1,4 +1,8 @@
# Add this site only to an existing Caddy. Replace BASIC_AUTH_HASH with `caddy hash-password` output.
# Add this site block to the existing Caddy 2.8 Caddyfile (or an already-imported
# site-fragment directory). This does not define a second listener or proxy.
# Replace BASIC_AUTH_HASH with interactive `caddy hash-password` output.
# Basic Auth is the supported access gate; do not add Authelia without confirming
# the existing proxy's auth policy and trusted identity headers.
random.ochenstarik.ru {
tls {
issuer acme {
@ -8,6 +12,7 @@ random.ochenstarik.ru {
basic_auth {
owner BASIC_AUTH_HASH
}
# Caddy 2.8 supports this body limit. Keep it ahead of the upstream.
request_body {
max_size 1MB
}
@ -16,6 +21,8 @@ random.ochenstarik.ru {
header_up Host random.ochenstarik.ru
header_up X-Forwarded-Host random.ochenstarik.ru
header_up X-Forwarded-Proto https
# Replace caller-supplied forwarding metadata; Caddy's peer identity
# depends on the confirmed HAProxy-to-Caddy path.
header_up X-Forwarded-For {remote_host}
header_up -X-Real-IP
header_up -Forwarded

View file

@ -0,0 +1,27 @@
# Additive examples only; this is NOT a standalone HAProxy configuration.
# Do not paste this whole file over the live config.
#
# 443 TLS passthrough example: insert in the existing mode tcp frontend bound
# to :443 after its existing TLS inspection rules and after explicit routes for
# other domains, but before any catch-all use_backend/default_backend. Replace
# CADDY_TLS_BACKEND_FROM_ACTIVE_CONFIG with the exact backend already used for
# krona-reverse-proxy. Do not create backend addresses based on guesses about
# Docker networks.
# If this frontend has no TLS ClientHello inspection, stop and reconcile its
# active routing design before adding these lines.
#
# tcp-request inspect-delay 5s # add only if the frontend has no inspect delay
acl randomayzer_sni req.ssl_sni -i random.ochenstarik.ru
use_backend CADDY_TLS_BACKEND_FROM_ACTIVE_CONFIG if randomayzer_sni
# Keep existing explicit routes and the original default_backend unchanged.
# HTTP-01 example: only if inspection shows an existing mode http :80 frontend
# does not already route this host to Caddy. Put the host ACL and use_backend
# after existing explicit host routes and before any catch-all rule. This must
# use a distinct cleartext Caddy :80 backend, never the TLS :443 backend above.
# Caddy must receive the full, unchanged /.well-known/acme-challenge/<token>
# path on port 80; do not redirect, rewrite, or route it elsewhere in HAProxy.
#
# acl randomayzer_http01 path_beg /.well-known/acme-challenge/
# acl randomayzer_http_host hdr(host),field(1,:) -i random.ochenstarik.ru
# use_backend CADDY_HTTP_BACKEND_FROM_ACTIVE_CONFIG if randomayzer_http_host randomayzer_http01

View file

@ -1,37 +0,0 @@
# Include in the existing nginx HTTP context. Install ONLY if nginx already owns 80/443.
server {
listen 80;
server_name random.ochenstarik.ru;
location ^~ /.well-known/acme-challenge/ { root /var/www/randomayzer-acme; }
location / { return 301 https://random.ochenstarik.ru$request_uri; }
}
server {
listen 443 ssl;
server_name random.ochenstarik.ru;
ssl_certificate /etc/letsencrypt/live/random.ochenstarik.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/random.ochenstarik.ru/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
auth_basic "Randomayzer owner";
auth_basic_user_file /etc/nginx/randomayzer.htpasswd;
client_max_body_size 1m;
client_body_timeout 20s;
# OAuth callback has query credentials: never log request paths/query strings.
access_log off;
error_log /var/log/nginx/randomayzer-error.log crit;
location / {
proxy_pass http://10.77.0.3:3100;
proxy_bind 10.77.0.1;
proxy_set_header Host random.ochenstarik.ru;
proxy_set_header X-Forwarded-Host random.ochenstarik.ru;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP "";
proxy_set_header Forwarded "";
proxy_set_header CF-Connecting-IP "";
# Basic credentials are for nginx only, never forward them to the application.
proxy_set_header Authorization "";
proxy_connect_timeout 10s;
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
}

View file

@ -139,28 +139,56 @@ docker compose -f docker-compose.yml down
Integration tests очищают все данные: **никогда не подставлять production URL**. Имена и volume отделены от production.
## 6. R5: предварительно на VPS и домашнем сервере
## 6. R5: подготовка и безопасное изменение действующих proxy-контейнеров
**BLOCKED до подтверждения владельца:** A-record `random.ochenstarik.ru → 178.212.13.102`,
peer `10.77.0.1` — именно этот VPS, какой существующий веб-сервер владеет 80/443.
Здесь не предполагается, что DNS/сертификат/вход уже проверены.
R5 остаётся **BLOCKED** до подтверждения владельцем DNS `random.ochenstarik.ru → 178.212.13.102`
и того, что домашний `10.77.0.1` — этот VPS. Не менять WireGuard, firewall manager, Compose-проекты,
контейнеры или существующие виртуальные хосты других приложений.
На VPS (без изменений):
VPS-команды ниже запускай из root shell с доступом к Docker; если SSH-сеанс не root, выполни `sudo -i`
и перейди в checkout. Это нужно для приватного backup под `/root` и управления действующими контейнерами.
В root Bash включить остановку при ошибке; до начала согласовать окно без параллельного редактирования proxy
конфигов. При изменении файлов другим оператором прекратить работу и согласовать diff, не затирать его backup.
```bash
ss -ltn '( sport = :80 or sport = :443 )'
systemctl is-active nginx caddy apache2
ip route get 10.77.0.3
getent ahostsv4 random.ochenstarik.ru
curl --connect-timeout 5 http://10.77.0.3:3100/api/auth/me
set -euo pipefail
```
До включения domain overlay последний curl должен быть недоступен. Маршрут к `10.77.0.3` должен иметь
`src 10.77.0.1`; иначе остановиться, текущий WireGuard не менять.
Если 443 принадлежит Apache или контейнеру неизвестной конфигурации, эти nginx/Caddy snippets не применять:
оператор сначала определяет точку подключения отдельного virtual host. Чужой vhost не заменять.
Не выводи `.Config.Env`. На домашнем сервере оставайся под своим обычным пользователем, используя `sudo`
только для firewall-команд.
### Домашний сервер: firewall раньше публикации WG-порта
### 6.1. Установить фактическую топологию без просмотра environment
На VPS выполняет оператор. Команды намеренно не выводят `.Config.Env`, содержимое конфигов, логи или секреты:
```bash
docker ps --filter name=krona-proxy-mux --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
docker ps --filter name=krona-reverse-proxy --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
docker inspect --format 'image={{.Config.Image}} path={{.Path}} args={{json .Args}} entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}}' krona-proxy-mux
docker inspect --format '{{range .Mounts}}{{printf "%s\t%s\t%s\tRW=%t\n" .Type .Source .Destination .RW}}{{end}}' krona-proxy-mux
docker inspect --format 'image={{.Config.Image}} path={{.Path}} args={{json .Args}} entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}}' krona-reverse-proxy
docker inspect --format '{{range .Mounts}}{{printf "%s\t%s\t%s\tRW=%t\n" .Type .Source .Destination .RW}}{{end}}' krona-reverse-proxy
docker exec krona-proxy-mux haproxy -vv | sed -n '1,3p'
docker exec krona-reverse-proxy caddy version
ss -ltn '( sport = :80 or sport = :443 )'
getent ahostsv4 random.ochenstarik.ru
ip route get 10.77.0.3
```
Ожидается: контейнеры с указанными именами и образами семейств `haproxy:3.0-alpine` / `caddy:2.8-alpine`,
HAProxy 3.0.x и Caddy 2.8.x. Точные patch-версии на VPS зафиксировать из вывода; локальный образ/тег не
доказывает версию удалённого контейнера. `getent` должен вернуть подтверждённый A-record, маршрут к дому —
`src 10.77.0.1`. Если имя/образ/режим процесса отличается, не применять примеры ниже.
По `Entrypoint`, `Cmd` и mounts оператор определяет фактические config path(ы), все `include`-файлы и source
файлы, которыми контейнер управляется. Не печатать/публиковать raw-конфиги или полные inspect-объекты.
Для HAProxy подтвердить, что порт 443 — `mode tcp` TLS passthrough и где уже выполняются SNI inspection,
`use_backend` и catch-all. Для HTTP-01 установить фактическую цепочку порта 80 до Caddy. Если HAProxy
терминирует TLS, существующий SNI-маршрут не ведёт к Caddy, не определён безопасный путь HTTP-01 до Caddy,
или для редактирования/reload не найден штатный механизм — **остановиться**. Не угадывать backend, адрес
Docker-сети, config path или команду reload.
На домашнем сервере сначала проверить владельца WG peer и firewall:
```bash
ip -brief address show krona-wg
@ -169,11 +197,11 @@ sudo iptables -S DOCKER-USER
sudo iptables -S INPUT
```
Ожидается 10.77.0.3 на существующем интерфейсе и Docker iptables backend. Если используется nftables native,
firewalld/ufw или другая система управления — остановиться и поручить владельцу эквивалентные правила в его
существующем manager; не смешивать managers. Для iptables Docker backend следующий отдельный chain
ограничивает только original destination 10.77.0.3:3100. Docker DNAT может обходить INPUT, поэтому нужен
DOCKER-USER. Дополнительный INPUT rule покрывает host/userland-proxy путь.
Ожидается `10.77.0.3` на существующем `krona-wg`, маршрут через него и подтверждённый владельцем peer
`10.77.0.1`. Если используется nftables, firewalld, ufw или иной manager — не добавлять параллельные
iptables rules; остановиться и согласовать правила в существующем manager. Только после подтверждения
iptables Docker backend, отсутствия chain с таким именем и проверки текущих rules установить ограничение
до публикации порта:
```bash
sudo iptables -N RANDOMAYZER-WG
@ -181,13 +209,21 @@ sudo iptables -A RANDOMAYZER-WG -s 10.77.0.1 -j RETURN
sudo iptables -A RANDOMAYZER-WG -j DROP
sudo iptables -I DOCKER-USER 1 -p tcp -m conntrack --ctorigdst 10.77.0.3 --ctorigdstport 3100 -j RANDOMAYZER-WG
sudo iptables -I INPUT 1 -p tcp -d 10.77.0.3 --dport 3100 -j RANDOMAYZER-WG
sudo iptables -S RANDOMAYZER-WG
sudo iptables -S DOCKER-USER
sudo iptables -S INPUT
```
Выполнять один раз, не дублировать chain/rules. После тестов оператор сохраняет **только добавленные правила**
через существующий менеджер firewall. При отсутствии подтверждённой persistence R5 не завершён.
Не открывать 3100 на 0.0.0.0, LAN или роутере. IPv6 WG-порт не публикуется.
Ожидается разрешение только source `10.77.0.1` для host destination `10.77.0.3:3100`; остальным source
соединения к этому destination drop. Если chain уже существует или вывод не соответствует, не повторять
команды/не вставлять дубли; разобрать текущие rules с владельцем. После проверки сохранить только новые
rules через уже работающий firewall manager. Без подтверждённой persistence R5 не принят. Не публиковать
порт на LAN, wildcard или роутере.
В той же безопасной последовательности, из checkout на домашнем сервере, включить только domain overlay:
```bash
export RANDOMAYZER_ENV_FILE=/etc/randomayzer/production.env
ryd() { docker compose --env-file "$RANDOMAYZER_ENV_FILE" -f compose.production.yml -f compose.domain.yml "$@"; }
ryd config --quiet
ryd up -d --wait app
@ -195,79 +231,308 @@ ryd ps
ss -ltn '( sport = :3100 )'
```
Ожидаются host bindings только 127.0.0.1:3100 и 10.77.0.3:3100. Domain profile использует HTTPS canonical origin,
Secure cookie и VK redirect `https://random.ochenstarik.ru/api/auth/vk/callback`. Loopback URL более не подходит
для пользовательского login в строгом профиле. В VK ID оператор меняет зарегистрированный redirect на HTTPS.
На VPS повторить curl к `10.77.0.3:3100/api/auth/me`: ожидается JSON. Подтвердить сохранение socket source
10.77.0.1 при Docker DNAT, например tcpdump метаданными `-q`, **без `-A`/payload/logging OAuth**.
При source NAT на gateway приложение игнорирует proxy headers; менять trust на всю Docker subnet запрещено.
### VPS: добавить ровно один virtual host
**nginx (только если он уже обслуживает 443).** Сохранить текущий конфиг владельцем. Сначала добавить только
HTTP server block из `deploy/randomayzer.nginx.conf` в подключаемый sites directory, создать
`/var/www/randomayzer-acme`, проверить `sudo nginx -t` и выполнить `sudo systemctl reload nginx`.
Ожидается, что app опубликовал только `127.0.0.1:3100` и `10.77.0.3:3100`; DB по-прежнему без host binding.
Если Docker показывает `0.0.0.0`, другой host address или DB port, немедленно вернуть loopback-only профиль и
не продолжать. На VPS до reload прокси проверить новый WG upstream:
```bash
sudo certbot certonly --webroot -w /var/www/randomayzer-acme -d random.ochenstarik.ru
sudo htpasswd -c /etc/nginx/randomayzer.htpasswd owner
sudo chmod 640 /etc/nginx/randomayzer.htpasswd
# Владелец/группа файла должны позволять только nginx worker читать hash.
curl --connect-timeout 5 --max-time 15 --fail --silent --show-error http://10.77.0.3:3100/api/auth/me
```
Пароль вводится интерактивно, не через аргументы/логи. Certbot должен сообщить успешную выдачу.
Теперь добавить HTTPS server block из snippet. `sudo nginx -t`, затем `sudo systemctl reload nginx`.
Проверить существующий certbot renewal timer и `sudo certbot renew --dry-run` (не перезапускать Forgejo).
Basic auth применяется также к callback; браузер владельца уже авторизован на том же origin.
Ожидается JSON `authenticated: false`, `user: null`; это проверяет путь до приложения, не внешний TLS/login.
**Caddy (только если он уже обслуживает 443).** Выполнить `caddy hash-password` интерактивно, вставить hash
в новый отдельный site block `deploy/randomayzer.Caddyfile`, хранить конфиг с доступом только оператора/Caddy.
Добавить block в существующий Caddyfile, сохранить старую копию; выполнить
`sudo caddy validate --config /etc/caddy/Caddyfile`, затем `sudo systemctl reload caddy`.
TLS-ALPN отключён в этом site; ACME использует HTTP-01. Проверить автоматическое renewal в существующем Caddy.
Фактическая версия Caddy и конфиг валидируются на VPS; если validate отвергает директиву — не reload.
До редактирования прочитать 6.4 и выполнить его read-only preflight; определить функцию reload в той же
root shell. Не начинать изменение, если предусловия стандартного master-worker варианта не выполнены и
не существует другого уже документированного и проверенного оператором механизма. Команда reload при
этом ещё не выполняется.
Оба snippets переписывают Host/X-Forwarded-* (не дописывают к client-supplied chain), убирают Basic Authorization
перед upstream, задают 1MB body limit и таймауты. Не включать access log с callback query/cookie.
Приложение запускается **только** через `tools/serve-production.cjs`: он проверяет socket.remoteAddress,
доверяет forwarded IP/proto только `10.77.0.1`, удаляет все входящие proxy headers остальных клиентов.
Обычный `next start` не является production entry point этих compose-профилей.
### 6.2. Базовый снимок и резервная копия обоих конфигов
### Приёмка и откат R5
С внешней машины:
До редактирования сохранить ответы всех уже обслуживаемых сайтов по обоим публичным входам. Адреса listener
80 и 443 оператор находит в `ss`; вводить конкретный локальный адрес listener, например `127.0.0.1`, никогда
`0.0.0.0`. Перечислить в `BASELINE_HOSTS` все имена из текущих HAProxy/Caddy routes, включая
`git.ochenstarik.ru`. Не включать URL, query, заголовки авторизации или bodies:
```bash
curl -s -o /dev/null -w '%{http_code}\n' https://random.ochenstarik.ru/
# ожидается 401 без basic auth
curl -u owner --fail --silent https://random.ochenstarik.ru/api/auth/me
# пароль вводится curl интерактивно; ожидается JSON user:null
curl --connect-timeout 5 http://178.212.13.102:3100/
# timeout/refused; порт НЕ должен отвечать
read -r -p 'Local HTTP listener IP for port 80 from ss: ' HTTP_LISTENER_IP
read -r -p 'Local TLS listener IP for port 443 from ss: ' TLS_LISTENER_IP
BASELINE_HOSTS=(git.ochenstarik.ru) # дописать все остальные текущие домены из proxy-конфигов
for host in "${BASELINE_HOSTS[@]}"; do
curl --noproxy '*' --connect-timeout 5 --max-time 15 --silent --show-error \
--output /dev/null --write-out "$host http %{http_code} %{remote_ip}\n" \
--resolve "$host:80:$HTTP_LISTENER_IP" "http://$host/" || { echo "STOP: HTTP baseline failed for $host" >&2; exit 1; }
curl --noproxy '*' --connect-timeout 5 --max-time 15 --silent --show-error \
--output /dev/null --write-out "$host https %{http_code} %{remote_ip}\n" \
--resolve "$host:443:$TLS_LISTENER_IP" "https://$host/" || { echo "STOP: HTTPS baseline failed for $host" >&2; exit 1; }
done
```
С LAN другой машины `curl --connect-timeout 5 http://HOME_LAN_IP:3100/` — refused/timeout.
С другого WG peer запрос к 10.77.0.3:3100 — timeout. С VPS запрос к нему — успешен.
Браузером проверить сертификат, basic auth, VK login, `/api/auth/me`, Secure/HttpOnly cookie, A–H smoke.
Записать фактические результаты/дату/SHA в `agents/claude/done/`, без секретных ответов.
До этого сертификат, 401 на публичном домене, live VK и изоляция на стенде — **UNVERIFIED**.
Записать именно выведенные HTTP и HTTPS status codes/адреса в operator change record. Для случайного сайта до
добавления route допустим существующий default response; не угадывать ожидаемый статус. Baseline Forgejo и
каждого другого хоста по обоим протоколам после reload должен совпасть с его собственным доизменительным
результатом.
Откат: удалить только добавленный vhost, проверить существующий конфиг и reload соответствующего сервера.
На домашнем сервере `ry up -d --wait app` вернёт loopback-only профиль. Сначала проверить исчезновение WG binding,
затем удалить только наши firewall entries:
По mounts определить host source и container path каждого активного Caddy/HAProxy config-файла и всех
подключаемых конфигов. Если конфиг генерируется entrypoint/Compose, работать через его подтверждённый
source/deployment mechanism, не править сгенерированный файл. Создать root-only каталог резервной копии и
снять копии как активных файлов из контейнеров, так и source деревьев конфигурации:
```bash
sudo iptables -D DOCKER-USER -p tcp -m conntrack --ctorigdst 10.77.0.3 --ctorigdstport 3100 -j RANDOMAYZER-WG
sudo iptables -D INPUT -p tcp -d 10.77.0.3 --dport 3100 -j RANDOMAYZER-WG
sudo iptables -F RANDOMAYZER-WG
sudo iptables -X RANDOMAYZER-WG
umask 077
BACKUP_DIR="/root/randomayzer-proxy-backup-$(date -u +%Y%m%dT%H%M%SZ)"
install -d -m 0700 "$BACKUP_DIR"
# Задать пути только после inspection. Если mount содержит include-файлы, копировать всё config-дерево.
CADDY_ACTIVE_CONFIG=/path/confirmed/in/container/Caddyfile
CADDY_HOST_SOURCE=/path/confirmed/on/host/Caddyfile
HAPROXY_ACTIVE_CONFIG=/path/confirmed/in/container/haproxy.cfg
HAPROXY_HOST_SOURCE=/path/confirmed/on/host/haproxy.cfg
docker cp "krona-reverse-proxy:$CADDY_ACTIVE_CONFIG" "$BACKUP_DIR/caddy-active"
docker cp "krona-proxy-mux:$HAPROXY_ACTIVE_CONFIG" "$BACKUP_DIR/haproxy-active"
cp -a -- "$CADDY_HOST_SOURCE" "$BACKUP_DIR/caddy-source"
cp -a -- "$HAPROXY_HOST_SOURCE" "$BACKUP_DIR/haproxy-source"
chmod -R go-rwx "$BACKUP_DIR"
cmp -s -- "$CADDY_HOST_SOURCE" "$BACKUP_DIR/caddy-source" || { echo 'STOP: Caddy source backup incomplete'; exit 1; }
cmp -s -- "$HAPROXY_HOST_SOURCE" "$BACKUP_DIR/haproxy-source" || { echo 'STOP: HAProxy source backup incomplete'; exit 1; }
CADDY_INODE_BEFORE=$(stat -Lc '%d:%i' "$CADDY_HOST_SOURCE")
HAPROXY_INODE_BEFORE=$(stat -Lc '%d:%i' "$HAPROXY_HOST_SOURCE")
cmp -s -- "$CADDY_HOST_SOURCE" "$BACKUP_DIR/caddy-active" || { echo 'STOP: Caddy active/source differ'; exit 1; }
cmp -s -- "$HAPROXY_HOST_SOURCE" "$BACKUP_DIR/haproxy-active" || { echo 'STOP: HAProxy active/source differ'; exit 1; }
sha256sum "$CADDY_HOST_SOURCE" "$HAPROXY_HOST_SOURCE" "$BACKUP_DIR/caddy-active" "$BACKUP_DIR/haproxy-active"
```
Владелец также убирает эти entries из persistence своего firewall manager. DB volume, чужие vhosts и tunnel сохраняются.
Для directory mounts сделать отдельные backups всего mounted config tree, включая imports (задать подтверждённые
пути directory mounts из `docker inspect`):
```bash
CADDY_CONFIG_DIR=/confirmed/container/config-dir
CADDY_HOST_CONFIG_DIR=/confirmed/host/config-dir
HAPROXY_CONFIG_DIR=/confirmed/container/config-dir
HAPROXY_HOST_CONFIG_DIR=/confirmed/host/config-dir
install -d -m 0700 "$BACKUP_DIR/caddy-tree-active" "$BACKUP_DIR/caddy-tree-source"
install -d -m 0700 "$BACKUP_DIR/haproxy-tree-active" "$BACKUP_DIR/haproxy-tree-source"
docker cp "krona-reverse-proxy:$CADDY_CONFIG_DIR/." "$BACKUP_DIR/caddy-tree-active"
docker cp "krona-proxy-mux:$HAPROXY_CONFIG_DIR/." "$BACKUP_DIR/haproxy-tree-active"
cp -a -- "$CADDY_HOST_CONFIG_DIR/." "$BACKUP_DIR/caddy-tree-source/"
cp -a -- "$HAPROXY_HOST_CONFIG_DIR/." "$BACKUP_DIR/haproxy-tree-source/"
chmod -R go-rwx "$BACKUP_DIR"
# Любой неполный copy/mismatch — STOP до редактирования. Не выводить содержимое/дифф конфигов.
for tree in caddy-tree-active caddy-tree-source haproxy-tree-active haproxy-tree-source; do
test -n "$(find "$BACKUP_DIR/$tree" -type f -print -quit)" || { echo 'STOP: empty config-tree backup'; exit 1; }
done
diff -qr -- "$CADDY_HOST_CONFIG_DIR" "$BACKUP_DIR/caddy-tree-source" >/dev/null || { echo 'STOP: Caddy source backup differs'; exit 1; }
diff -qr -- "$HAPROXY_HOST_CONFIG_DIR" "$BACKUP_DIR/haproxy-tree-source" >/dev/null || { echo 'STOP: HAProxy source backup differs'; exit 1; }
diff -qr -- "$BACKUP_DIR/caddy-tree-active" "$BACKUP_DIR/caddy-tree-source" >/dev/null || { echo 'STOP: Caddy active/source tree differs'; exit 1; }
diff -qr -- "$BACKUP_DIR/haproxy-tree-active" "$BACKUP_DIR/haproxy-tree-source" >/dev/null || { echo 'STOP: HAProxy active/source tree differs'; exit 1; }
```
Сверить хеши активных файлов с source до изменения. Если active path или source не доказан, остановиться. Резервная копия может
содержать proxy credentials и Caddy basic-auth hash: оставить её только в каталоге `0700`, не коммитить,
не отправлять в чат/логи и удалить штатной процедурой владельца после принятия изменения.
### 6.3. Валидация до и после изменения
Сначала текущие конфиги должны проходить те же checks до редактирования:
```bash
docker exec krona-reverse-proxy caddy validate \
--config "$CADDY_ACTIVE_CONFIG" --adapter caddyfile
docker exec krona-proxy-mux haproxy -c -V -f "$HAPROXY_ACTIVE_CONFIG"
```
Если HAProxy стартует с несколькими `-f`, передать в проверку все активные конфиги в том же порядке.
Ожидается `Valid configuration` от Caddy и exit code 0 от HAProxy (`-V` печатает версию; HAProxy может не
печатать отдельную строку успеха). Любая ошибка до изменения —
стоп, не пытаться «починить» baseline конфигурацию в рамках Randomayzer.
Смонтированный **одиночный файл** может оставаться старым inode при atomic rename/`mv`/некоторых редакторах;
Docker продолжит показывать старое содержимое. Смонтированный каталог имеет другую семантику. Поэтому сначала
определить type mount и текущие file inode/hash. Изменять только подтверждённый host source существующим
способом деплоя. Для single-file bind mount сохранять inode: не использовать `sed -i`, `mv`, `sudoedit` или
full-file copy-over. После правки повторить stat/hash checks, убедиться что device/inode остались прежними,
а host source и active container content совпали. Для directory mount также сверить итоговые хеши из контейнера.
При несовпадении ничего не reload;
сначала вернуть подтверждённый source и добиться совпадения.
Не заменять active Caddyfile/HAProxy config целиком шаблонами из репозитория. Вставить только новый Caddy site
block из `deploy/randomayzer.Caddyfile` в активный Caddyfile или уже подключённый fragments directory. Если на
VPS установлен Vim и подтверждённый source — обычный файл, редактировать так, чтобы сохранить inode:
```bash
sudo vim -c 'set backupcopy=yes' "$CADDY_HOST_SOURCE"
sudo vim -c 'set backupcopy=yes' "$HAPROXY_HOST_SOURCE"
```
В редакторе добавить только доменный block/правила из snippet в правильные места, проверить diff локально,
сохранить, затем выполнить host/container inode/hash checks ниже. Если Vim отсутствует, файл — symlink,
конфигурация генерируется или source read-only, остановиться и применить подтверждённый deployment mechanism;
не переключаться на `sed -i`/`mv`/atomic rename. Интерактивно
создать hash: `docker exec -it krona-reverse-proxy caddy hash-password`; значение hash поместить только в
новый локальный конфиг. Не писать пароль/hash в CLI args, shell history или Git. Выбран Basic Auth; Authelia
не подключать без подтверждения действующих access-control policy и identity headers. Для Caddy body limit —
1 MB; access log для этого сайта не добавлять, так как OAuth callback может нести чувствительные query values.
HAProxy snippet `deploy/randomayzer.haproxy.cfg` содержит **только примеры вставок**, а не полный конфиг.
Порядок активации — HTTP-01 при необходимости, затем защищённый Caddy site, затем новый TLS SNI route.
Это три отдельных этапа; не публиковать новый TLS route до успешного Caddy reload с Basic Auth.
Если текущий порт 80 уже передаёт challenge для этого Host к Caddy, оставить его без изменения. Иначе,
только для подтверждённого `mode http` frontend, сначала добавить **лишь** HTTP-01 ACL/route из snippet к
существующему cleartext Caddy HTTP backend: точный Host **и** path prefix `/.well-known/acme-challenge/`.
Не использовать TLS/443 backend, не менять другие HTTP routes, не добавлять новый listener и не добавлять
пока TLS SNI ACL. Провалидировать полный HAProxy config, выполнить подтверждённый reload из 6.4 и повторить
other-site baseline. Этот этап делает доступным только challenge path; новый TLS route ещё отсутствует.
Если существующий HTTP mode/route/backend не установлен, остановиться — не угадывать топологию.
Теперь добавить Caddy block с Basic Auth, проверить source/active inode/hash и `caddy validate`, затем
выполнить только Caddy reload из 6.4. Успешный reload доказывает загрузку auth-конфига; выдача сертификата
проверяется отдельно. После успешного Caddy reload, если и только если frontend 443 — TCP TLS passthrough,
добавить точный SNI ACL/`use_backend` к существующему Caddy TLS backend после явных маршрутов других доменов
и перед catch-all. Другие rules/default backend не менять. Повторить source/active checks и полный
`haproxy -c -V`, затем graceful reload HAProxy из 6.4 и baseline всех сайтов. Не добавлять Authelia без
подтверждённого policy. Для каждого из этапов применяются checks ниже; не пропускать их при повторном reload.
После вносимых дополнений ещё раз проверить хеши и оба конфига:
```bash
sha256sum "$CADDY_HOST_SOURCE" "$HAPROXY_HOST_SOURCE"
docker exec krona-reverse-proxy sha256sum "$CADDY_ACTIVE_CONFIG"
docker exec krona-proxy-mux sha256sum "$HAPROXY_ACTIVE_CONFIG"
test "$CADDY_INODE_BEFORE" = "$(stat -Lc '%d:%i' "$CADDY_HOST_SOURCE")" || { echo 'STOP: Caddy source inode changed'; exit 1; }
test "$HAPROXY_INODE_BEFORE" = "$(stat -Lc '%d:%i' "$HAPROXY_HOST_SOURCE")" || { echo 'STOP: HAProxy source inode changed'; exit 1; }
docker cp "krona-reverse-proxy:$CADDY_ACTIVE_CONFIG" "$BACKUP_DIR/caddy-active-after"
docker cp "krona-proxy-mux:$HAPROXY_ACTIVE_CONFIG" "$BACKUP_DIR/haproxy-active-after"
cmp -s -- "$CADDY_HOST_SOURCE" "$BACKUP_DIR/caddy-active-after" || { echo 'STOP: Caddy host/active differ'; exit 1; }
cmp -s -- "$HAPROXY_HOST_SOURCE" "$BACKUP_DIR/haproxy-active-after" || { echo 'STOP: HAProxy host/active differ'; exit 1; }
docker exec krona-reverse-proxy caddy validate \
--config "$CADDY_ACTIVE_CONFIG" --adapter caddyfile
docker exec krona-proxy-mux haproxy -c -V -f "$HAPROXY_ACTIVE_CONFIG"
```
Ожидается одинаковый source/active hash для каждого файла плюс успешные Caddy/HAProxy checks. Если include
files edited, сверить и провалидировать весь config set. Не reload при любом mismatch/error.
### 6.4. Условные команды reload без container restart
**До изменения** подтвердить, что PID1 именно `haproxy`, в его действующих argv есть отдельный `-W`/`-Ws`,
а все `-f` paths соответствуют полному config set, проверяемому в 6.3. Этот стандартный вариант проверен
локально на official `haproxy:3.0-alpine`, но не доказывает process model VPS. При wrapper/tini, другом PID1,
отсутствии flags или неизвестном config set — STOP, не посылать сигнал и не менять процессную модель.
Read-only preflight (выполняется до 6.2):
```bash
docker exec krona-proxy-mux sh -c 'test "$(cat /proc/1/comm)" = haproxy && tr "\000" "\n" < /proc/1/cmdline | grep -Eq "^-W(s)?$"' || { echo 'STOP: unconfirmed HAProxy master-worker PID1'; exit 1; }
# Сверить каждый -f с обнаруженными active paths, не угадывать значения.
docker exec krona-proxy-mux sh -c 'tr "\000" "\n" < /proc/1/cmdline'
```
Для подтверждённого выше PID1=master определить функции в той же root shell. Они не запускают reload до
явного вызова. Проверка generation не заменяет последующие probes всех сайтов и нового route.
```bash
haproxy_worker_pids() {
docker exec "$1" sh -c 'for pid in $(cat /proc/1/task/1/children); do if [ "$(cat /proc/$pid/comm 2>/dev/null)" = haproxy ]; then printf "%s " "$pid"; fi; done'
}
reload_haproxy_without_restart() {
local container="$1" before after old_workers new_workers pid attempt handoff
docker exec "$container" sh -c 'test "$(cat /proc/1/comm)" = haproxy && tr "\000" "\n" < /proc/1/cmdline | grep -Eq "^-W(s)?$"' || return 1
before=$(docker inspect --format '{{.Id}} {{.State.StartedAt}} {{.RestartCount}}' "$container") || return 1
old_workers=$(haproxy_worker_pids "$container") || return 1
test -n "$old_workers" || return 1
# USR2 targets confirmed PID1 master. This is NOT docker restart/kill default signal.
docker kill --signal=USR2 "$container" >/dev/null || return 1
handoff=no
for attempt in 1 2 3 4 5 6 7 8 9 10; do
new_workers=$(haproxy_worker_pids "$container") || return 1
for pid in $new_workers; do
case " $old_workers " in *" $pid "*) ;; *) handoff=yes ;; esac
done
test "$handoff" = yes && break
sleep 1
done
after=$(docker inspect --format '{{.Id}} {{.State.StartedAt}} {{.RestartCount}}' "$container") || return 1
test "$before" = "$after" && test "$handoff" = yes || return 1
test "$(docker inspect --format '{{.State.Running}}' "$container")" = true || return 1
printf 'HAProxy worker handoff observed; container identity/start/restart count unchanged\n'
}
```
После **каждого** полного validation/source-match gate из 6.3 активировать только соответствующий этап:
```bash
# Сначала только если нужен новый HTTP-01 route; затем повторить этот вызов для финального TLS SNI route.
reload_haproxy_without_restart krona-proxy-mux || { echo 'STOP: HAProxy handoff failed; use rollback 6.6'; exit 1; }
```
Между HTTP-01 и TLS этапами загрузить Caddy Basic Auth/site (без container restart):
```bash
CADDY_CONTAINER_BEFORE=$(docker inspect --format '{{.Id}} {{.State.StartedAt}} {{.RestartCount}}' krona-reverse-proxy)
docker exec krona-reverse-proxy caddy reload --config "$CADDY_ACTIVE_CONFIG" --adapter caddyfile || { echo 'STOP: Caddy reload failed; use rollback 6.6'; exit 1; }
test "$CADDY_CONTAINER_BEFORE" = "$(docker inspect --format '{{.Id}} {{.State.StartedAt}} {{.RestartCount}}' krona-reverse-proxy)" || { echo 'STOP: Caddy container identity changed'; exit 1; }
```
Если Caddy admin API выключен/имеет другой адрес — STOP; использовать только заранее подтверждённый admin
endpoint и штатный reload, не сигнал/restart как обход. Если worker handoff не подтверждён или service probes
изменились, не повторять USR2 бесконечно: восстановить private backup, validate и применить ту же проверенную
reload-процедуру по 6.6. При неизвестном process model оставить конфиги неизменными и передать blocker владельцу.
### 6.5. После reload и приёмка
Повторить baseline loop из 6.2 с теми же `BASELINE_HOSTS` и адресами listener 80/443. Каждый существующий сайт
должен вернуть прежний status code по обоим протоколам и ответить за 15 секунд; минимум Forgejo
`git.ochenstarik.ru` обязателен. Иные
результаты — немедленный rollback.
После подтверждения DNS и внешнего маршрута:
```bash
curl --connect-timeout 5 --max-time 15 --silent --show-error \
--output /dev/null --write-out '%{http_code}\n' https://random.ochenstarik.ru/
```
Ожидается `401` без Basic Auth. Для защищённой проверки `/api/auth/me` использовать интерактивный prompt:
```bash
curl --connect-timeout 5 --max-time 15 --user owner --fail --silent --show-error \
https://random.ochenstarik.ru/api/auth/me
```
Не указывать пароль в команде. Ожидается безопасный JSON с `authenticated: false` и `user: null` до входа.
Проверить TLS-сертификат извне, ручное VK consent/login и callback в браузере. Путь HTTP-01 удостоверяется
фактической выдачей/renewal сертификата через Caddy; `caddy validate` сам по себе выдачу не подтверждает.
Проверить с домашнего сервера, что порт приложения доступен с VPS через WG, но недоступен с LAN; с внешней
машины и неразрешённого WG peer он также недоступен. Не тестировать порт через интернет, если публичный
bind/firewall ещё не подтверждены. Порт БД не должен появиться. Записать наблюдения/даты/SHA в операторский
отчёт, без secret/config/body/log dumps. Сертификат, 401, VK login, source identity и firewall isolation до
фактического стенда остаются `UNVERIFIED`.
### 6.6. Откат без воздействия на остальные сервисы
Если reload не прошёл, TLS/HTTP-01 не работает, Randomayzer не отвечает или любой предыдущий сайт изменил
результат: прекратить дальнейшие проверки. На host source восстановить только изменённые config-файлы из
private backup, записывая в существующий inode (`cat backup-file > existing-mounted-file` после проверки
пути/hash), либо применить подтверждённый штатный config deployment. Не `mv`/atomic replace single-file bind
mount. Не восстанавливать/перезаписывать весь proxy config tree поверх чужих параллельных изменений. Проверить
source и active hashes, затем Caddy `validate` и HAProxy `-c` теми же путями, что выше.
Загрузить восстановленный Caddy config его reload-командой, затем вызвать `reload_haproxy_without_restart krona-proxy-mux` с теми же предусловиями; обе операции выполняются без рестарта и проверить Caddy/HAProxy container ID/uptime,
затем повторить исходные other-site baselines. Ожидается прежний baseline для Forgejo и каждого перечисленного
домена; Randomayzer снова не должен попадать на новый route. Если безопасный HAProxy reload не найден или
baseline чужого сайта не восстановился, не перезапускать общий proxy: оставить его работающим в доступном
состоянии и немедленно передать владельцу backup, фактический config path, validation output и точную ошибку
без секретов. На домашнем сервере переключить только Randomayzer в loopback-only профиль и удалить только
его добавленные firewall rules через тот manager, которым они были установлены. Существующие containers,
volumes, sites, Authelia, Forgejo, fleet-hub и WireGuard сохраняются.
## Источники конфигурации
- [Next.js custom server](https://nextjs.org/docs/app/guides/custom-server) — socket-aware ingress перед Next.
- [Docker Compose services](https://docs.docker.com/reference/compose-file/services/) — exact host port binding и resource limits.
- [nginx proxy module](https://nginx.org/en/docs/http/ngx_http_proxy_module.html) — proxy_bind и overwrite headers.
- [Caddy reverse_proxy](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy), [TLS](https://caddyserver.com/docs/caddyfile/directives/tls).
- [Docker Compose services](https://docs.docker.com/reference/compose-file/services/) — host bindings и resource limits.
- [HAProxy 3.0 Configuration Manual](https://docs.haproxy.org/3.0/configuration.html) — TCP inspection, SNI sample fetches, ACL/use_backend and validation options. Использовать семантику для фактической версии 3.0.x; локальный template остаётся примером.
- [Caddy `basic_auth`](https://caddyserver.com/docs/caddyfile/directives/basic_auth) — хеширование пароля; директива переименована в `basic_auth` до версии 2.8.
- [Caddy `reverse_proxy`](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy) — upstream и HTTP transport timeouts.
- [Caddy automatic HTTPS](https://caddyserver.com/docs/automatic-https) — HTTP-01/redirect/certificate behavior. Доступность пути подтверждается реальной конфигурацией/сертификатом, не статическим предположением.
- [Caddy command line](https://caddyserver.com/docs/command-line) — `validate` и `reload`; admin endpoint и успешный reload должны быть подтверждены на существующем контейнере.
- [Docker inspect](https://docs.docker.com/reference/cli/docker/container/inspect/) and [Docker cp](https://docs.docker.com/reference/cli/docker/container/cp/) — узкий вывод конфигурации контейнера, mounts и резервное копирование. Никогда не выводить `.Config.Env`.
- [HAProxy 3.0 Management Guide](https://docs.haproxy.org/3.0/management.html) — `-W`/`-Ws`, SIGUSR2 для master и exit status `-c`/`-V`. [Official Docker entrypoint](https://github.com/docker-library/haproxy/blob/master/3.0/alpine/docker-entrypoint.sh) — пример образа; действующий PID1/argv VPS всё равно проверяется отдельно.