7733e64b08
A dockerfile or static workload can opt in to reading its deploy config from a
.tinyforge.yml in its own repo. Tinyforge fetches the file, shows field-level
drift vs the live config, and an admin applies it with an explicit Sync. The
repo becomes the source of truth for the declared fields. Manual-sync only;
no auto-apply on deploy, no multi-workload reconcile, no create/delete in v1.
Scope is deliberately source-aware and source_config-resident: dockerfile
declares port/healthcheck/deploy_strategy, static declares deploy_strategy.
The file never carries repo coords or secrets (those stay in the encrypted
DB), which keeps credentials out of the repo.
Backend:
- internal/gitops: Spec/ParseSpec (KnownFields rejects unknown keys), a
source-aware ApplyPlan/BuildPlan, MergeAndValidate (omitted-field-preserving
deep merge + validate-the-merged-result-then-commit — never a partial
config), declared-only Drift with normalization, and Fetch with
ok/no_file/fetch_failed/invalid statuses and token-redacted messages.
- staticsite: DownloadFile added to GitProvider + Gitea/GitHub/GitLab impls,
reusing each provider's SSRF-safe client; 64 KiB cap; ErrFileNotFound.
- store: 4 additive gitops_* columns + setters (disjoint from UpdateWorkload
so the edit-form save and a sync never clobber each other).
- api: GET /workloads/{id}/gitops (status + raw + live drift + managed_fields),
PUT /gitops (admin, enable/path, traversal-safe), POST /gitops/sync (admin,
per-workload locked read->merge->validate->write, audited to event_log).
Frontend:
- GitOpsPanel.svelte: status pill, a purpose-built field-level drift view,
.tinyforge.yml preview, enable ToggleSwitch, Sync via ConfirmDialog; all five
statuses handled, admin affordances gated on the real viewer role.
- GitOps-managed badge (list + detail hero) and a read-only edit-form banner.
- api.ts fetchers + types; i18n apps.detail.gitops.* (en + ru parity).
Built phase-by-phase with an adversarial plan review (caught 5 design flaws
pre-implementation) and an independent review per phase (go / security / ts /
final) — all APPROVE, 0 CRITICAL/HIGH. docs/gitops.md documents the schema and
what's intentionally out of v1. Plan: plans/gitops/.
78 lines
3.9 KiB
Markdown
78 lines
3.9 KiB
Markdown
# GitOps: config-as-code with `.tinyforge.yml`
|
|
|
|
A **dockerfile** or **static** workload can read part of its deploy config from a
|
|
`.tinyforge.yml` file in its own repo. Tinyforge fetches the file, shows you how it
|
|
differs from the live config (**drift**), and applies it when you click **Sync** — so the
|
|
repo becomes the source of truth for the declared fields.
|
|
|
|
This is opt-in per workload and **manual-sync only** in v1: nothing is applied automatically
|
|
on deploy, and a sync never runs without an explicit admin action.
|
|
|
|
## Enabling it
|
|
|
|
1. Open the workload (Apps → your app).
|
|
2. In the **GitOps** panel, toggle it on. The default file path is `.tinyforge.yml` at the
|
|
repo root; change it if your file lives elsewhere (e.g. `deploy/.tinyforge.yml`).
|
|
3. Add a `.tinyforge.yml` to the repo (schema below) and push.
|
|
4. The panel shows the parsed file and any drift vs. the live config. Click **Sync now** to
|
|
apply the repo's values to the workload.
|
|
|
|
Only **dockerfile** and **static** sources are eligible — they're the git-backed sources.
|
|
`image` and `compose` workloads don't show the panel.
|
|
|
|
## `.tinyforge.yml` schema (v1)
|
|
|
|
```yaml
|
|
version: 1 # required, must be 1
|
|
deploy:
|
|
# dockerfile only:
|
|
port: 8080 # container port the app listens on
|
|
healthcheck: /healthz # HTTP path probed before a blue-green cutover ("" to disable)
|
|
# dockerfile + static:
|
|
deploy_strategy: blue-green # "" | recreate | blue-green
|
|
```
|
|
|
|
Notes:
|
|
|
|
- **Only the fields above are honored.** Unknown keys are rejected with an error (so a typo
|
|
surfaces instead of being silently ignored).
|
|
- Fields you omit are **left untouched** — the file overlays only what it declares; it never
|
|
clears the rest of your config.
|
|
- The file is **source-aware**: a `static` workload only honors `deploy_strategy` (a static
|
|
site has no port/healthcheck); `port`/`healthcheck` in a static site's file are ignored.
|
|
- `deploy_strategy: ""` and `recreate` are equivalent (both are the default for dockerfile
|
|
and static), so they never show as drift against each other.
|
|
|
|
## What `.tinyforge.yml` does **not** contain
|
|
|
|
- **No repo location** (provider / owner / repo / branch) and **no access token** — those
|
|
stay in Tinyforge's encrypted database. This is deliberate: it keeps credentials out of
|
|
your repo. (You need the repo coords to find the file in the first place, so they can't
|
|
live in it.)
|
|
|
|
## Drift and sync
|
|
|
|
- **Drift** is computed only over the fields the file declares, after normalization (so a
|
|
defaulted strategy or a YAML-int vs stored-number difference isn't a false positive).
|
|
- **Sync** fetches the file, merges the declared fields onto a copy of the live config,
|
|
**validates the merged result** with the source's own rules, and only persists it if it
|
|
passes — a bad file is rejected as a whole and never leaves a partial config. The sync is
|
|
recorded to the workload's activity log (not the deploy ledger — it changes config, it
|
|
isn't a deploy).
|
|
- While GitOps is enabled, the edit form shows a banner noting which fields the repo manages;
|
|
editing them in the UI works, but the next Sync overwrites them with the repo's values.
|
|
|
|
## Not in v1 (planned)
|
|
|
|
These are intentionally out of scope for the first version; the design leaves clean seams
|
|
for them:
|
|
|
|
- **`env` and `faces` (public subdomains)** — they live in separate stores and (for `env`)
|
|
would re-introduce a secrets-in-repo risk; deferred to a typed multi-target apply.
|
|
- **Auto-apply on deploy** — applying the repo config automatically on every push. v1 keeps
|
|
a human in the loop with the drift view + manual Sync. When added, it will read the file
|
|
at the exact deployed commit (a source-plugin concern), not at dispatch time.
|
|
- **Multi-workload reconcile** — one repo declaring/creating/deleting many workloads
|
|
(the full Flux/Argo model). v1 is per-workload, config-only, with no create/delete.
|
|
- **`image` / `compose` sources** — not git-backed / overlapping config surface.
|