Render cards on the nodes, and wire the GitHub mirror

1502d9d52179 · AtlantisPleb · · parent 19326be8e35a

Render cards on the nodes, and wire the GitHub mirror

The two operational follow-ups from the Open Graph work and the forge
cutover, done together:

- The release image installs librsvg2-bin, fontconfig, and the pinned
  Geist TTFs (v1.7.2; zip and per-file digests checked), with a
  fontconfig alias from the CSS family name "Geist Sans" to the files'
  internal name "Geist". Dynamic card rasterization now works on any
  node running a rebuilt image instead of serving the fallback.
- OPENAGENTS_FORGE_MIRROR_URLS_JSON configures one-way forge-to-GitHub
  mirroring: RuntimeConfig validates the JSON object and refuses
  credential-bearing URLs (auth belongs to node credential helpers or
  deploy keys), Pushes.mirror_now does the force-push mirror, and
  MirrorWatch reports freshness and lag. Staging receives it through an
  optional forge_mirror_urls_json Terraform variable that flows into
  runtime.env only when set.

Also updates the push guard's comment, which described mirroring as
unconfigurable, and pins the new infrastructure wiring in the staging
candidate contract.

Changelog: Forge pushes can now mirror to GitHub automatically once a mirror URL is configured for the environment.

Changelog-Category: feature

Changelog-Visibility: public
Changelog
Forge pushes can now mirror to GitHub automatically once a mirror URL is configured for the environment.
Changelog-Category
feature
Changelog-Visibility
public

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified Dockerfile
  • modified config/runtime.exs
  • modified docs/2026-08-21-open-graph-cards.md
  • modified docs/runtime-configuration.md
  • modified infra/staging/main.tf
  • modified infra/staging/templates/fleet-startup.sh.tftpl
  • modified infra/staging/variables.tf
  • modified ops/ci/push-remote-check.sh
  • modified test/openagents/staging_candidate_contract_test.exs

Diff

9 files changed, +143 -16

Dockerfile modified +48 -1

@@ -144,6 +144,15 @@ ARG CODEX_VERSION

144 144
ARG OPENCODE_VERSION
145 145
ARG DEBIAN_SNAPSHOT
146 146
ARG SOURCE_DATE_EPOCH=0
147
148
# Geist TTFs for server-side image rendering (Open Graph cards). The web
149
# ships the same faces as woff2; librsvg reads system fonts through
150
# fontconfig, so the release image carries the pinned release's static weights.
151
ARG GEIST_FONT_VERSION=1.7.2
152
ARG GEIST_FONT_SHA256=7fc800d2ac6b92844895196e5041aca55d814c15db70c44f79b3b83ab82b04e2
153
ARG GEIST_REGULAR_SHA256=5c8968eafb98a4c4f47033daf29e38e284a6f2a82eb017d171ab040fe7c4b615
154
ARG GEIST_MEDIUM_SHA256=0090e004725f6f64b841715b4167920580f883fcf9b67fc6d744089103fec101
155
ARG GEIST_SEMIBOLD_SHA256=612ec98df33935354f39e81e54101656961ab6e5549f64b63eb57868ba7bab8d
147 156
ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}
148 157
149 158
RUN sed -i \

@@ -154,9 +163,47 @@ RUN sed -i \

154 163
      /etc/apt/sources.list.d/debian.sources \
155 164
  && printf 'Acquire::Check-Valid-Until "false";\n' > /etc/apt/apt.conf.d/99snapshot \
156 165
  && apt-get update \
157
  && apt-get install -y --no-install-recommends libstdc++6 openssl libncurses6 locales ca-certificates curl git \
166
  && apt-get install -y --no-install-recommends libstdc++6 openssl libncurses6 locales ca-certificates curl git unzip fontconfig librsvg2-bin \
158 167
  && rm -rf /var/lib/apt/lists/*
159 168
169
# Server-side image rendering (Open Graph cards) reads system fonts through
170
# fontconfig. The CSS declares the family as "Geist Sans" while the TTFs'
171
# internal name is "Geist", so the alias below keeps librsvg on-brand without
172
# touching the web font stack. Zip and extracted-file digests are both checked.
173
RUN set -eu; \
174
  archive="geist-font-v${GEIST_FONT_VERSION}.zip"; \
175
  curl -fsSL --retry 3 -o "/tmp/${archive}" \
176
    "https://github.com/vercel/geist-font/releases/download/v${GEIST_FONT_VERSION}/${archive}"; \
177
  echo "${GEIST_FONT_SHA256}  /tmp/${archive}" | sha256sum --check --strict; \
178
  install -d -m 0755 /usr/local/share/fonts/geist; \
179
  unzip -q -j "/tmp/${archive}" \
180
    "geist-font/Geist/ttf/Geist-Regular.ttf" \
181
    "geist-font/Geist/ttf/Geist-Medium.ttf" \
182
    "geist-font/Geist/ttf/Geist-SemiBold.ttf" \
183
    -d /usr/local/share/fonts/geist; \
184
  rm "/tmp/${archive}"; \
185
  cd /usr/local/share/fonts/geist; \
186
  printf '%s  %s\n' \
187
    "${GEIST_REGULAR_SHA256}" "Geist-Regular.ttf" \
188
    "${GEIST_MEDIUM_SHA256}" "Geist-Medium.ttf" \
189
    "${GEIST_SEMIBOLD_SHA256}" "Geist-SemiBold.ttf" \
190
    | sha256sum --check --strict; \
191
  printf '%s\n' \
192
    '<?xml version="1.0"?>' \
193
    '<!DOCTYPE fontconfig SYSTEM "fonts.dtd">' \
194
    '<fontconfig>' \
195
    '  <match target="pattern">' \
196
    '    <test qual="any" name="family"><string>Geist Sans</string></test>' \
197
    '    <edit name="family" mode="assign" binding="same"><string>Geist</string></edit>' \
198
    '  </match>' \
199
    '</fontconfig>' \
200
    > /etc/fonts/local.conf; \
201
  fc-cache -f >/dev/null; \
202
  fc-match --format '%{family}\n' 'Geist Sans' | grep -qx 'Geist'; \
203
  rsvg-convert --version
204
205
# Codex, for the SCV deployment lane (SCV-001), pinned by version and
206
# checksum like every other external artifact in this image.
160 207
RUN set -eu; \
161 208
  case "${TARGETARCH:-$(dpkg --print-architecture)}" in \
162 209
    amd64) codex_arch=x86_64; checksum=bd758d53d56e41dc65e045f4589df79a038ed197a011adcb52a258e6ad64cfda ;; \
config/runtime.exs modified +25

@@ -310,6 +310,30 @@ if config_env() == :prod and runtime_role == :web do

310 310
      _invalid -> raise "environment variable OPENAGENTS_FORGE_ROLLING_PROVIDER is not admitted"
311 311
    end
312 312
313
  # Optional one-way GitHub mirrors, keyed by bare repository name. URLs must
314
  # stay credential-free (RuntimeConfig enforces it): authentication belongs
315
  # to the nodes' git credential helper or SSH key, never to this value.
316
  forge_mirror_urls =
317
    case optional_text.("OPENAGENTS_FORGE_MIRROR_URLS_JSON") do
318
      nil ->
319
        %{}
320
321
      encoded ->
322
        case Jason.decode(encoded) do
323
          {:ok, urls} when is_map(urls) and map_size(urls) >= 0 ->
324
            valid? = Enum.all?(urls, fn {repo, url} -> is_binary(repo) and is_binary(url) end)
325
326
            if valid? do
327
              Map.new(urls, fn {repo, url} -> {repo, url} end)
328
            else
329
              raise "environment variable OPENAGENTS_FORGE_MIRROR_URLS_JSON must map repository names to credential-free git URL strings"
330
            end
331
332
          _invalid ->
333
            raise "environment variable OPENAGENTS_FORGE_MIRROR_URLS_JSON must be a JSON object from repository name to credential-free git URL"
334
        end
335
    end
336
313 337
  rolling_instances =
314 338
    case optional_text.("OPENAGENTS_GCP_ROLLING_INSTANCES_JSON") do
315 339
      nil ->

@@ -390,6 +414,7 @@ if config_env() == :prod and runtime_role == :web do

390 414
    forge_public_paths: Map.new(forge_repos, &{&1, []}),
391 415
    forge_internal_git_url: required_text.("OPENAGENTS_FORGE_INTERNAL_GIT_URL"),
392 416
    forge_operator_token: optional_text.("OPENAGENTS_FORGE_OPERATOR_TOKEN"),
417
    forge_mirror_urls: forge_mirror_urls,
393 418
    forge_data_dir: required_text.("OPENAGENTS_FORGE_DATA_DIR"),
394 419
    forge_build_dir: required_text.("OPENAGENTS_FORGE_BUILD_DIR"),
395 420
    forge_build_queue_dir: required_text.("OPENAGENTS_FORGE_BUILD_QUEUE_DIR"),
docs/2026-08-21-open-graph-cards.md modified +7 -6

@@ -5,9 +5,8 @@ Date: 2026-08-21

5 5
Status: Implemented. Meta tags ship on every public page; the signed,
6 6
content-versioned card endpoint serves repository, issue, blob, and commit
7 7
cards; rasterization runs through librsvg with a committed fallback card when
8
the binary is absent. One operations requirement remains: fleet images need
9
`librsvg2-bin` (and, for exact brand type, the Geist TTFs) installed — until
10
then nodes serve the fallback card by design.
8
the binary is absent. The release image carries librsvg, fontconfig, and the
9
pinned Geist TTFs; dynamic cards render wherever a rebuilt image runs.
11 10
12 11
## What links to our pages look like today
13 12

@@ -208,8 +207,10 @@ The implementation notes below record what landed and where.

208 207
209 208
Remaining operations work, tracked here so it cannot be forgotten:
210 209
211
* Add `librsvg2-bin` (and optionally the Geist TTF faces) to the release
212
  image. Until that lands, production nodes serve the fallback card for all
213
  dynamic URLs — correct, branded, but generic.
210
* The release image now carries `librsvg2-bin`, `fontconfig`, and the pinned
211
  Geist TTFs (v1.7.2, zip and per-file digests checked in the Dockerfile) with
212
  a fontconfig alias from the CSS family name "Geist Sans" to the files'
213
  internal name "Geist". Dynamic cards render on any node running a rebuilt
214
  image; deploy the rebuilt image and the staging check below goes green.
214 215
* The staging check should assert a dynamic card URL returns bytes other
215 216
  than the committed fallback, proving rasterization end to end.
docs/runtime-configuration.md modified +26

@@ -209,6 +209,7 @@ discovery, node identity, cookie, and bounded distribution ports.

209 209
| `OPENAGENTS_FORGE_OWNER` | Exactly `OpenAgentsInc` |
210 210
| `OPENAGENTS_FORGE_INTERNAL_GIT_URL` | HTTP(S) service URL with no embedded credentials |
211 211
| `OPENAGENTS_FORGE_OPERATOR_TOKEN` | Secret required when the forge is enabled; empty while disabled |
212
| `OPENAGENTS_FORGE_MIRROR_URLS_JSON` | Optional JSON object from repository name to credential-free git mirror URL; empty disables one-way GitHub mirroring |
212 213
| `OPENAGENTS_FORGE_BUILD_EXECUTOR` | `sidecar` |
213 214
| `OPENAGENTS_FORGE_ARTIFACT_STORE` | `local` until the durable store gate replaces it |
214 215
| `OPENAGENTS_FORGE_WAL_ADAPTER` | `local` or `gcs` |

@@ -246,6 +247,31 @@ Ra and fleet deployment additionally require a stable `RELEASE_NODE`, a

246 247
records returned by `DNS_CLUSTER_QUERY`. The readiness report records only
247 248
whether those settings passed; it never prints their values.
248 249
250
### Forge GitHub mirroring
251
252
Setting `OPENAGENTS_FORGE_MIRROR_URLS_JSON` turns on one-way mirroring: every
253
accepted forge push is followed by a best-effort `git push --mirror` to the
254
configured URL, and `OpenAgents.Forge.MirrorWatch` compares refs every five
255
minutes, retries drift, and raises one `forge_mirror_lagging` incident per lag
256
episode past fifteen minutes. Mirror freshness appears on the public status
257
page as `current` or `lagging`; with no URLs configured it reads `off`.
258
259
Two rules are load-bearing:
260
261
1. **The URL carries no credential.** RuntimeConfig refuses mirror URLs that
262
   embed userinfo. Authentication belongs to the nodes: give each fleet node
263
   a read-write GitHub credential through a git credential helper or an SSH
264
   deploy key for the target repository. The mirror push runs as the same
265
   user as the release.
266
2. **The mirror is force-pushed.** `--mirror` makes the configured remote
267
   exactly match the forge. Configure it only on a repository you accept
268
   being overwritten by forge state — for this project, GitHub's
269
   `OpenAgentsInc/openagents.com`.
270
271
Staging receives the value through the optional
272
`forge_mirror_urls_json` Terraform variable; production sets the environment
273
variable directly in its fleet configuration.
274
249 275
## Local defaults
250 276
251 277
Development and test remain sanctioned degraded modes: the database may use a
infra/staging/main.tf modified +4 -3

@@ -685,9 +685,10 @@ resource "google_compute_instance" "fleet" {

685 685
    openagents-runtime-secret = google_secret_manager_secret.runtime["openagents-staging-fleet-config"].secret_id
686 686
    openagents-builder-secret = google_secret_manager_secret.runtime["openagents-staging-builder-config"].secret_id
687 687
    startup-script = templatefile("${path.module}/templates/fleet-startup.sh.tftpl", {
688
      network_cidr = var.network_cidr
689
      project_id   = var.staging_project_id
690
      region       = var.region
688
      network_cidr           = var.network_cidr
689
      project_id             = var.staging_project_id
690
      region                 = var.region
691
      forge_mirror_urls_json = var.forge_mirror_urls_json
691 692
    })
692 693
  }
693 694
infra/staging/templates/fleet-startup.sh.tftpl modified +8

@@ -149,6 +149,14 @@ RELEASE_DISTRIBUTION=name

149 149
RELEASE_NODE=openagents@$instance_ip
150 150
EOF
151 151
152
# Optional one-way GitHub mirrors, keyed by repository name. The value is
153
# deployment policy (credential-free URLs by construction -- RuntimeConfig
154
# refuses anything else), so it rides the plain environment when the
155
# operator sets it.
156
if [ -n "${forge_mirror_urls_json}" ]; then
157
  echo "OPENAGENTS_FORGE_MIRROR_URLS_JSON=${forge_mirror_urls_json}" >>/run/openagents/runtime.env
158
fi
159
152 160
export DOCKER_CONFIG=/run/openagents/docker-config
153 161
mkdir -p "$DOCKER_CONFIG"
154 162
chmod 0700 "$DOCKER_CONFIG"
infra/staging/variables.tf modified +6

@@ -112,3 +112,9 @@ variable "labels" {

112 112
  type        = map(string)
113 113
  default     = {}
114 114
}
115
116
variable "forge_mirror_urls_json" {
117
  description = "Optional JSON object from repository name to credential-free git mirror URL (e.g. {\"openagents.com\":\"https://github.com/OpenAgentsInc/openagents.com.git\"}). Empty disables mirroring."
118
  type        = string
119
  default     = ""
120
}
ops/ci/push-remote-check.sh modified +6 -5

@@ -9,11 +9,12 @@ set -eu

9 9
# and the divergence stays invisible until a clone disagrees with the site.
10 10
#
11 11
# The forge can mirror `main` to GitHub (`OpenAgents.Forge.Pushes.mirror_now/1`,
12
# watched by `OpenAgents.Forge.MirrorWatch`), but `:forge_mirror_urls` is empty
13
# in `config/config.exs` and no environment sets it, so no mirror runs today.
14
# Until one is configured, GitHub receives only what someone pushes to it, and
15
# `mirror_now/1` is a `git push --mirror` -- a force push -- so a configured
16
# mirror would overwrite whatever a direct push left there.
12
# watched by `OpenAgents.Forge.MirrorWatch`): an operator sets
13
# `OPENAGENTS_FORGE_MIRROR_URLS_JSON` for the environment and every forge push
14
# is mirrored one-way, with lag detection on the status page. Until that
15
# variable is set, GitHub receives only what someone pushes to it, and
16
# `mirror_now/1` is a `git push --mirror` -- a force push -- so configuring it
17
# makes the mirror authoritative over whatever a direct push left there.
17 18
#
18 19
# Git hands a pre-push hook the remote's name and URL on argv. Called directly,
19 20
# take the same two arguments; a name alone is resolved through `git remote`.
test/openagents/staging_candidate_contract_test.exs modified +13 -1

@@ -70,7 +70,7 @@ defmodule OpenAgents.StagingCandidateContractTest do

70 70
    startup = File.read!("infra/staging/templates/fleet-startup.sh.tftpl")
71 71
    outputs = File.read!("infra/staging/outputs.tf")
72 72
73
    assert terraform =~ "network_cidr = var.network_cidr"
73
    assert terraform =~ ~s(network_cidr           = var.network_cidr)
74 74
    assert startup =~ "instance_ip=$(metadata instance/network-interfaces/0/ip)"
75 75
    assert startup =~ ~s(iptables -C INPUT -s "${network_cidr}")
76 76
    assert startup =~ "--dports 4000,4369,9100:9115 -j ACCEPT"

@@ -81,6 +81,18 @@ defmodule OpenAgents.StagingCandidateContractTest do

81 81
    assert outputs =~ ~s("openagents@${instance_ip}" => instance_name)
82 82
  end
83 83
84
  test "the optional forge mirror rides the fleet environment untouched" do
85
    variables = File.read!("infra/staging/variables.tf")
86
    terraform = File.read!("infra/staging/main.tf")
87
    startup = File.read!("infra/staging/templates/fleet-startup.sh.tftpl")
88
89
    # Deployment policy, not a secret: the JSON carries credential-free URLs
90
    # only (RuntimeConfig refuses userinfo), so it flows as plain env.
91
    assert variables =~ "forge_mirror_urls_json"
92
    assert terraform =~ "forge_mirror_urls_json = var.forge_mirror_urls_json"
93
    assert startup =~ "OPENAGENTS_FORGE_MIRROR_URLS_JSON=${forge_mirror_urls_json}"
94
  end
95
84 96
  test "IAP SSH reaches the fleet and deploy controller" do
85 97
    terraform = File.read!("infra/staging/main.tf")
86 98

This page updates live while a promote is in flight · changelog