Merge pull request 'Build immutable Wiki images and deploy via Portainer GitOps' (#2) from feat/portainer-gitops into main
Build and Push MkDocs Site / build-and-push (push) Successful in 24s

This commit was merged in pull request #2.
This commit is contained in:
rewa81 committed 2026-10-11 13:37:43 +00:00
commit a246132ff1
5 files changed
+117 -26

No files matched your search

+17 -25
View File
@@ -6,6 +6,10 @@ on:
branches:
- main # Trigger on pushes to the main branch
concurrency:
group: wiki-build-${{ github.ref }}
cancel-in-progress: false
jobs:
build-and-push:
runs-on: ubuntu-latest
@@ -52,33 +56,21 @@ jobs:
password: ${{ secrets.AUTH_TOKEN }}
- name: Build and push Docker images
id: image
uses: docker/build-push-action@v6.9.0
with:
push: true
context: .
dockerfile: Dockerfile
tags: gitea.rwcloud.ch/homelab/wiki:latest
# TODO: Deploy muss neu geschrieben werden
# deploy:
# needs: build-and-push
# runs-on: ubuntu-latest
# steps:
# - name: executing remote ssh commands using ssh key
# uses: appleboy/ssh-action@v1.2.0
# with:
# host: ${{ secrets.SSH_HOST }}
# username: ${{ secrets.SSH_USERNAME }}
# key: ${{ secrets.SSH_KEY }}
# port: ${{ secrets.SSH_PORT }}
# script: |
# cd ~/ansible
# git pull
# source ./.venv/bin/activate
# pip install -r requirements.txt
# ansible-playbook -i inventory/hosts.yml --vault-password-file ./.vault_pass playbooks/proxmox/deploy_docker_services.yml --tags oauth2-wiki,wiki-uninstall
# ansible-playbook -i inventory/hosts.yml --vault-password-file ./.vault_pass playbooks/proxmox/deploy_docker_services.yml --tags oauth2-wiki,wiki-install
# deactivate
file: Dockerfile
labels: |
org.opencontainers.image.revision=${{ github.sha }}
tags: |
gitea.rwcloud.ch/homelab/wiki:sha-${{ github.sha }}
gitea.rwcloud.ch/homelab/wiki:latest
- name: Promote Wiki image to Portainer GitOps
if: github.ref_name == 'main'
env:
GITEA_DEPLOY_TOKEN: ${{ secrets.AUTH_TOKEN }}
WIKI_IMAGE: gitea.rwcloud.ch/homelab/wiki:sha-${{ github.sha }}@${{ steps.image.outputs.digest }}
run: python3 scripts/promote_wiki.py
+1 -1
View File
@@ -1,4 +1,4 @@
FROM caddy:alpine
FROM caddy:2.11.6-alpine
COPY ./site /usr/share/caddy
COPY Caddyfile /etc/caddy/Caddyfile
@@ -0,0 +1,31 @@
# Wiki-Build und Deployment über Portainer
Das Wiki läuft als Stack `wiki` auf `rwcloudsrv112` (100.72.156.2), erreichbar
unter https://wiki.rwcloud.ch. Caddy und Platzierung werden in
`homelab/portainer-hosted` verwaltet. Der alte SSH-/Ansible-Deploy entfällt.
Ein Push auf `main` startet `.gitea/workflows/ci.yml`: Abhängigkeiten installieren,
Multirepo-Dokumentation per vorhandenem `GIT_SSH_KEY` laden, `mkdocs build`,
Container bauen und in `gitea.rwcloud.ch/homelab/wiki` publizieren. Der Build
veröffentlicht `sha-<Quellcommit>` und `latest`, mit Quell-SHA als OCI-Label.
Erst nach erfolgreichem Push aktualisiert `scripts/promote_wiki.py` ausschließlich
`WIKI_IMAGE` der Wiki-Zeile in `hosts/deployments.yaml` auf `main` des
Deployment-Repos. Die Referenz enthält Tag und festen Registry-Digest.
Dieser Commit startet dort `deploy`; Portainer aktualisiert den Wiki-Stack.
Der vorhandene Secret `AUTH_TOKEN` benötigt read/write packages und Schreibrecht
auf `homelab/portainer-hosted`. Registry-Login bleibt beim bisherigen Benutzer
`build.user`. Portainer-Zugangsdaten bleiben ausschließlich im Deployment-Repo.
Überholte Builds werden anhand der aktuellen Main-SHA vor Promotion übersprungen.
Die Dateiversion schützt vor konkurrierenden Matrixänderungen.
Kontrolle: zuerst Wiki-Build-Run, dann automatisch erzeugten Matrix-Commit und
Deployment-Run prüfen; anschließend https://wiki.rwcloud.ch öffnen und eine
Unterseite/Suche testen. Ein grüner Build allein beweist noch kein Deployment.
Bei Buildfehler bleibt das bisher festgelegte Image. Bei fehlendem Schreibrecht
schlägt die Promotion sichtbar fehl. Kein Zurückschreiben ins Wiki-Quellrepo.
Rollback: früheren Wiki-Digest aus der Git-Historie in der Matrix eintragen und
committen. Das Wiki ist statisch; es hat keine Geschäftsdatenbank oder Datenvolumes.
Caddy-Konfiguration und Zertifikatsverteilung sind im Deployment-Runbook
`docs/runbooks/rwcloudsrv112.md` beschrieben.
+1
View File
@@ -62,6 +62,7 @@ nav:
- Gitea:
- Gitea Actions: devops/tools/gitea/gitea_actions.md
- Nützliches: devops/tools/gitea/gitea_basics.md
- Wiki Deployment: devops/tools/gitea/wiki-deployment.md
- Backup und Restore: devops/tools/gitea/backup_restore.md
- GitHub:
- Github Codespace: devops/tools/github/codespace.md
+67
View File
@@ -0,0 +1,67 @@
"""Promote a successful Wiki image build to the Portainer placement matrix."""
import base64
import json
import os
import re
import urllib.error
import urllib.request
API = "https://gitea.rwcloud.ch/api/v1"
TARGET = "/repos/homelab/portainer-hosted/contents/hosts/deployments.yaml"
def update_matrix(content, image):
if not re.fullmatch(r"gitea\.rwcloud\.ch/homelab/wiki:sha-[0-9a-f]{40}@sha256:[0-9a-f]{64}", image):
raise ValueError("Invalid immutable Wiki image reference")
section = re.search(r"(?m)^wiki:\n(?:[ \t].*\n|\n)*", content)
if section is None:
raise ValueError("Wiki stack missing from placement matrix")
updated, count = re.subn(r"(?m)^( WIKI_IMAGE: ).+$", lambda m: m[1] + image, section.group())
if count != 1:
raise ValueError("Expected exactly one WIKI_IMAGE")
return content[:section.start()] + updated + content[section.end():]
def request(method, path, token, payload=None):
req = urllib.request.Request(
API + path, method=method,
data=json.dumps(payload).encode() if payload is not None else None,
headers={"Authorization": "token " + token, "Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=60) as response:
return json.load(response)
def main():
token = os.environ["GITEA_DEPLOY_TOKEN"]
source = os.environ["GITHUB_SHA"]
image = os.environ["WIKI_IMAGE"]
for attempt in range(3):
# A slower, older build must never replace a newer main revision.
latest = request("GET", "/repos/rewa81/mkdocs-wiki/branches/main", token)
if latest["commit"]["id"] != source:
print("Newer main revision exists; skipping obsolete build promotion.")
return
current = request("GET", TARGET + "?ref=main", token)
content = base64.b64decode(current["content"]).decode()
updated = update_matrix(content, image)
if updated == content:
print("Wiki image already selected; no deployment commit needed.")
return
try:
result = request("PUT", TARGET, token, {
"branch": "main", "sha": current["sha"],
"content": base64.b64encode(updated.encode()).decode(),
"message": f"deploy(wiki): mkdocs-wiki main {source}",
})
except urllib.error.HTTPError as exc:
if exc.code == 409 and attempt < 2:
continue
raise
print("Wiki deployment requested by commit " + result["commit"]["sha"])
return
raise RuntimeError("Placement matrix changed concurrently; promotion failed")
if __name__ == "__main__":
main()