Add site credential lifecycle #169

Merged
vnprc merged 4 commits from agent/site-credential-lifecycle into agent/site-config-path 2026-09-09 20:14:55 +01:00
Member

Site credentials can now be checked against the server without building or publishing a site.
The owner can inspect safe fields, update only the host, user, or password, or explicitly replace all three.
A rejected FTP login says username or password because the protocol cannot identify which value is wrong.
Existing deploy behavior, hosting layouts, default config resolution, and the compatible --force spelling stay available.
Focused lifecycle tests and the full tagged and untagged Go test and vet matrix prove the new paths.
If this is wrong, revert this PR; existing rclone configuration files remain valid and usable.

Closes allod/tools#163

Contract

  • allod site check [--config <path>] reuses the deploy preflight's read-only lsd shared: probe and failure reporter. It does not resolve a site repository, build, sync, verify HTTPS, or publish.
  • allod site config show [--config <path>] prints Config, Remote, Type, Host, and User only. It rejects missing or duplicate required keys, unsafe terminal text, non-FTP remotes, and configurations without explicit TLS; it never prints the plaintext-equivalent password field.
  • allod site config update {host|user|password} [--config <path>] accepts no value in argv, asks exactly one terminal question, and replaces only that field's byte range. Both rclone-supported key = value and key: value separators work, while other formatting, directives, line endings, and stanzas survive unchanged.
  • allod site config replace [--config <path>] is the discoverable full replacement. allod site config --force remains its compatibility spelling, while a bare allod site config keeps create-and-refuse-existing behavior. Replacement refuses duplicate shared stanzas before prompting or writing rather than reporting success over an ambiguous credential.
  • Read-only check/show may follow an activation symlink to a regular file. Create and replace accept a missing path; update requires an existing shared stanza. All mutable actions accept a regular file and refuse symlinks and non-regular files before prompting. A rejected credential selected through a symlink, whether explicitly or through rclone's default resolution, points back to its source credential rather than recommending an impossible mutation of the symlink.
  • Successful create, update, and replacement output points to the available allod site check command. Named-path output tells the owner to reuse the reported Config path with --config without emitting an unquoted pseudo-command.

Risk

Medium. These commands write a machine-wide, plaintext-equivalent rclone configuration. The write remains atomic at mode 0600, password input still reaches rclone only on stdin, and mutable operations reuse the named-config file-type protections from the parent PR. The remaining operational risk is the narrow filesystem race after the writer's final destination recheck; ordinary symlink and non-regular destinations are refused.

Validation

  • nix develop -c bash -lc 'go test -tags site ./cmd/allod -run "TestSite(Check|Config|ReportSiteRemoteFailure)" -count=1'
  • nix develop -c bash -lc 'go test ./... && go vet ./... && go vet -tags site ./... && go test -tags site ./...'
  • gofmt -d on all four changed Go files and git diff --check
  • Focused review regressions cover duplicate replacement through both spellings, mixed =/: parsing and exact byte preservation, explicit and default symlink-safe credential-rejection guidance, and the update precondition in help.
  • nix flake check reaches the existing unrelated allod-parity failure at test 23: tests/pr-explain/components.sh expects the section prompt to contain not decoration. The lifecycle and full Go matrices above pass.

Files

  • cmd/allod/site.go
  • cmd/allod/site_config.go
  • cmd/allod/site_test.go
  • cmd/allod/site_config_test.go

Depends on: #168

Site credentials can now be checked against the server without building or publishing a site. The owner can inspect safe fields, update only the host, user, or password, or explicitly replace all three. A rejected FTP login says username or password because the protocol cannot identify which value is wrong. Existing deploy behavior, hosting layouts, default config resolution, and the compatible `--force` spelling stay available. Focused lifecycle tests and the full tagged and untagged Go test and vet matrix prove the new paths. If this is wrong, revert this PR; existing rclone configuration files remain valid and usable. Closes allod/tools#163 ## Contract - `allod site check [--config <path>]` reuses the deploy preflight's read-only `lsd shared:` probe and failure reporter. It does not resolve a site repository, build, sync, verify HTTPS, or publish. - `allod site config show [--config <path>]` prints Config, Remote, Type, Host, and User only. It rejects missing or duplicate required keys, unsafe terminal text, non-FTP remotes, and configurations without explicit TLS; it never prints the plaintext-equivalent password field. - `allod site config update {host|user|password} [--config <path>]` accepts no value in argv, asks exactly one terminal question, and replaces only that field's byte range. Both rclone-supported `key = value` and `key: value` separators work, while other formatting, directives, line endings, and stanzas survive unchanged. - `allod site config replace [--config <path>]` is the discoverable full replacement. `allod site config --force` remains its compatibility spelling, while a bare `allod site config` keeps create-and-refuse-existing behavior. Replacement refuses duplicate `shared` stanzas before prompting or writing rather than reporting success over an ambiguous credential. - Read-only check/show may follow an activation symlink to a regular file. Create and replace accept a missing path; update requires an existing `shared` stanza. All mutable actions accept a regular file and refuse symlinks and non-regular files before prompting. A rejected credential selected through a symlink, whether explicitly or through rclone's default resolution, points back to its source credential rather than recommending an impossible mutation of the symlink. - Successful create, update, and replacement output points to the available `allod site check` command. Named-path output tells the owner to reuse the reported Config path with `--config` without emitting an unquoted pseudo-command. ## Risk Medium. These commands write a machine-wide, plaintext-equivalent rclone configuration. The write remains atomic at mode 0600, password input still reaches rclone only on stdin, and mutable operations reuse the named-config file-type protections from the parent PR. The remaining operational risk is the narrow filesystem race after the writer's final destination recheck; ordinary symlink and non-regular destinations are refused. ## Validation - `nix develop -c bash -lc 'go test -tags site ./cmd/allod -run "TestSite(Check|Config|ReportSiteRemoteFailure)" -count=1'` - `nix develop -c bash -lc 'go test ./... && go vet ./... && go vet -tags site ./... && go test -tags site ./...'` - `gofmt -d` on all four changed Go files and `git diff --check` - Focused review regressions cover duplicate replacement through both spellings, mixed `=`/`:` parsing and exact byte preservation, explicit and default symlink-safe credential-rejection guidance, and the update precondition in help. - `nix flake check` reaches the existing unrelated `allod-parity` failure at test 23: `tests/pr-explain/components.sh` expects the section prompt to contain `not decoration`. The lifecycle and full Go matrices above pass. ## Files - `cmd/allod/site.go` - `cmd/allod/site_config.go` - `cmd/allod/site_test.go` - `cmd/allod/site_config_test.go` Depends on: https://forge.anarch.diy/allod/tools/pulls/168
Author
Member

Independent review found three lifecycle defects and one help-text defect. Please fix these additively before merge; the focused and full Go matrices otherwise pass.

  • P1: replace/compatible --force replaces only the first [shared] stanza. Rclone 1.72.1 accepts duplicate sections and later credential keys win, so the command can report success while rclone keeps using an untouched later password. Reject duplicate owned stanzas before prompting or writing, and test no mutation.
  • P2: credential rejection for --config <symlink> recommends update with that same path, but read-only symlinks are intentionally accepted by check/show and refused by every mutable lifecycle command. Direct the owner to the activation/source credential rather than an impossible mutation of the selected symlink.
  • P2: show/update recognize only key = value, while rclone 1.72.1 also accepts key: value. Support the accepted colon separator with exact byte-range preservation (including mixed syntax), or diagnose it explicitly; do not report valid required keys as missing.
  • P3: help says create, update, and replace may create a missing path, but update requires an existing [shared] stanza. Narrow that wording to create/replace and state update’s precondition.
Independent review found three lifecycle defects and one help-text defect. Please fix these additively before merge; the focused and full Go matrices otherwise pass. - P1: replace/compatible `--force` replaces only the first `[shared]` stanza. Rclone 1.72.1 accepts duplicate sections and later credential keys win, so the command can report success while rclone keeps using an untouched later password. Reject duplicate owned stanzas before prompting or writing, and test no mutation. - P2: credential rejection for `--config <symlink>` recommends update with that same path, but read-only symlinks are intentionally accepted by check/show and refused by every mutable lifecycle command. Direct the owner to the activation/source credential rather than an impossible mutation of the selected symlink. - P2: show/update recognize only `key = value`, while rclone 1.72.1 also accepts `key: value`. Support the accepted colon separator with exact byte-range preservation (including mixed syntax), or diagnose it explicitly; do not report valid required keys as missing. - P3: help says create, update, and replace may create a missing path, but update requires an existing `[shared]` stanza. Narrow that wording to create/replace and state update’s precondition.
Author
Member

Addressed in 344f837.

  • Replacement and the compatible --force spelling now reject duplicate [shared] stanzas before any prompt, obscuring call, or write.
  • Show and single-field updates accept mixed rclone = and : separators; update still replaces only the selected value bytes.
  • A rejected credential selected through a read-only symlink now directs the owner to its source credential and uses the same selected path only for the follow-up check.
  • Help now says only create and replace may create a missing path, and that update requires an existing shared stanza.

Focused lifecycle tests, the full tagged and untagged Go test/vet matrix, gofmt -d, and git diff --check pass at this head.

Addressed in `344f837`. - Replacement and the compatible `--force` spelling now reject duplicate `[shared]` stanzas before any prompt, obscuring call, or write. - Show and single-field updates accept mixed rclone `=` and `:` separators; update still replaces only the selected value bytes. - A rejected credential selected through a read-only symlink now directs the owner to its source credential and uses the same selected path only for the follow-up check. - Help now says only create and replace may create a missing path, and that update requires an existing `shared` stanza. Focused lifecycle tests, the full tagged and untagged Go test/vet matrix, `gofmt -d`, and `git diff --check` pass at this head.
Author
Member

One P2 remediation gap remains for an implicitly resolved symlink.

rcloneConfigSelection.configPath() returns "" when the caller omits --config, and site check carries that empty value into reportSiteRemoteFailure. The credential-rejected branch therefore recommends allod site config update user|password without checking whether rclone's default path (including RCLONE_CONFIG) is a symlink. That update then resolves the default path and refuses it through the mutable O_NOFOLLOW path. An activation-managed default symlink thus receives the same impossible mutation remedy fixed here for explicit --config <symlink>.

Please make the default rejection guidance mention updating the activation/source credential when applicable, or safely carry enough resolved-path metadata to choose the symlink-specific remedy. The focused duplicate-section, mixed-separator/byte-preservation, explicit-symlink, help, and secret/argv tests otherwise pass at 344f837.

One P2 remediation gap remains for an implicitly resolved symlink. `rcloneConfigSelection.configPath()` returns `""` when the caller omits `--config`, and `site check` carries that empty value into `reportSiteRemoteFailure`. The credential-rejected branch therefore recommends `allod site config update user|password` without checking whether rclone's default path (including `RCLONE_CONFIG`) is a symlink. That update then resolves the default path and refuses it through the mutable `O_NOFOLLOW` path. An activation-managed default symlink thus receives the same impossible mutation remedy fixed here for explicit `--config <symlink>`. Please make the default rejection guidance mention updating the activation/source credential when applicable, or safely carry enough resolved-path metadata to choose the symlink-specific remedy. The focused duplicate-section, mixed-separator/byte-preservation, explicit-symlink, help, and secret/argv tests otherwise pass at `344f837`.
Author
Member

Addressed in a61b3df.

Credential-rejection reporting now checks the path selected by rclone's implicit precedence: RCLONE_CONFIG first, then the XDG/HOME default. When that path is a symlink, the remedy points to the source credential and recommends only rerunning the implicit allod site check; it does not print the resolved path or recommend site config update/--config against the read-only link.

Focused regressions cover both an RCLONE_CONFIG symlink and an XDG-default symlink. The focused lifecycle tests, full tagged and untagged Go test/vet matrix, gofmt -d, and git diff --check pass at this head.

Addressed in `a61b3df`. Credential-rejection reporting now checks the path selected by rclone's implicit precedence: `RCLONE_CONFIG` first, then the XDG/HOME default. When that path is a symlink, the remedy points to the source credential and recommends only rerunning the implicit `allod site check`; it does not print the resolved path or recommend `site config update`/`--config` against the read-only link. Focused regressions cover both an `RCLONE_CONFIG` symlink and an XDG-default symlink. The focused lifecycle tests, full tagged and untagged Go test/vet matrix, `gofmt -d`, and `git diff --check` pass at this head.
vnprc changed target branch from agent/hosting-layout-profile to agent/site-config-path 2026-09-09 20:13:55 +01:00
vnprc approved these changes 2026-09-09 20:14:47 +01:00
vnprc merged commit a61b3dfe9f into agent/site-config-path 2026-09-09 20:14:55 +01:00
vnprc deleted branch agent/site-credential-lifecycle 2026-09-09 20:14:55 +01:00
Sign in to join this conversation.
No description provided.