feat(cli): merge oxicloud binary and cli

this feature to simplify the creation of only 1 binary for multiple architecture
This commit is contained in:
Edouard Vanbelle
2026-08-28 20:37:36 +02:00
parent 811c356cde
commit 390aa31443
25 changed files with 769 additions and 571 deletions
@@ -41,7 +41,9 @@ Mirrors the `tests/api/` shell pattern (run.sh, test.env, separate server port).
### 2. Rust bulk seeder — `src/bin/load-seed.rs`
New binary registered in `Cargo.toml` alongside `generate-openapi` and `migrate-nfc-filenames`.
New binary registered in `Cargo.toml` alongside `generate-openapi`
(the historical `migrate-nfc-filenames` bin has since been folded into
`oxicloud migrate nfc-filenames` — see `docs/plan/bundled-binary.md` § 1b).
**CLI:**
```
@@ -158,7 +160,7 @@ Matches existing recipe naming (`test-*`, `front-*`, `api-test`).
- `.github/workflows/load-nightly.yml`, `load-smoke.yml`
**Modify:**
- `Cargo.toml` — add `[[bin]] name = "load-seed" path = "src/bin/load-seed.rs"` after the `migrate-nfc-filenames` entry
- `Cargo.toml` — add `[[bin]] name = "load-seed" path = "src/bin/load-seed.rs"` after the `generate-openapi` entry (the `migrate-nfc-filenames` bin referenced in earlier drafts has been folded into `oxicloud migrate nfc-filenames`)
- `justfile` — append four `load*` recipes
- `.gitignore` — add `tests/load/results/*.json` and `tests/load/storage/`
+1 -1
View File
@@ -330,7 +330,7 @@ authenticated session):
**Deferred:**
- Step-up auth before link start
- Admin-mediated link/unlink via `oxicloud-cli federation` (proper for
- Admin-mediated link/unlink via `oxicloud federation` (proper for
"user changed IdP email" recovery scenario)
- OCM link (same shape, different kind)
- Multi-federation (multiple linked identities per user — see
+5 -5
View File
@@ -62,7 +62,7 @@ and ordering are the load-bearing decisions here.
## Preconditions before we start the wipe
Every one of these MUST hold. Adding a pre-flight check in
`oxicloud-cli opaque wipe-legacy` (proposed below) that refuses to run
`oxicloud opaque wipe-legacy` (proposed below) that refuses to run
otherwise.
1. **`OXICLOUD_AUTH_OPAQUE_MODE=opaque_only`** on the deployment for at
@@ -174,7 +174,7 @@ it can't, since login-link users just clicked email — no proof-of-current).
### The wipe migration
Delivered as `oxicloud-cli opaque wipe-legacy` — a dedicated subcommand,
Delivered as `oxicloud opaque wipe-legacy` — a dedicated subcommand,
NOT a schema migration. Reasons:
- Idempotent (won't re-wipe already-nulled rows)
- Pre-flight refuses when preconditions aren't met (unlike a migration
@@ -207,7 +207,7 @@ UPDATE auth.users
Output: `N password_hash columns nulled. M users still have password_hash
because they don't meet the OPAQUE-migrated preconditions — inspect via
`oxicloud-cli opaque wipe-legacy --dry-run` and address separately.`
`oxicloud opaque wipe-legacy --dry-run` and address separately.`
The `WHERE` clause is intentionally strict: OIDC users, externals, and
under-migrated users are ALL left alone. The strict version is safer than
@@ -232,7 +232,7 @@ can drop the legacy password code:
6. `has_password` field on `UserDto` / `AdminUserSummaryDto`: delete (always
false, meaningless signal)
7. `admin`-badge `password` chip: delete (same reason)
8. `oxicloud-cli opaque reset --user X` for legacy-recovery: still useful
8. `oxicloud opaque reset --user X` for legacy-recovery: still useful
as an emergency lever (envelope somehow corrupted, need to force
re-registration via recovery-magic-link), but its "silent-migration
handles the recovery" semantics become "recovery-magic-link handles the
@@ -269,7 +269,7 @@ running smoothly for the indicated period."
| G1 | Land task #31: change_password OPAQUE-lockout fix + hybrid-user password gate | Days |
| G2 | Land recovery-magic-link admin reset flow | Weeks |
| G3 | Land OPAQUE-verify-current + change_password redesign that COMPOSES the two (Argon2-verify AND OPAQUE-verify both work; use whichever the user has) | Weeks |
| G4 | Ship `oxicloud-cli opaque wipe-legacy` (dry-run only initially, no destructive flag) | Days |
| G4 | Ship `oxicloud opaque wipe-legacy` (dry-run only initially, no destructive flag) | Days |
| G5 | Add admin-dashboard metric: "N users still on legacy (`password_hash IS NOT NULL AND !opaque_migrated`)" | Days |
| G6 | Operator switches deployment to `opaque_only` mode | ✅ already possible |
| G7 | Wait 90+ days at `opaque_only`, watch the metric drop to 0 | Months |
+5 -5
View File
@@ -324,7 +324,7 @@ at), boot fails fast with a clear error. Operator has two ways out:
restart.
2. **CLI repair flag on the `oxicloud` binary itself**:
```
oxicloud --select-storage <name>
oxicloud storage select <name>
```
Behaviour: parse `.env`, verify `<name>` exists in `_ENTRIES` (fail-fast
with the available names listed if not), connect to DB, UPDATE
@@ -334,7 +334,7 @@ at), boot fails fast with a clear error. Operator has two ways out:
The bare-flag on the shipped binary is chosen over a separate `just`
recipe or auxiliary bin because:
- **Docker-friendly**: `docker exec oxicloud oxicloud --select-storage foo`
- **Docker-friendly**: `docker exec oxicloud oxicloud storage select foo`
— no need to install extra tooling in the container.
- **Systemd-friendly**: can be run as a `ExecStartPre=` one-shot before the
main service unit.
@@ -377,7 +377,7 @@ foundational; the rest layer on top independently within reason.
| 5 | Cutover state machine: on migration `Completed`, write `active_backend_name = target_name`, keep read-only on. Boot on new backend after operator restart. | 4 | ~half day |
| 6 | Admin storage tab rewrite: list entries, show active, migrate dropdown, read-only banner. Delete Save form + S3 field editors + .env cutover hint. | 1, 3, 4 | 1 day |
| 7 | `?storage=<name>` on `blobs_consistency` + `backend_consistency`. `JobRunArgs.storage` plumbing, `TriggerJobQuery.storage`, entry-resolver at run start, params records probed name. Retire `verify_migration` + its DTO + its route + its handler. | 1, 3 | 1 day |
| 8 | `oxicloud --select-storage <name>` bare-flag repair command on the main binary. Parses `.env`, verifies entry exists, UPDATEs DB, exits. Boot-time missing-entry error message points at it. See §Fallback. | 2 | ~quarter day |
| 8 | `oxicloud storage select <name>` bare-flag repair command on the main binary. Parses `.env`, verifies entry exists, UPDATEs DB, exits. Boot-time missing-entry error message points at it. See §Fallback. | 2 | ~quarter day |
**Total: ~5-6 days end to end.** Slices 6 and 7 can proceed in parallel with
each other once 1-5 land. Slice 8 is an ops nicety, could ship whenever.
@@ -414,8 +414,8 @@ Per slice, plus these end-to-end scenarios in Hurl:
→ 400 with known-names list. No run row created.
9. **Missing entry at boot**: `active_backend_name = "gone"` but `_ENTRIES`
doesn't include it → boot aborts with the specific message pointing at
`oxicloud --select-storage <name>` (with the available names filled in).
Re-run the binary with `--select-storage local_main` → verifies + updates
`oxicloud storage select <name>` (with the available names filled in).
Re-run the binary with `storage select local_main` → verifies + updates
DB + exits 0. Restart the server → boots cleanly on `local_main`.
10. **Encryption key invalid**: `OXICLOUD_STORAGE_<N>_ENCRYPTION_KEY=badbase64`
→ boot aborts with entry name + reason (not valid base64 / wrong length).