Persönliche Landingpage und Portfolio. Astro, statisch gebaut, selbst gehostet auf dem vServer hinter Traefik.
Forgejo ist die Single Source of Truth.
git.joshuahirsig.xyz/joshii/joshuahirsig.xyz ← hierhin pushen (origin)
│
└── Push-Mirror ──► github.com/joshii-h/joshuahirsig.xyz (nur Spiegel)
Der Mirror läuft mit Force und überschreibt GitHub bedingungslos — er feuert
sofort bei jedem Push, das 8-Stunden-Intervall ist nur der Fallback. Ein Push
nach GitHub geht deshalb garantiert verloren. Damit das nicht versehentlich
passiert, steht die Push-URL des github-Remotes lokal auf einem ungültigen
Wert; ein Push dorthin scheitert laut statt still.
git remote -v
# origin ssh://git@git.joshuahirsig.xyz:5478/joshii/joshuahirsig.xyz.git
# github https://github.com/joshii-h/joshuahirsig.xyz.git (fetch)
# github DISABLED-mirror-target-push-to-origin (push)npm install
npm run dev # lokaler Dev-Server
npm run build # statischer Build nach dist/
npm run preview # Build lokal ansehenInhalte liegen als Markdown in src/content/ — je Sprache ein Unterordner
(en/, de/). Ein neues Projekt ist eine neue .md-Datei, kein Code.
Achtung: Die Kacheln rendern nur das Frontmatter, nicht den Markdown-Body. Alles Sichtbare gehört also in
summary,role,techusw. Text unterhalb des Frontmatters erscheint nirgends — er eignet sich als Notiz, aber nicht als Inhalt.
Push auf main → Forgejo Action (.forgejo/workflows/deploy.yml) baut die Seite
und legt dist/ auf dem Host unter ~/Docker/Website/dist ab, dann Neustart des
website-Containers. Es wird kein SSH-Key benötigt: Der Runner läuft auf
demselben Host und bekommt den Docker-Socket gemountet.
dist/ ist bewusst nicht eingecheckt.
Der Workflow deployt nur dist/. docker-compose.yaml und nginx.conf
bringt er nicht auf den Server, und docker restart liest ohnehin keine
Compose-Änderungen ein. Solange der Server noch die alte Compose fährt, mountet
nginx weiterhin das ganze Repo-Verzeichnis als Webroot und liefert die alte
Seite aus — der Deploy läuft dabei grün durch, weil die alte Startseite
antwortet. Diese Migration ist deshalb einmalig von Hand nötig:
ssh vserver
cd ~/Docker/Website
# 1) Die bisher untracked Configs sichern; sie kommen jetzt aus dem Repo
# und würden den Pull sonst blockieren.
mkdir -p ~/Docker/_backups/website-cutover
cp nginx.conf docker-compose.yaml ~/Docker/_backups/website-cutover/
rm -f nginx.conf docker-compose.yaml nginx.conf.bak-premas
# 2) Neuen Stand holen (entfernt auch index.html, style.css und media/).
git pull origin main
# 3) Recreate statt restart — nur so greift der geänderte Volume-Mount.
docker compose up -d --force-recreate
# 4) Prüfen: Webroot muss jetzt auf dist zeigen, nicht auf das Repo.
docker inspect website --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'Danach gilt der normale Weg: Push auf main → Action baut und deployt.
Ist dist/ zum Zeitpunkt von Schritt 3 noch nicht vorhanden, liefert nginx bis
zum ersten Action-Lauf 404. Deshalb zuerst mergen (Action läuft, legt dist/
an) und erst danach die Schritte oben ausführen.
| Datei | Zweck |
|---|---|
docker-compose.yaml |
nginx-Container, Traefik-Labels, www-Redirect |
nginx.conf |
Matrix-Delegation, Uptime-Kuma-Status-Proxy |
nginx.conf liefert /.well-known/matrix/server und /.well-known/matrix/client
aus. Diese Domain delegiert damit die Föderation an matrix.joshuahirsig.xyz und
verweist auf MAS und LiveKit. Geht das kaputt, bricht die Föderation still —
keine Fehlermeldung, nur Server, die einen nicht mehr erreichen.
Der Deploy-Workflow prüft beide Endpunkte nach jedem Deploy und schlägt fehl, wenn sie nicht stimmen. Nach manuellen Änderungen zusätzlich prüfen:
curl -s https://joshuahirsig.xyz/.well-known/matrix/server
curl -s https://joshuahirsig.xyz/.well-known/matrix/clientnginx.conf reicht /api/status/… intern an Uptime Kuma weiter, weil dessen API
keine CORS-Header setzt und ein direkter Browser-Fetch auf
status.joshuahirsig.xyz deshalb blockiert würde. Bewusst auf die zwei
benötigten Endpunkte begrenzt.