Moving the forge¶
Before the move, most Purple Lantern repositories were on GitHub, and a few on GitLab, and a push to GitHub was what put a page on the web, through a US host. That is the arrangement the scrap for this layer calls “identity, forge, repository, build, deployment, website, all one account”. A forge can hold sources, log developers in, run a build, hand out deployment credentials, receive webhooks, and end up as the only place from which a production system can be changed. A control point, not a filing cabinet. The question is whether, if the forge vanished tomorrow, everything could still be built, deployed and maintained, and how fast.
Somewhere to push to¶
A forge is a service built around Git: repositories, access control, pull requests, issues, releases, webhooks, authentication, sometimes CI and package storage. Git itself needs none of it. A repository can live on the workstation, on a European forge, on another forge, or on several at once, and that is what makes an exit possible. Git is not GitHub.
Once the feature list was held up against what Purple Lantern actually did with it, two items were left. The forge is a Git remote over SSH, and it sends the webhook that tells the host to build. No issues, no pull requests, no releases, no CI, no OAuth logins. That keeps the forge close to what Git needs, which is somewhere to push to.
The issue tracker was the one item that took a decision. Issues get treated as disposable because the code is what people think they own. For a long-running project they hold design decisions, vulnerability details, operational notes and release plans, and those vanish with the forge while the code survives. Notes that would otherwise be issues live as Markdown in the repository, which works anywhere Git works and is considerably harder to hold hostage than a project-management platform.
One too many¶
The sites went to CodeFloe: the five documentation sites and the Frozen-Flask main site
under one organisation, the blog and the tools under a personal account. CodeFloe is run
by devXY GmbH in Zug and runs Forgejo on Hetzner
in Germany. The labs and games went
to Codeberg, a non-profit association in Berlin, also
on Forgejo, whose own name is a .org and so sits in a namespace run
by Public Interest Registry from Reston, Virginia. Nothing is
mirrored between the two. Each repository exists at one forge and in the clones on the
workstation, and that is its redundancy. The old copies are still on GitHub and GitLab: unmaintained, kept because
links elsewhere still point at them, receiving nothing.
Two forges is a split. Websites in one place and labs in another costs two accounts, two recovery paths, two sets of keys and two places to look, for one person, and a third forge would not mend that.
Runners¶
If one forge owns push, runner, build and deployment, it is the production control plane, which is what GitHub had been. Now CodeFloe holds the repository, a webhook calls statichost, and statichost builds and publishes. The forge has no access to the host, and the host has no write access to the repository.
Both forges offer runners, differently. Codeberg’s hosted Forgejo Actions are, in its own words, provided in a limited fashion in open alpha because of outstanding security issues and bus factor, and its docs point at the shared Woodpecker instance instead, which is access by request, linux/amd64 only, and provided as-is. A self-hosted runner can be attached to Codeberg from a machine with no public address. CodeFloe runs hosted Forgejo Actions runners in containers, with four CPUs and three gigabytes per job and time, storage and concurrency unlimited for now and subject to change, and Crow CI beside them.
Neither got used. A Forgejo Actions workflow runs in a container image that has to carry Node for most actions to
work, and a relative uses: line resolves against data.forgejo.org by default, Forgejo’s own mirror of the common
actions, or against GitHub when written out in full, as
the Forgejo docs describe. A workflow at a European forge would
bring a container registry and a second forge into the build chain by the side door, to run checks that already
run twice. For seven static sites the checks are a Sphinx build with warnings as errors, a link check, and
zola check, and they run on the workstation before the push exists. The build of record is the host’s, which is
also the deployment. If a heavier check ever turns up, a self-hosted runner at home is the route, and the forge sees
only the result.
Still from the US¶
This is where US dependency walks back into a European stack, and the move did not remove it. The Sphinx sites pull
from PyPI on every build at statichost. The blog and the main site are built on the workstation and their output
committed, so their dependencies, the Zola binary from GitHub releases, Flask from PyPI, Node for the search index,
are the workstation’s. The one container in the stack is the build image, python:3.12-slim from Docker Hub, named
in statichost.yml, pinned by tag where a digest would pin it properly. A European forge and a Swedish host still
produce a chain that runs CodeFloe, statichost, PyPI, package. The provider of the source is not the provider of
the software the source is built with.
None of those registries needs replacing. What went into the scraps is ordinary: pinned versions in
requirements.txt, a local package cache, a kept copy of the Zola binary, and for each project a look through
requirements.txt, pyproject.toml, .gitmodules and the build configuration for the edge that runs CodeFloe,
dependency, GitHub. The supply chain page goes through it package by package. Nobody is rebuilding
PyPI in the basement. Nobody wants to find out that a site cannot be rebuilt because one package has gone, either.
Keys¶
Commits are not signed, and that stayed as it was. If they were, one signing key would be recognised by CodeFloe, Codeberg, local Git and any future forge, the forge account would stop being the proof of authorship, and moving forges would not give the history a new author. That is the case for signing. For a one-person repository the forge account already answers who pushed, and the case is weak there.
SSH did change. A forge recognises a key; it does not own it. The SSH configuration on the workstation now has one key per remote, CodeFloe and Codeberg on separate identities, and a change of forge is a change of hostname in that file. The key is easier to move than the account.
Both forge accounts have direct credentials and are reached over SSH; there is no OAuth. Where OAuth is used, a European forge reached through “sign in with Google” depends on a Google account, which depends on a recovery email, which depends on another provider. A direct account with a password, a hardware key and recovery codes is the shorter chain.
The webhook¶
The webhook is where development crosses into operations. A push at CodeFloe calls the statichost builder, which publishes. The webhook is scoped to one repository, its URL is a secret that went into the recovery kit, and the forge holds no credential for the host. If the webhook stops, the build can be started from the statichost dashboard or the site rebuilt on the workstation and uploaded, so a broken webhook is a delay. The webhook is a trigger and nothing more.
No mirrors¶
Mirrors are the cheapest independence a Git user can buy. Forgejo does them natively: a repository created through “New Migration” with the mirror box ticked is a pull mirror, fetched periodically from its source by the mirroring forge with its own credentials, so it keeps working when the source forge does not. A push mirror is the other way round, pushed by the source, and stops when the source does.
Two European Forgejo hosts would take such a mirror. gitbuild.dev is run by StennMedia, a one-person company in Breda registered with the Dutch Chamber of Commerce, on compute from netcup in Karlsruhe with backups on a Hetzner storage box in Germany; the person who writes its code also runs its servers, which is the same property statichost has. Codebahn is Hackerman AB in Gothenburg, on a maintained Forgejo fork, hosted on Scaleway in France and Hetzner in Germany with every sub-processor EU-incorporated. Either shares no owner, datacentre or bank with CodeFloe or Codeberg.
Purple Lantern set up neither. Since nothing but code exists at any forge, a full clone holds everything a mirror would. The clones on the workstation, with the workstation’s own backups, are the mirror: CodeFloe is primary and the local clones are the backup. A third forge would be one more account to recover, one more token to rotate and one more organisation on the graph, in exchange for a copy of something already held. That reverses on the day the local copies stop being enough, and those two hosts are where it would go. The old GitHub and GitLab copies are neither mirror nor backup. They are where the repositories used to be.
Three accounts¶
local Git
│
CodeFloe
│
webhook
│
statichost
│
build
│
purplelantern.eu
Three accounts can change what a visitor sees. The CodeFloe account, by pushing to the branch statichost builds. The statichost account, by changing the build configuration, the published directory or the attached domains. The Infomaniak account, by pointing the name somewhere else. None of them holds a credential for the other two, and the workstation can produce the site without any of them, so a compromised forge account is a bad commit to revert, not a lost website. What the three can share is the road back in: each recovers through a recovery address, and where those addresses are one mailbox, that mailbox is the fourth account on the diagram and the one that outranks the others. The diagram is drawn with that in mind.
Pushed¶
local workstation
│
┌──────────┴──────────┐
Git + keys local build
│ │
CodeFloe │
│ │
webhook │
│ │
statichost │
│ │
└──────────┬──────────┘
production
Keys independent of every forge, the build reproducible on the workstation, complete clones held locally and backed up as the mirror, dependencies pinned and cached, and the deployment replaceable without moving the source. Before the move, a closed GitHub account would have been a production incident. Now the same event closes an old, unmaintained copy that nothing depends on. If CodeFloe closed the account tomorrow, the workstation clone is pushed to a fresh repository at another Forgejo host, the statichost site is pointed at it and the webhook registered there, and the next push builds. It takes an afternoon, on the condition that the workstation or its backup is intact, which is the condition the no-mirror decision accepts. The forge can be European, the build host can be European, and some package registry somewhere can still be American. That is tolerable when each layer can be replaced on its own.
Lancre is full of Oggs. There is an Ogg in every cottage, they all know the same songs, and losing one Ogg, although sad for the Ogg, changes nothing the village can remember.