Skip to content

feat: plain compose with shared fragments, a shared backup service, the secret store's host side, and the chart, tags and people retired - #43

Merged
AmrMsCLL merged 2 commits into
mainfrom
feat/host-env-files
Nov 5, 2026
Merged

AmrMsCLL merged 2 commits into
mainfrom
feat/host-env-files

Conversation

@AmrMsCLL

@AmrMsCLL AmrMsCLL commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Phase 1 of the Graphite Hub plan for platform: the host side of the console's secret store (step 2), the shared backup service (step 3), plain compose with shared fragments (steps 4 and 5), and the retirement of the chart, tag records and people files (step 7). Goes with Project-Graphite/console#19 and Project-Graphite/actions#10.

Host and agent

  • graphite-up and the agent use /etc/graphite/<project>.env when it exists and secrets.env otherwise, so each project moves to the console's store on its first rollout after its secrets are imported there.
  • The agent runs as root, reads /etc/graphite/agent.env, accepts values and files/<name> in a payload, refuses a missing secret name before it writes anything, and answers POST /reapply with Authorization: Bearer <CONSOLE_TOKEN> for the console's Redeploy.
  • graphite-up passes --env-file values before the secrets file.

Plain compose

  • shared/app.yaml (service: hardening, logging, health timing, limits; app: plus the edge alias ${PROJECT}, APP_URL, APP_PORT/APP_MEMORY/APP_CPUS/APP_RESERVE), shared/postgres.yaml, shared/redis.yaml, shared/redis-password.yaml.
  • Console, Strata, Tracker and PixelPocket are each deploy/<slug>/compose.yaml plus deploy/<slug>/values; the Pages projects keep only values with IMAGE_TAG. Volume names, container names and the compose project name are unchanged, so data stays where it is.
  • deploy.py flattens the compose (docker compose config --no-interpolate), renders it with the secret names stubbed, derives the Traefik route from the service on the edge network, and sends compose, values (with PROJECT and HOST from the registry) and files/ to the agent. A removed deploy/<slug>/ takes a compose project down.
  • PR checks: deploy.py check (every compose renders), check-memory.py (reservations against deploy/host.yaml), check-values.py (no value equal to its default, ghcr.io images only on ${IMAGE_TAG:?} or sha-, no ${NAME} without :? or :-).

Rendered both ways

Each project rendered through the chart (origin/main) and through the new compose, both interpolated with the same stubbed secrets, differs only in:

Project Difference
console SECRETS_KEY: ${SECRETS_KEY:-} added; graphite.backup label on data
strata backup sidecar and its backups volume gone; postgres flags reordered and work_mem=4MB (the default) dropped; health check reads $POSTGRES_USER/$POSTGRES_DB; backup labels on postgres and files-data
graphite-tracker postgres command states the defaults shared_buffers=128MB, max_connections=100; health check as above; backup label on postgres
pixelpocket MinIO scripts as config files instead of inline content; APP_URL on the frontend; the unused MONGO_PASSWORD gone from mongo

Required secret names and routes are identical for all four.

Backups

edge/backup.yaml runs edge/backup.py (Python standard library, Docker API over the socket):

  • Nightly at 03:00 UTC, per compose project: pg_dump of every container labelled graphite.backup.postgres, run and checked with pg_restore --list inside the container, pulled out through the archive API and compared by checksum; a tar of every volume labelled graphite.backup, with every SQLite database in it replaced by a consistent copy through SQLite's online backup API (no endpoint in the app). One /var/lib/graphite/backups/<project>/<stamp>.tar with a manifest, and a <stamp>.json summary beside it.
  • Every archive is reopened and verified: each member's checksum, every volume tar read to its end, every SQLite copy passes integrity_check. A failure marks the run failed, which the console alerts on.
  • 14 days kept. The console asks for a run, a verification of an uploaded archive, or a restore through request files in .requests/; a restore verifies first, stops the project, restores the dumps with pg_restore --clean --single-transaction and the volumes, starts it and waits for health.

Removed

charts/, deploy/chart.yaml, tags/, people/, scripts/compile.py, scripts/people.py, schema/person.schema.json, the Main workflow, sleep and wake jobs (no project uses them; edge/wake.yaml goes), release-core, Helm in CI, sync.py's single-manifest check. validate.py checks owners against organisation membership. Sync registry runs hourly as well as on dispatch.

deploy/graphite-message-lab/ is removed, which takes Message Lab down on merge (volumes kept). deploy/traceboard/ (disabled) is removed.

Rollout

  1. Merge this, then feat: IMAGE_TAG in platform's values, GitHub releases, no registry sync workflow actions#10 straight after. If main gained tags/ commits meanwhile, rebase and copy the newest tags into each values IMAGE_TAG.

  2. Deploy runs on merge: the edge and Message Lab's takedown work with the current agent; the backup service and the compose projects are refused by it before anything is written.

  3. Install the new agent over SSH:

    sudo install -d -m 700 /etc/graphite
    sudo mv /etc/graphite-agent.env /etc/graphite/agent.env
    source=https://raw.githubusercontent.com/project-graphite/platform/main/host
    sudo curl -fsSL -o /srv/graphite/bin/agent.py "$source/agent.py"
    sudo curl -fsSL -o /srv/graphite/bin/graphite-up "$source/graphite-up"
    sudo chmod 755 /srv/graphite/bin/graphite-up
    sudo curl -fsSL -o /etc/systemd/system/graphite-agent.service "$source/graphite-agent.service"
    sudo systemctl daemon-reload
    sudo systemctl restart graphite-agent
  4. Run Deploy by hand with project all.

  5. Back up one project from the console's Backups page, download it, import it and restore it: the phase 1 exit check.

The core-v1.0.0, core-v1.0.1 and core-v2.0.0 tags are left in place; git push origin --delete core-v1.0.0 core-v1.0.1 core-v2.0.0 removes them when wanted.

Checks

deploy.py check, check-memory.py, check-values.py (plus a negative run catching each of its rules), validate.py against live membership, the render diff above, py_compile and a local agent answering 401, 409 and a signed down. backup.py was run locally for packing a volume with a live WAL database, verification, tamper and wrong-project refusal, retention and the schedule. Not run: anything against Docker on the VPS (dumps, archive pulls, restore), graphite-up and bootstrap.sh.

README.md is stale throughout on the chart, core.yaml/values.yaml, tags/, people/, the Main workflow, /etc/graphite-agent.env and the backup sidecar.

… as root and re-applies for the console

graphite-up and the agent use /etc/graphite/<project>.env when it exists and
secrets.env otherwise, so each project moves to the console's secret store on
its next rollout after its secrets are imported. The agent runs as root, reads
/etc/graphite/agent.env, and answers POST /reapply with a Bearer CONSOLE_TOKEN
by re-running graphite-up on the project's last applied folder.
@AmrMsCLL
AmrMsCLL requested a review from a team as a code owner November 5, 2026 00:49
@github-actions github-actions Bot added the size/s 11-50 changed lines, excluding lockfiles label Nov 5, 2026
…p service, and the chart, tags and people retired

Every compose project is deploy/<slug>/compose.yaml extending shared/app.yaml,
postgres.yaml, redis.yaml or redis-password.yaml, plus deploy/<slug>/values with
IMAGE_TAG and only the values that differ from a default. deploy.py flattens the
compose, renders it with the secret names stubbed, and sends compose, values and
files to the agent. check-memory.py and check-values.py run on every PR.

edge/backup.yaml runs a nightly backup at 03:00 UTC of every postgres labelled
graphite.backup.postgres and every volume labelled graphite.backup, with
consistent copies of SQLite databases, verifies each archive, keeps 14 days, and
runs, verifies and restores on request from the console.

The core chart, deploy/chart.yaml, tags/, people/, compile.py, people.py, the
person schema and the Main workflow are removed. validate.py checks owners
against organisation membership. Message Lab and the disabled Traceboard lose
their deploy folders, which takes Message Lab down.
@github-actions github-actions Bot added size/xl Over 600 changed lines, excluding lockfiles and removed size/s 11-50 changed lines, excluding lockfiles labels Nov 5, 2026
@AmrMsCLL AmrMsCLL changed the title feat(host): per-project env files in /etc/graphite, root agent, /reapply for the console feat: plain compose with shared fragments, a shared backup service, the secret store's host side, and the chart, tags and people retired Nov 5, 2026
@AmrMsCLL
AmrMsCLL merged commit c171c68 into main Nov 5, 2026
5 checks passed
@AmrMsCLL
AmrMsCLL deleted the feat/host-env-files branch November 5, 2026 01:41
AmrMsCLL added a commit to Project-Graphite/actions that referenced this pull request Nov 5, 2026
…nc workflow (#10)

Phase 1, step 6 of the Graphite Hub plan, with the `actions` part of
step 7. Goes with Project-Graphite/platform#43, which replaces `tags/`
and the chart with `deploy/<slug>/compose.yaml` and `values`.

## Changes

- **Deploy** writes `IMAGE_TAG=<tag>` as the first line of
`platform/deploy/<slug>/values`, keeping every other line, and commits
it as before. A project without a `deploy/<slug>/` folder (Message Lab
after #43) is skipped with a notice instead of failing.
- **Changelog** lists the repository's tags before `sync:releases --cut`
and publishes a GitHub release, with generated notes, for each tag the
cut added. The hub's release records are still written as today until
the new hub replaces them.
- **Sync registry** (`reusable-sync-registry.yml`) is removed.
`platform`'s Sync registry now runs hourly as well as on dispatch, and
the new hub will dispatch it from the organisation webhook. Each
project's `registry.yml` that called this workflow is removed in its own
PR.

## Merge order

Merge platform#43 first, then this straight after. Between the two,
project CI still writes `tags/`, which platform no longer reads, so a
deploy in that window waits for the next push.

## Checks

The `values` rewrite was run locally against a sample file. The
workflows are linted by this repository's CI.

`README.md` is stale on `reusable-sync-registry`, the `tags/<slug>.json`
description of Deploy, and the `registry.yml` every project needs.
AmrMsCLL added a commit that referenced this pull request Nov 5, 2026
…upply (#44)

The Deploy run for #43 failed every compose project with `no service
joins the edge network as <slug>`. The workflow sets `PROJECT: ${{
inputs.project }}` for the job, empty on a push, and Compose gives the
process environment priority over `--env-file`, so `${PROJECT}` rendered
empty. PR checks passed because that job has no `PROJECT`.

`deploy.py` now runs `docker compose` without any process variable that
one of its env files supplies. Nothing reached the agent in the failed
run: the four projects kept running as before.

Checked locally with `PROJECT=wrong` and `HOST=wrong` in the
environment: all four routes render; `deploy.py check`,
`check-memory.py` and `check-values.py` pass.
AmrMsCLL added a commit that referenced this pull request Nov 5, 2026
… hosts (#48)

`uptime.py` still read `deploy/<slug>/values.yaml` and `core.yaml`, both
retired with the chart in #43. The `enabled` check therefore always
passed, so a project set to `ENABLED=false` (#47) would be probed and
reported as Down.

- Projects come from `deploy/` folders, with `entry`, `values`,
`projects` and `DEPLOY_HOST` imported from `deploy.py` instead of a
second parser.
- A project whose `values` says `ENABLED=false` is not probed.
- The `core.yaml` extra-hosts loop is gone; compose projects have one
host, from the registry.

Checked locally: `targets()` lists the six live hosts plus the agent,
and drops a project whose `values` has `ENABLED=false`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl Over 600 changed lines, excluding lockfiles

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant