mediamark: deploy the media marking app #441

Merged
benvin merged 2 commits from benvin/deploy-mediamark into main 2026-08-30 15:07:42 +10:00
Member

Why

mediamark needs a home in the cluster: it marks/organises media on the shared mediastore tree and drives the adult-tier sonarr/radarr through arrproxy's hash routes. This adds the full app to the media project, mirroring the watchstate two-host oauth2-proxy pattern and the arrstack static-CephFS + projected-API-keys patterns.

How

  • Adds apps/base/mediamark/: namespace, VaultAuth (k8s/au/syd1, role default), three VaultStaticSecrets, static mediastore PV/PVC, the app Deployment, oauth2-proxy ConfigMap/Deployment, two Services, and internal + external Gateway/HTTPRoute pairs.
  • Binds a dedicated static PV (mediamark-mediastore, own volumeHandle, claimRef-pinned) to the same CephFS mediastore subvolume arrstack/fafflix/cheeztv use, RWX 10Ti Retain, mounted at /media.
  • Runs the app as 1000:1000 (deliberately not 65532) so it owns files on the shared media tree and hardlink/rename moves stay valid; read-only root filesystem, all caps dropped, no service-account token, /livez + /readyz probes.
  • Projects the sonarr/radarr API keys as one file per app under /etc/mediamark/keys, mirroring arrproxy's keys projection, with reloader annotations on both secrets.
  • Fronts both mediamark.unkin.net (traefik-external, reflected Let's Encrypt wildcard, no cert-manager annotations) and mediamark.k8s.syd1.au.unkin.net (traefik-internal, vault-issuer) with a single oauth2-proxy using a relative /oauth2/callback redirect; gated on akP-mediamark-user and passing identity to the app as X-Forwarded-Groups via PASS_USER_HEADERS.
  • Appends mediamark to the wildcard-unkin-net Certificate's two reflector namespace lists, and registers the app in argocd/applicationsets/media.yaml + argocd/projects/media.yaml with a passthrough apps/overlays/au-syd1/mediamark overlay.

Prerequisite seeds (Ben, before pods go Ready)

These KV paths must exist under kv/kubernetes/namespace/mediamark/default/ — the mediamark/default templated policy already grants read, so no terraform-vault change is needed:

  • oauth-credentials — needs client_id and cookie_secret added alongside the existing client_secret (Authentik mediamark provider; both absolute callback URIs registered there).
  • sonarr — key apitoken.
  • radarr — key apitoken.

Validation

  • kustomize build --enable-helm apps/overlays/au-syd1/mediamark (18 resources) and .../cert-manager both build.
  • kubeconform clean on both touched overlays.
  • pre-commit run --all-files passes.
## Why mediamark needs a home in the cluster: it marks/organises media on the shared mediastore tree and drives the adult-tier sonarr/radarr through arrproxy's hash routes. This adds the full app to the `media` project, mirroring the watchstate two-host oauth2-proxy pattern and the arrstack static-CephFS + projected-API-keys patterns. ## How - Adds `apps/base/mediamark/`: namespace, VaultAuth (`k8s/au/syd1`, role `default`), three VaultStaticSecrets, static mediastore PV/PVC, the app Deployment, oauth2-proxy ConfigMap/Deployment, two Services, and internal + external Gateway/HTTPRoute pairs. - Binds a dedicated static PV (`mediamark-mediastore`, own `volumeHandle`, `claimRef`-pinned) to the same CephFS mediastore subvolume arrstack/fafflix/cheeztv use, RWX 10Ti Retain, mounted at `/media`. - Runs the app as 1000:1000 (deliberately not 65532) so it owns files on the shared media tree and hardlink/rename moves stay valid; read-only root filesystem, all caps dropped, no service-account token, `/livez` + `/readyz` probes. - Projects the sonarr/radarr API keys as one file per app under `/etc/mediamark/keys`, mirroring arrproxy's keys projection, with reloader annotations on both secrets. - Fronts both `mediamark.unkin.net` (traefik-external, reflected Let's Encrypt wildcard, no cert-manager annotations) and `mediamark.k8s.syd1.au.unkin.net` (traefik-internal, vault-issuer) with a single oauth2-proxy using a relative `/oauth2/callback` redirect; gated on `akP-mediamark-user` and passing identity to the app as `X-Forwarded-Groups` via `PASS_USER_HEADERS`. - Appends `mediamark` to the `wildcard-unkin-net` Certificate's two reflector namespace lists, and registers the app in `argocd/applicationsets/media.yaml` + `argocd/projects/media.yaml` with a passthrough `apps/overlays/au-syd1/mediamark` overlay. ## Prerequisite seeds (Ben, before pods go Ready) These KV paths must exist under `kv/kubernetes/namespace/mediamark/default/` — the `mediamark/default` templated policy already grants read, so no terraform-vault change is needed: - `oauth-credentials` — needs `client_id` and `cookie_secret` added alongside the existing `client_secret` (Authentik mediamark provider; both absolute callback URIs registered there). - `sonarr` — key `apitoken`. - `radarr` — key `apitoken`. ## Validation - `kustomize build --enable-helm apps/overlays/au-syd1/mediamark` (18 resources) and `.../cert-manager` both build. - kubeconform clean on both touched overlays. - `pre-commit run --all-files` passes.
unkin-agent added 1 commit 2026-08-29 23:24:55 +10:00
mediamark: deploy the media marking app
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
5ea193b957
Adds the mediamark app (Go single binary) to the media project: an
oauth2-proxy-fronted service that reads the shared mediastore CephFS tree
and talks to the adult-tier sonarr/radarr through arrproxy's hash routes.

- Add apps/base/mediamark: namespace, VaultAuth, three VaultStaticSecrets,
  static mediastore PV/PVC, the app Deployment, oauth2-proxy
  ConfigMap/Deployment, two Services, and the internal + external
  Gateway/HTTPRoute pairs.
- Run the app as 1000:1000 so it owns files on the shared media tree
  (hardlink/rename safe), with a read-only root filesystem, all caps
  dropped and no service-account token.
- Project the sonarr/radarr API keys as one file per app under
  /etc/mediamark/keys, mirroring arrproxy's keys projection, with reloader
  annotations on both secrets.
- Front both mediamark.unkin.net (traefik-external, reflected LE wildcard)
  and mediamark.k8s.syd1.au.unkin.net (traefik-internal, vault-issuer) with
  one oauth2-proxy using a relative redirect URL, gated on
  akP-mediamark-user and passing X-Forwarded-Groups to the app.
- Append mediamark to the wildcard-unkin-net reflector namespace lists and
  register the app in the media ApplicationSet and AppProject.
unkin-agent added 1 commit 2026-08-30 00:15:13 +10:00
mediamark: use ephemeral arrstack virtual keys
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
fbe7348428
The static sonarr/radarr API keys were copies of the real per-app keys,
seeded by hand into kv and never rotated. The arrstack secrets engine can
mint short-lived machine tokens instead, so mediamark leases one rather
than holding a copy of the real thing.

Drop the sonarr-apikey and radarr-apikey VaultStaticSecrets.
Add an arrstack-creds VaultAuth bound to the new `mediamark` k8s role.
Add an arrstack-virtual-key VaultDynamicSecret reading arrstack/creds/mediamark.
Project the leased token to both /etc/mediamark/keys/{sonarr,radarr}.
Point MEDIAMARK_{SONARR,RADARR}_URL at arrproxy-api, which is the only
thing that honours a virtual key.
Reload on arrstack-virtual-key instead of the removed secrets.
Author
Member

Updated: ephemeral virtual keys instead of copied static keys (fbe7348)

Pushed an additive commit that swaps the hand-seeded sonarr/radarr API keys for short-lived credentials leased from the arrstack secrets engine.

Removed

  • VaultStaticSecret/sonarr-apikey (kv kubernetes/namespace/mediamark/default/sonarr)
  • VaultStaticSecret/radarr-apikey (kv kubernetes/namespace/mediamark/default/radarr)

Added

  • VaultAuth/arrstack-creds — mount k8s/au/syd1, role mediamark, SA default, audience vault, TTL 600. Separate from the default VaultAuth because only the mediamark role's policy grants the creds path.
  • VaultDynamicSecret/arrstack-virtual-key — mount arrstack, path creds/mediamark, vaultAuthRef: arrstack-creds, destination secret arrstack-virtual-key (create: true), revoke: true, allowStaticCreds: false, default renewalPercent: 67. Role ttl is 60s so VSO renews the lease continuously.

Rewired

  • Keys volume now projects the single token key to both /etc/mediamark/keys/sonarr and /etc/mediamark/keys/radarr — one machine token covers both apps, and mediamark re-reads the file per request, so renewal rotates in place.
  • MEDIAMARK_SONARR_URL / MEDIAMARK_RADARR_URL now point at http://arrproxy-api.arrstack.svc.cluster.local:8080/3aa168/{sonarr,radarr}. This is required, not cosmetic: a virtual key is only meaningful to arrproxy, which validates the machine token and injects the real per-app key upstream. The sonarr-adult / radarr-adult Services would reject it. The 3aa168 prefix matches the fafflix tier in apps/base/arrstack/arrproxy/tiers-configmap.yaml.
  • Reloader annotation is now arrstack-virtual-key.

Prerequisites (revised)

The sonarr/radarr KV seeds are no longer needed — drop them from the checklist. What remains before pods can get credentials:

  1. oauth-credentials KV patch — Ben, unchanged from before.
  2. terraform-vault #141 merged and applied — this PR is inert without it. Until the arrstack/creds/mediamark role and the mediamark k8s auth role exist, the VaultDynamicSecret will fail to sync and the keys volume will have no secret to project, so mediamark pods will not become ready. Merge order: #141 applied first, then this.

Note on blast radius

The engine cannot method-restrict machine tokens, so the minted virtual key is full-method (it would permit writes through arrproxy if something used it that way). mediamark itself only issues GETs. Worth knowing that the restriction is behavioural rather than enforced at the token; if that becomes a concern the fix belongs in the engine, not here.

Validation

  • kustomize build apps/overlays/au-syd1/mediamark — clean
  • kubeconform with the repo's ci/validate-apps.sh args — 18/18 valid, VaultDynamicSecret arrstack-virtual-key is valid (the v1beta1 schema is already committed under schemas/secrets.hashicorp.com/, no skip needed)
  • pre-commit run --all-files — all passed
### Updated: ephemeral virtual keys instead of copied static keys (`fbe7348`) Pushed an additive commit that swaps the hand-seeded sonarr/radarr API keys for short-lived credentials leased from the arrstack secrets engine. **Removed** - `VaultStaticSecret/sonarr-apikey` (kv `kubernetes/namespace/mediamark/default/sonarr`) - `VaultStaticSecret/radarr-apikey` (kv `kubernetes/namespace/mediamark/default/radarr`) **Added** - `VaultAuth/arrstack-creds` — mount `k8s/au/syd1`, role `mediamark`, SA `default`, audience `vault`, TTL 600. Separate from the `default` VaultAuth because only the `mediamark` role's policy grants the creds path. - `VaultDynamicSecret/arrstack-virtual-key` — mount `arrstack`, path `creds/mediamark`, `vaultAuthRef: arrstack-creds`, destination secret `arrstack-virtual-key` (`create: true`), `revoke: true`, `allowStaticCreds: false`, default `renewalPercent: 67`. Role ttl is 60s so VSO renews the lease continuously. **Rewired** - Keys volume now projects the single `token` key to **both** `/etc/mediamark/keys/sonarr` and `/etc/mediamark/keys/radarr` — one machine token covers both apps, and mediamark re-reads the file per request, so renewal rotates in place. - `MEDIAMARK_SONARR_URL` / `MEDIAMARK_RADARR_URL` now point at `http://arrproxy-api.arrstack.svc.cluster.local:8080/3aa168/{sonarr,radarr}`. This is required, not cosmetic: a virtual key is only meaningful to arrproxy, which validates the machine token and injects the real per-app key upstream. The `sonarr-adult` / `radarr-adult` Services would reject it. The `3aa168` prefix matches the fafflix tier in `apps/base/arrstack/arrproxy/tiers-configmap.yaml`. - Reloader annotation is now `arrstack-virtual-key`. ### Prerequisites (revised) The sonarr/radarr KV seeds are **no longer needed** — drop them from the checklist. What remains before pods can get credentials: 1. **`oauth-credentials` KV patch** — Ben, unchanged from before. 2. **terraform-vault #141 merged and applied** — this PR is inert without it. Until the `arrstack/creds/mediamark` role and the `mediamark` k8s auth role exist, the VaultDynamicSecret will fail to sync and the keys volume will have no secret to project, so mediamark pods will not become ready. Merge order: #141 applied first, then this. ### Note on blast radius The engine cannot method-restrict machine tokens, so the minted virtual key is full-method (it would permit writes through arrproxy if something used it that way). mediamark itself only issues GETs. Worth knowing that the restriction is behavioural rather than enforced at the token; if that becomes a concern the fix belongs in the engine, not here. ### Validation - `kustomize build apps/overlays/au-syd1/mediamark` — clean - `kubeconform` with the repo's `ci/validate-apps.sh` args — 18/18 valid, `VaultDynamicSecret arrstack-virtual-key is valid` (the v1beta1 schema is already committed under `schemas/secrets.hashicorp.com/`, no skip needed) - `pre-commit run --all-files` — all passed
benvin merged commit b355d6aafb into main 2026-08-30 15:07:42 +10:00
benvin deleted branch benvin/deploy-mediamark 2026-08-30 15:07:42 +10:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unkin/argocd-apps#441