Build immutable Wiki images and deploy via Portainer GitOps #2
No files matched your search
+17
-25
@@ -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
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user