Compare commits
23 Commits
automation/ses
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e7d84d665 | |||
| e4ae304954 | |||
| bdca12837e | |||
| 4f134af7bc | |||
| 905a1051a8 | |||
| 4919ce9450 | |||
| 03be97799d | |||
| 01901b3821 | |||
| 6dc9f68b12 | |||
| 83a5d86115 | |||
| 2072db4980 | |||
| 00527e46b1 | |||
| 34a278870d | |||
| 736f40f953 | |||
| 73a8b5834a | |||
| b2714ab5af | |||
| bf96cd10e7 | |||
| bb57f319c5 | |||
| e838d3b105 | |||
| 217d633853 | |||
| 3d7af25aed | |||
| 47e5127299 | |||
| 2103a423fe |
@@ -0,0 +1,102 @@
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- Dockerfile
|
||||
- "pipelines/**/requirements.txt"
|
||||
- .gitea/workflows/build-image.yml
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
# Daemonless image builder: kaniko :debug plus the /bin/sleep symlink
|
||||
# act needs for job-container PID 1 (see ci/Dockerfile.kaniko-act).
|
||||
# No docker socket, no privileges.
|
||||
# Deliberately the `mikkeli` copy: the org rebuild of kaniko-act loses
|
||||
# /tmp (kaniko does not persist the empty dir), which breaks the wget
|
||||
# below. See build-kaniko-act.yml. Do not repoint without testing.
|
||||
image: pi5-16.local:3005/mikkeli/kaniko-act:v1.23.2-r2
|
||||
|
||||
steps:
|
||||
- name: Fetch source, build, push
|
||||
# kaniko image has no bash — busybox sh only
|
||||
shell: sh
|
||||
env:
|
||||
# package r/w PAT (user-level secret) — registry push only
|
||||
PKG_TOKEN: ${{ secrets.PKGRW_PAT }}
|
||||
# per-run auto-token, read access to this repo only — source fetch
|
||||
JOB_TOKEN: ${{ github.token }}
|
||||
URL_TO_GITEA: ${{ vars.URL_TO_GITEA }}
|
||||
run: |
|
||||
set -eu
|
||||
HOST="${URL_TO_GITEA#http://}"
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
IMAGE="${HOST}/novoyuuparosk-wiki/novoyuuparosk-wiki-runner"
|
||||
|
||||
# Source via Gitea archive API (no git/node in this image; repo is
|
||||
# private — authenticate with the job's own per-run token)
|
||||
wget -qO /tmp/src.tar.gz \
|
||||
"http://mikkeli:${JOB_TOKEN}@${HOST}/api/v1/repos/novoyuuparosk-wiki/novoyuuparosk-auto-wiki/archive/${GITHUB_SHA}.tar.gz"
|
||||
mkdir -p /tmp/src
|
||||
tar -xzf /tmp/src.tar.gz -C /tmp/src --strip-components=1
|
||||
|
||||
# Registry auth for the push
|
||||
AUTH=$(printf '%s' "mikkeli:${PKG_TOKEN}" | base64 | tr -d '\n')
|
||||
printf '{"auths":{"%s":{"auth":"%s"}}}' "${HOST}" "${AUTH}" \
|
||||
> /kaniko/.docker/config.json
|
||||
|
||||
# --insecure: push target is the plain-HTTP Gitea registry.
|
||||
# Base image pull (docker.io) stays HTTPS — no --insecure-pull.
|
||||
# :latest is a convenience tag for internal CI (the pin job below
|
||||
# runs in it). Publish workflows stay pinned to the immutable sha.
|
||||
/kaniko/executor \
|
||||
--context dir:///tmp/src \
|
||||
--dockerfile /tmp/src/Dockerfile \
|
||||
--destination "${IMAGE}:${SHORT_SHA}" \
|
||||
--destination "${IMAGE}:latest" \
|
||||
--insecure
|
||||
|
||||
echo "Pushed ${IMAGE}:${SHORT_SHA} and ${IMAGE}:latest"
|
||||
|
||||
pin:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
# Freshly built runner image: git + GNU sed baked in, and it has no
|
||||
# non-shell ENTRYPOINT, so it works as a job container as-is (unlike the
|
||||
# kaniko image above). Pulls :latest that the build job just pushed.
|
||||
image: pi5-16.local:3005/novoyuuparosk-wiki/novoyuuparosk-wiki-runner:latest
|
||||
steps:
|
||||
- name: Repoint publish workflows at the new image tag
|
||||
env:
|
||||
# Full-access PAT — needs contents:write to push the pin commit.
|
||||
# github.token is read-only here; PKGRW_PAT is registry-only.
|
||||
FAPAT: ${{ secrets.FAPAT }}
|
||||
URL_TO_GITEA: ${{ vars.URL_TO_GITEA }}
|
||||
run: |
|
||||
set -eu
|
||||
HOST="${URL_TO_GITEA#http://}"
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
|
||||
git clone "http://mikkeli:${FAPAT}@${HOST}/novoyuuparosk-wiki/novoyuuparosk-auto-wiki" repo
|
||||
cd repo
|
||||
|
||||
for f in .gitea/workflows/publish-songs.yml \
|
||||
.gitea/workflows/publish-ses.yml \
|
||||
.gitea/workflows/publish-tech.yml; do
|
||||
sed -i -E "s#(novoyuuparosk-wiki-runner:)[A-Za-z0-9._-]+#\1${SHORT_SHA}#" "$f"
|
||||
done
|
||||
|
||||
if git diff --quiet; then
|
||||
echo "Pins already at ${SHORT_SHA}; nothing to commit."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.email "actions@novoyuuparosk.org"
|
||||
git config user.name "wiki-runner image bot"
|
||||
git commit -am "build: pin runner image to ${SHORT_SHA}"
|
||||
# Pin commit only touches publish-*.yml (on: workflow_call) — triggers
|
||||
# nothing, so no rebuild loop. Fails loudly if master moved meanwhile.
|
||||
git push origin HEAD:master
|
||||
@@ -0,0 +1,65 @@
|
||||
# Rebuilds the kaniko-act builder image.
|
||||
#
|
||||
# Dispatch-only on purpose. This is the image build-image.yml runs inside, so
|
||||
# an automatic trigger could push a broken builder and take the whole build
|
||||
# path down with it. Bump deliberately, verify, then repoint build-image.yml.
|
||||
#
|
||||
# Self-hosting: builds the new kaniko-act using the *previous* kaniko-act, per
|
||||
# the bootstrap note in ci/Dockerfile.kaniko-act.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
VERSION:
|
||||
description: "Tag to publish, e.g. v1.23.2-r2"
|
||||
required: true
|
||||
type: string
|
||||
default: v1.23.2-r2
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
# The `mikkeli` copy is the only known-good kaniko-act: it was built on
|
||||
# the host with docker. The org copy this workflow produced exits 0 but
|
||||
# lacks /tmp, so it cannot build anything — do not point here at it.
|
||||
#
|
||||
# This workflow is consequently NOT usable as-is for a version bump. Fix
|
||||
# ci/Dockerfile.kaniko-act to force /tmp to materialise first, or build
|
||||
# on the host. Kept because the destination and auth wiring are correct.
|
||||
image: pi5-16.local:3005/mikkeli/kaniko-act:v1.23.2-r2
|
||||
|
||||
steps:
|
||||
- name: Fetch source, build, push
|
||||
# kaniko image has no bash — busybox sh only
|
||||
shell: sh
|
||||
env:
|
||||
# package r/w PAT (org-level secret) — registry push only
|
||||
PKG_TOKEN: ${{ secrets.PKGRW_PAT }}
|
||||
# per-run auto-token, read access to this repo only — source fetch
|
||||
JOB_TOKEN: ${{ github.token }}
|
||||
URL_TO_GITEA: ${{ vars.URL_TO_GITEA }}
|
||||
VERSION: ${{ inputs.VERSION }}
|
||||
run: |
|
||||
set -eu
|
||||
HOST="${URL_TO_GITEA#http://}"
|
||||
IMAGE="${HOST}/novoyuuparosk-wiki/kaniko-act"
|
||||
|
||||
wget -qO /tmp/src.tar.gz \
|
||||
"http://mikkeli:${JOB_TOKEN}@${HOST}/api/v1/repos/novoyuuparosk-wiki/novoyuuparosk-auto-wiki/archive/${GITHUB_SHA}.tar.gz"
|
||||
mkdir -p /tmp/src
|
||||
tar -xzf /tmp/src.tar.gz -C /tmp/src --strip-components=1
|
||||
|
||||
# Registry auth for the push
|
||||
AUTH=$(printf '%s' "mikkeli:${PKG_TOKEN}" | base64 | tr -d '\n')
|
||||
printf '{"auths":{"%s":{"auth":"%s"}}}' "${HOST}" "${AUTH}" \
|
||||
> /kaniko/.docker/config.json
|
||||
|
||||
# --insecure: push target is the plain-HTTP Gitea registry.
|
||||
# Base image pull (gcr.io) stays HTTPS — no --insecure-pull.
|
||||
/kaniko/executor \
|
||||
--context dir:///tmp/src \
|
||||
--dockerfile /tmp/src/ci/Dockerfile.kaniko-act \
|
||||
--destination "${IMAGE}:${VERSION}" \
|
||||
--insecure
|
||||
|
||||
echo "Pushed ${IMAGE}:${VERSION}"
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: novoyuuparosk-wiki-runner:latest
|
||||
image: pi5-16.local:3005/novoyuuparosk-wiki/novoyuuparosk-wiki-runner:4f134af
|
||||
|
||||
steps:
|
||||
- name: Checkout ses-light-novel
|
||||
@@ -18,14 +18,14 @@ jobs:
|
||||
FAPAT: ${{ secrets.FAPAT }}
|
||||
URL_TO_GITEA: ${{ vars.URL_TO_GITEA }}
|
||||
run: |
|
||||
git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/mikkeli/ses-light-novel" ses-light-novel
|
||||
git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/novoyuuparosk-wiki/ses-light-novel" ses-light-novel
|
||||
git -C ses-light-novel checkout ${{ inputs.SOURCE_REF }}
|
||||
|
||||
- name: Checkout auto-wiki
|
||||
env:
|
||||
FAPAT: ${{ secrets.FAPAT }}
|
||||
URL_TO_GITEA: ${{ vars.URL_TO_GITEA }}
|
||||
run: git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/mikkeli/novoyuuparosk-auto-wiki" auto-wiki
|
||||
run: git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/novoyuuparosk-wiki/novoyuuparosk-auto-wiki" auto-wiki
|
||||
|
||||
- name: Publish
|
||||
env:
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
WIKI_BOT_USER: ${{ secrets.WIKI_BOT_USER }}
|
||||
WIKI_BOT_PASSWORD: ${{ secrets.WIKI_BOT_PASSWORD }}
|
||||
SOURCE_REF: ${{ inputs.SOURCE_REF }}
|
||||
GITEA_REPO_URL: ${{ vars.URL_TO_GITEA }}/mikkeli/ses-light-novel
|
||||
GITEA_REPO_URL: ${{ vars.URL_TO_GITEA }}/novoyuuparosk-wiki/ses-light-novel
|
||||
run: |
|
||||
python auto-wiki/pipelines/ses/publish.py \
|
||||
--source-dir ses-light-novel \
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: novoyuuparosk-wiki-runner:latest
|
||||
image: pi5-16.local:3005/novoyuuparosk-wiki/novoyuuparosk-wiki-runner:4f134af
|
||||
|
||||
steps:
|
||||
- name: Checkout ncmr-songs
|
||||
@@ -27,14 +27,14 @@ jobs:
|
||||
FAPAT: ${{ secrets.FAPAT }}
|
||||
URL_TO_GITEA: ${{ vars.URL_TO_GITEA }}
|
||||
run: |
|
||||
git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/mikkeli/ncmr-songs" ncmr-songs
|
||||
git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/novoyuuparosk-wiki/ncmr-songs" ncmr-songs
|
||||
git -C ncmr-songs checkout ${{ inputs.SOURCE_REF }}
|
||||
|
||||
- name: Checkout auto-wiki
|
||||
env:
|
||||
FAPAT: ${{ secrets.FAPAT }}
|
||||
URL_TO_GITEA: ${{ vars.URL_TO_GITEA }}
|
||||
run: git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/mikkeli/novoyuuparosk-auto-wiki" auto-wiki
|
||||
run: git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/novoyuuparosk-wiki/novoyuuparosk-auto-wiki" auto-wiki
|
||||
|
||||
- name: Detect changed files
|
||||
id: diff
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
WIKI_BOT_USER: ${{ secrets.WIKI_BOT_USER }}
|
||||
WIKI_BOT_PASSWORD: ${{ secrets.WIKI_BOT_PASSWORD }}
|
||||
SOURCE_REF: ${{ inputs.SOURCE_REF }}
|
||||
GITEA_REPO_URL: ${{ vars.URL_TO_GITEA }}/mikkeli/ncmr-songs
|
||||
GITEA_REPO_URL: ${{ vars.URL_TO_GITEA }}/novoyuuparosk-wiki/ncmr-songs
|
||||
run: |
|
||||
MODE="${{ steps.diff.outputs.mode }}"
|
||||
if [ "$MODE" = "all" ] || [ "${{ inputs.FULL_PUBLISH }}" = "true" ]; then
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: novoyuuparosk-wiki-runner:latest
|
||||
image: pi5-16.local:3005/novoyuuparosk-wiki/novoyuuparosk-wiki-runner:4f134af
|
||||
|
||||
steps:
|
||||
- name: Checkout tech-blogs
|
||||
@@ -18,14 +18,14 @@ jobs:
|
||||
FAPAT: ${{ secrets.FAPAT }}
|
||||
URL_TO_GITEA: ${{ vars.URL_TO_GITEA }}
|
||||
run: |
|
||||
git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/mikkeli/tech-blogs" tech-blogs
|
||||
git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/novoyuuparosk-wiki/tech-blogs" tech-blogs
|
||||
git -C tech-blogs checkout ${{ inputs.SOURCE_REF }}
|
||||
|
||||
- name: Checkout auto-wiki
|
||||
env:
|
||||
FAPAT: ${{ secrets.FAPAT }}
|
||||
URL_TO_GITEA: ${{ vars.URL_TO_GITEA }}
|
||||
run: git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/mikkeli/novoyuuparosk-auto-wiki" auto-wiki
|
||||
run: git clone "http://mikkeli:${FAPAT}@${URL_TO_GITEA#http://}/novoyuuparosk-wiki/novoyuuparosk-auto-wiki" auto-wiki
|
||||
|
||||
- name: Publish
|
||||
env:
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
WIKI_BOT_USER: ${{ secrets.WIKI_BOT_USER }}
|
||||
WIKI_BOT_PASSWORD: ${{ secrets.WIKI_BOT_PASSWORD }}
|
||||
SOURCE_REF: ${{ inputs.SOURCE_REF }}
|
||||
GITEA_REPO_URL: ${{ vars.URL_TO_GITEA }}/mikkeli/tech-blogs
|
||||
GITEA_REPO_URL: ${{ vars.URL_TO_GITEA }}/novoyuuparosk-wiki/tech-blogs
|
||||
run: |
|
||||
python auto-wiki/pipelines/tech/publish.py \
|
||||
--source-dir tech-blogs \
|
||||
|
||||
+4
-1
@@ -1,2 +1,5 @@
|
||||
# claude local settings
|
||||
.claude/
|
||||
.claude/
|
||||
|
||||
# python
|
||||
__pycache__/
|
||||
@@ -6,9 +6,9 @@ CI/CD pipelines that auto-apply commits to https://wiki.novoyuuparosk.org from u
|
||||
|
||||
| Path | Source repo | Purpose | Status |
|
||||
|---|---|---|---|
|
||||
| [`pipelines/songs/`](pipelines/songs/) | `mikkeli/ncmr-songs` | Song lyric pages | v1 live |
|
||||
| [`pipelines/ses/`](pipelines/ses/) | `mikkeli/ses-light-novel` | SES light novel pages | v1 in development |
|
||||
| [`pipelines/tech/`](pipelines/tech/) | `mikkeli/tech-blogs` | Tech blog posts | v1 ready (source repo not yet initialised) |
|
||||
| [`pipelines/songs/`](pipelines/songs/) | `novoyuuparosk-wiki/ncmr-songs` | Song lyric pages | v1 live |
|
||||
| [`pipelines/ses/`](pipelines/ses/) | `novoyuuparosk-wiki/ses-light-novel` | SES light novel pages | v1 in development |
|
||||
| [`pipelines/tech/`](pipelines/tech/) | `novoyuuparosk-wiki/tech-blogs` | Tech blog posts | v1 live |
|
||||
|
||||
Per-pipeline READMEs cover everything specific to that pipeline (source schema, renderer, runtime, decisions). This root README covers only what's cross-cutting.
|
||||
|
||||
@@ -35,9 +35,13 @@ Note: `workflow_call` across private repos was abandoned — the auto-generated
|
||||
|
||||
## Bot identity
|
||||
|
||||
MediaWiki BotPassword issued for user `Dubrowski`, bot name `giteaAutomaton`. Login form: `Dubrowski@giteaAutomaton`.
|
||||
MediaWiki BotPassword issued for user `Mikkeli`, bot name `giteaBot`. Login form: `Mikkeli@giteaBot`.
|
||||
|
||||
Credentials are stored in the Gitea user-scope secret vault under `mikkeli` (`WIKI_BOT_USER`, `WIKI_BOT_PASSWORD`). Not stored in this repo.
|
||||
`Mikkeli` holds the `bot` right, so pipeline edits are flagged as bot edits and stay out of default Recent Changes — `mwclient`'s `page.save()` requests the bot flag and the wiki honours it.
|
||||
|
||||
This section previously named `Dubrowski@giteaAutomaton`, but every bot-flagged revision in the wiki's history is attributed to `Mikkeli`, so that had been stale for some time — the live secret never matched the doc.
|
||||
|
||||
Credentials are stored in the Gitea org-scope secret vault under `novoyuuparosk-wiki` (`WIKI_BOT_USER`, `WIKI_BOT_PASSWORD`). Not stored in this repo.
|
||||
|
||||
## Runner infrastructure
|
||||
|
||||
@@ -49,23 +53,21 @@ Container network mode: `host` — required so job containers can reach `localho
|
||||
|
||||
### Job container image
|
||||
|
||||
All pipelines share a single pre-built Docker image: `novoyuuparosk-wiki-runner:latest`. The `Dockerfile` is at the repo root. It bakes in system deps (git, pandoc, ca-certificates) and all pipeline Python packages so job containers start instantly with no install steps.
|
||||
All pipelines share a single pre-built Docker image, served from the Gitea registry at `pi5-16.local:3005/novoyuuparosk-wiki/novoyuuparosk-wiki-runner`. The `Dockerfile` is at the repo root. It bakes in system deps (git, pandoc, ca-certificates) and all pipeline Python packages so job containers start instantly with no install steps.
|
||||
|
||||
The image is built manually on the Pi and stored in the local Docker daemon (`pull_image: false` in act_runner config). Rebuild after any change to the `Dockerfile` or a pipeline `requirements.txt`:
|
||||
The image builds automatically via [`.gitea/workflows/build-image.yml`](.gitea/workflows/build-image.yml), which triggers on pushes that touch the `Dockerfile`, any pipeline `requirements.txt`, or that workflow itself. It uses kaniko (daemonless, unprivileged) to build and push two tags: an immutable `:<short-sha>` and a moving `:latest`.
|
||||
|
||||
```bash
|
||||
docker build -t novoyuuparosk-wiki-runner:latest \
|
||||
/home/mikkeli/dev/novoyuuparosk-auto-wiki
|
||||
```
|
||||
A follow-up `pin` job then rewrites the `image:` pin in each `publish-*.yml` to the new `:<short-sha>` and commits it back to `master` (using `FAPAT` for contents write). The publish workflows therefore always reference an immutable tag, kept current automatically — no manual bump. The pin commit only touches `workflow_call` files, so it triggers no further runs.
|
||||
|
||||
## Gitea Actions setup (cross-cutting)
|
||||
|
||||
Secrets and variables are scoped to user `mikkeli` (no orgs on this instance), inherited by all repos under that account.
|
||||
Secrets and variables are scoped to the `novoyuuparosk-wiki` org, inherited by all repos under it. Runs belong to the *caller* repo, so a source repo calling a reusable workflow here resolves secrets and variables from its own owner — which is why they live at org scope rather than on this repo.
|
||||
|
||||
**Secrets:**
|
||||
- `WIKI_BOT_USER` = `Dubrowski@giteaAutomaton`
|
||||
- `WIKI_BOT_USER` = `Mikkeli@giteaBot`
|
||||
- `WIKI_BOT_PASSWORD` = the value from *Bot identity* above
|
||||
- `FAPAT` = Full-Access PAT under `mikkeli`, used by source-repo workflows to clone this repo at runtime
|
||||
- `FAPAT` = Full-Access PAT owned by `mikkeli`, used by source-repo workflows to clone this repo at runtime. The PAT stays personal; only its storage scope moved to the org — hence the `mikkeli:` basic-auth username in the clone URLs.
|
||||
- `PKGRW_PAT` = package read/write PAT owned by `mikkeli`, used by `build-image.yml` to push to the container registry
|
||||
|
||||
**Variables:**
|
||||
- `WIKI_BASE_URL` = `https://wiki.novoyuuparosk.org`
|
||||
@@ -86,17 +88,25 @@ Secrets and variables are scoped to user `mikkeli` (no orgs on this instance), i
|
||||
| Runner execution | Docker, added as a service to the existing Gitea docker-compose | 2026-06-09 |
|
||||
| Runner network mode | `host` — job containers need to reach Gitea on localhost | 2026-06-09 |
|
||||
| Secret/runner scope | User-level on `mikkeli` (no orgs on this instance) | 2026-06-09 |
|
||||
| Ownership | This repo and all three source repos moved to the `novoyuuparosk-wiki` org. Secrets/variables re-created at org scope; runner re-registered instance-level so it serves org-owned runs | 2026-08-11 |
|
||||
| **This repo must stay public** | `act_runner` resolves a cross-repo `uses:` by cloning the callee **anonymously** — the job token is not applied. A private callee therefore 404s with `repository not found`, regardless of the caller sharing its owner. Shared ownership does **not** satisfy the read requirement. Alternative if it must be private again: Settings → Actions → General → collaborative owners (Gitea 1.26+), untested here | 2026-08-11 |
|
||||
| Runner cache masks this | The resolved callee is cached at `/root/.cache/act/<owner>-<repo>@<ref>`. Changing owner changes the key, so a working pipeline can break on a clone that had been served from cache for months. Suspect the cache before suspecting permissions | 2026-08-11 |
|
||||
| **Callee edits need a cache flush** | The cache is *not* refreshed per run. Edits to `publish-*.yml` on `master` — including the pin job's own commits — keep executing the stale cached copy until the key changes or the cache is cleared. Observed directly: a run used the previous namespace and tag despite `master` being current. Flush with `docker compose up -d --force-recreate act_runner`; the cache is in the container layer, while `.runner` is in the `/data` volume, so registration survives | 2026-08-11 |
|
||||
| kaniko cannot rebuild kaniko-act | The org rebuild exits 0 but the resulting image has no `/tmp` — kaniko does not persist the empty dir from `mkdir -p -m 1777 /tmp`, so `wget -O /tmp/...` fails. The bootstrap note's claim that version bumps "can be built by the build-image workflow itself" does not hold as written. `kaniko-act` therefore still lives under `mikkeli`; a real bump needs a host `docker build` + push, or a Dockerfile that forces the dir to materialise (e.g. writing a file inside it) | 2026-08-11 |
|
||||
| Container registry | Gitea cannot transfer packages, so images were *rebuilt* into `pi5-16.local:3005/novoyuuparosk-wiki/*` rather than moved. `kaniko-act` bootstrapped via the new dispatch-only `build-kaniko-act.yml`, running in the old `mikkeli` copy; `novoyuuparosk-wiki-runner` came from a normal `build-image.yml` run. Registry auth is still the `mikkeli`-owned `PKGRW_PAT`, hence the `mikkeli:` username in the auth blob | 2026-08-11 |
|
||||
| Old images left in place | The `mikkeli/*` package versions are orphaned but retained — nothing references them, and deleting a container version is irreversible. Safe to purge once the org images have proven themselves | 2026-08-11 |
|
||||
| MediaWiki API path | `api.php` (classic action API) | 2026-06-09 |
|
||||
| Branch naming (this repo) | `automation/<pipeline>` for pipeline-development branches | 2026-06-09 |
|
||||
| Variable naming | `URL_TO_GITEA` not `GITEA_URL` — Gitea blocks `GITEA_`/`GITHUB_` prefixes | 2026-06-09 |
|
||||
| Columns shorthand | Side-by-side columns authored as a ` ```columns ` fenced block, expanded post-Pandoc in shared `lib/wiki.py` (universal across pipelines). No wiki template or PHP extension — runs Pi-side before the API call | 2026-06-14 |
|
||||
|
||||
Per-pipeline decisions live in each pipeline's README.
|
||||
|
||||
## Setup checklist (cross-cutting)
|
||||
|
||||
Via the Gitea web UI logged in as `mikkeli`:
|
||||
Via the Gitea web UI logged in as `mikkeli` (an owner of `novoyuuparosk-wiki`):
|
||||
|
||||
- [x] User-scoped secrets and variables set per *Gitea Actions setup* above
|
||||
- [x] Org-scoped secrets and variables set per *Gitea Actions setup* above
|
||||
- [x] `WIKI_BOT_USER`
|
||||
- [x] `WIKI_BOT_PASSWORD`
|
||||
- [x] `FAPAT` (Full-Access PAT — value not stored in this README; saved directly into the Gitea secret. Regenerate if lost.)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Kaniko executor adapted for Gitea act_runner job containers.
|
||||
#
|
||||
# act starts job containers with entrypoint /bin/sleep and sets
|
||||
# RUNNER_TEMP=/tmp; kaniko's scratch-based image has neither /bin/sleep
|
||||
# (busybox lives under /busybox; /bin/sh is already symlinked) nor /tmp.
|
||||
# This wrapper adds exactly those two. Nothing else changes.
|
||||
#
|
||||
# Bootstrap: the very first build of this image was done manually on the host
|
||||
# (docker build -f ci/Dockerfile.kaniko-act -t <registry>/<owner>/kaniko-act:<ver> .)
|
||||
# because no builder image existed yet. Version bumps are now built by
|
||||
# .gitea/workflows/build-kaniko-act.yml (dispatch-only), which runs in the
|
||||
# previous kaniko-act — confirmed working when the image moved to the org.
|
||||
FROM gcr.io/kaniko-project/executor:v1.23.2-debug
|
||||
SHELL ["/busybox/sh", "-c"]
|
||||
RUN ln -sf /busybox/sleep /bin/sleep && mkdir -p -m 1777 /tmp
|
||||
+45
-1
@@ -1,6 +1,8 @@
|
||||
"""Shared utilities for novoyuuparosk-auto-wiki pipelines."""
|
||||
|
||||
import html
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@@ -8,6 +10,25 @@ import mwclient
|
||||
|
||||
AUTO_BANNER_PREFIX = "{{Auto-generated"
|
||||
|
||||
# A ```columns fenced block is passed through Pandoc verbatim as
|
||||
# <pre class="columns">…</pre>; columns within it are separated by a line of ===.
|
||||
_COLUMNS_BLOCK_RE = re.compile(r'<pre class="columns">(.*?)</pre>', re.DOTALL)
|
||||
_COLUMN_SEP_RE = re.compile(r"^\s*===\s*$", re.MULTILINE)
|
||||
|
||||
# Minimal Markdown emphasis → wikitext, for use inside a verbatim columns block
|
||||
# (Pandoc doesn't process Markdown there). Asterisk style only, single line.
|
||||
# Order matters: bold-italic (***) before bold (**) before italic (*).
|
||||
_BOLD_ITALIC_RE = re.compile(r"\*\*\*(.+?)\*\*\*")
|
||||
_BOLD_RE = re.compile(r"\*\*(.+?)\*\*")
|
||||
_ITALIC_RE = re.compile(r"\*(.+?)\*")
|
||||
|
||||
|
||||
def _md_emphasis_to_wikitext(text: str) -> str:
|
||||
text = _BOLD_ITALIC_RE.sub(r"'''''\1'''''", text)
|
||||
text = _BOLD_RE.sub(r"'''\1'''", text)
|
||||
text = _ITALIC_RE.sub(r"''\1''", text)
|
||||
return text
|
||||
|
||||
|
||||
def strip_first_h1(text: str) -> str:
|
||||
"""Remove the first '# Heading' line and any immediately following blank line."""
|
||||
@@ -21,6 +42,29 @@ def strip_first_h1(text: str) -> str:
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def expand_columns(wikitext: str) -> str:
|
||||
"""Expand ```columns fenced blocks into a flex row of <poem> columns.
|
||||
|
||||
Authors write a fenced code block tagged ``columns``; Pandoc passes its body
|
||||
through verbatim as ``<pre class="columns">…</pre>`` (line breaks and blank
|
||||
lines preserved, inline markup entity-escaped). Columns within the block are
|
||||
separated by a line containing only ``===``. Each column is wrapped in
|
||||
<poem> so its line breaks survive MediaWiki parsing. Content is
|
||||
HTML-unescaped (so inline markup such as <b>…</b> renders rather than
|
||||
appearing as literal text) and Markdown emphasis (``*italic*``, ``**bold**``,
|
||||
``***bold-italic***``) is converted to wikitext, since Pandoc does not
|
||||
process Markdown inside the verbatim block.
|
||||
"""
|
||||
|
||||
def render(match: re.Match) -> str:
|
||||
body = _md_emphasis_to_wikitext(html.unescape(match.group(1)))
|
||||
columns = _COLUMN_SEP_RE.split(body)
|
||||
poems = "".join("<poem>\n" + col.strip("\n") + "\n</poem>" for col in columns)
|
||||
return '<div style="display:flex; gap:3em; align-items:flex-start">' + poems + "</div>"
|
||||
|
||||
return _COLUMNS_BLOCK_RE.sub(render, wikitext)
|
||||
|
||||
|
||||
def markdown_to_wikitext(body: str) -> str:
|
||||
result = subprocess.run(
|
||||
["pandoc", "-f", "markdown", "-t", "mediawiki"],
|
||||
@@ -30,7 +74,7 @@ def markdown_to_wikitext(body: str) -> str:
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"pandoc failed: {result.stderr.strip()}")
|
||||
return result.stdout
|
||||
return expand_columns(result.stdout)
|
||||
|
||||
|
||||
def connect_wiki() -> mwclient.Site:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# pipelines/ses
|
||||
|
||||
Publishes SES light novel pages from `mikkeli/ses-light-novel` to `https://wiki.novoyuuparosk.org` under the `SES:` namespace.
|
||||
Publishes SES light novel pages from `novoyuuparosk-wiki/ses-light-novel` to `https://wiki.novoyuuparosk.org` under the `SES:` namespace.
|
||||
|
||||
- Source-file contract (frontmatter and body conventions): [SCHEMA.md](SCHEMA.md)
|
||||
- Cross-cutting setup (wiki URL, bot identity, runner, Gitea secrets/variables): [repo root README](../../README.md)
|
||||
@@ -24,7 +24,7 @@ Not in scope (intentional):
|
||||
|
||||
## Source repo and branch convention
|
||||
|
||||
Source repo: `mikkeli/ses-light-novel`. The pipeline triggers on push to `master`.
|
||||
Source repo: `novoyuuparosk-wiki/ses-light-novel`. The pipeline triggers on push to `master`.
|
||||
|
||||
No branch convention enforced — work directly on `master` or use whatever branch workflow suits.
|
||||
|
||||
@@ -34,7 +34,7 @@ No branch convention enforced — work directly on `master` or use whatever bran
|
||||
|
||||
## Invocation
|
||||
|
||||
The pipeline runs as a self-contained Gitea Actions workflow in `mikkeli/ses-light-novel` (`.gitea/workflows/publish.yml`). It clones this repo at runtime to get the renderer.
|
||||
The pipeline runs as a self-contained Gitea Actions workflow in `novoyuuparosk-wiki/ses-light-novel` (`.gitea/workflows/publish.yml`). It clones this repo at runtime to get the renderer.
|
||||
|
||||
Triggers:
|
||||
- `push` to `master` (path-filtered as above)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SCHEMA — SES light novel source files
|
||||
|
||||
Source-file contract for the SES pipeline. Files in `mikkeli/ses-light-novel` must follow this contract to be picked up by the auto-publisher.
|
||||
Source-file contract for the SES pipeline. Files in `novoyuuparosk-wiki/ses-light-novel` must follow this contract to be picked up by the auto-publisher.
|
||||
|
||||
The schema lives entirely in the YAML frontmatter block at the top of each `.md` file. The body below the frontmatter is plain markdown, rendered to MediaWiki wikitext by Pandoc.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# pipelines/songs
|
||||
|
||||
Publishes lyric pages from `mikkeli/ncmr-songs` to `https://wiki.novoyuuparosk.org`.
|
||||
Publishes lyric pages from `novoyuuparosk-wiki/ncmr-songs` to `https://wiki.novoyuuparosk.org`.
|
||||
|
||||
- Source-file contract (frontmatter and body conventions): [SCHEMA.md](SCHEMA.md)
|
||||
- Cross-cutting setup (wiki URL, bot identity, runner, Gitea secrets/variables): [repo root README](../../README.md)
|
||||
@@ -27,7 +27,7 @@ Not in scope (intentional):
|
||||
|
||||
## Source repo and branch convention
|
||||
|
||||
Source repo: `mikkeli/ncmr-songs`. The pipeline triggers on push to `master`.
|
||||
Source repo: `novoyuuparosk-wiki/ncmr-songs`. The pipeline triggers on push to `master`.
|
||||
|
||||
**Branch convention in `ncmr-songs`**: per-song short-lived branches named `autowiki/<song-slug>` (e.g., `autowiki/pulse`). Create when staging edits, merge to master when ready to publish, delete after one cycle.
|
||||
|
||||
@@ -38,7 +38,7 @@ Source repo: `mikkeli/ncmr-songs`. The pipeline triggers on push to `master`.
|
||||
|
||||
## Invocation
|
||||
|
||||
The pipeline runs as a self-contained Gitea Actions workflow in `mikkeli/ncmr-songs` (`.gitea/workflows/publish.yml`). It clones this repo at runtime to get the renderer.
|
||||
The pipeline runs as a self-contained Gitea Actions workflow in `novoyuuparosk-wiki/ncmr-songs` (`.gitea/workflows/publish.yml`). It clones this repo at runtime to get the renderer.
|
||||
|
||||
Triggers:
|
||||
- `push` to `master` (path-filtered as above)
|
||||
@@ -50,7 +50,7 @@ Triggers:
|
||||
|---|---|---|---|
|
||||
| `WIKI_API_URL` | variable | Gitea (cross-cutting) | MediaWiki action API endpoint |
|
||||
| `WIKI_BASE_URL` | variable | Gitea (cross-cutting) | Wiki base URL |
|
||||
| `WIKI_BOT_USER` | secret | Gitea (cross-cutting) | Bot login (`Dubrowski@giteaAutomaton`) |
|
||||
| `WIKI_BOT_USER` | secret | Gitea (cross-cutting) | Bot login (`Mikkeli@giteaBot`) |
|
||||
| `WIKI_BOT_PASSWORD` | secret | Gitea (cross-cutting) | BotPasswords value |
|
||||
| `URL_TO_GITEA` | variable | Gitea (cross-cutting) | Gitea instance base URL for cloning |
|
||||
| `FAPAT` | secret | Gitea (cross-cutting) | Full-Access PAT for cloning private repos |
|
||||
@@ -89,6 +89,7 @@ The pipeline always runs `--all`: every file with `wiki.publish: true` (not unde
|
||||
| Publish mode | Always `--all`; no diff detection — small repo, simpler than fragile git-diff gating | 2026-06-09 |
|
||||
| Manual-page protection | Bot skips pages without the `{{Auto-generated` banner to avoid overwriting hand-written content | 2026-06-09 |
|
||||
| `workflow_call` abandoned | Cross-repo `workflow_call` fails — run token scoped to triggering repo; cannot clone private callee | 2026-06-09 |
|
||||
| Side-by-side columns | Authored as a ` ```columns ` fenced block (separator `===`); Pandoc emits `<pre class="columns">`, expanded post-Pandoc into a flex `<div>` of `<poem>` columns. No wiki template or extension — all Pi-side before the API write. See [SCHEMA.md](SCHEMA.md#columns-side-by-side-shorthand) | 2026-06-14 |
|
||||
|
||||
## Status
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SCHEMA — song source files
|
||||
|
||||
Source-file contract for the songs pipeline. Files in `mikkeli/ncmr-songs` must follow this contract to be picked up by the auto-publisher.
|
||||
Source-file contract for the songs pipeline. Files in `novoyuuparosk-wiki/ncmr-songs` must follow this contract to be picked up by the auto-publisher.
|
||||
|
||||
The schema lives entirely in the YAML frontmatter block at the top of each `.md` file. The body below the frontmatter is plain markdown, rendered to MediaWiki wikitext by Pandoc — no special body conventions are enforced beyond the standard markdown grammar, except that `[[TAG]]` placeholders declared via `wiki.siblings` are substituted with resolved wikilinks after rendering.
|
||||
|
||||
@@ -122,20 +122,46 @@ The siblings list is a bill of materials — declaring a sibling has no effect u
|
||||
|
||||
## Body
|
||||
|
||||
Plain markdown. The renderer applies two transformations before and after Pandoc:
|
||||
Plain markdown. The renderer applies these transformations before and after Pandoc:
|
||||
|
||||
1. **Pre-Pandoc**: strip the first-line `# Heading` if present.
|
||||
2. **Post-Pandoc**: substitute `TAG` placeholders declared in `wiki.siblings`.
|
||||
2. **Post-Pandoc**: expand `columns` fenced blocks into side-by-side wikitext (see below).
|
||||
3. **Post-Pandoc**: substitute `TAG` placeholders declared in `wiki.siblings`.
|
||||
|
||||
Do not embed raw wikitext-specific syntax (`{{Template}}`, raw `[[Wikilink]]` not declared via `siblings`, etc.) in the body unless you intend the literal output. Use markdown idioms; the renderer adds the metadata-derived bits (banner, LRC link, categories, sibling resolution) around Pandoc's output.
|
||||
|
||||
### Columns (side-by-side) shorthand
|
||||
|
||||
For parallel content — e.g. an original and its translation — write a fenced code block tagged `columns` and separate the columns with a line containing only `===`:
|
||||
|
||||
````markdown
|
||||
```columns
|
||||
**原題**
|
||||
|
||||
一行目
|
||||
二行目
|
||||
===
|
||||
**Title**
|
||||
|
||||
first line
|
||||
second line
|
||||
```
|
||||
````
|
||||
|
||||
The renderer turns this into a flexbox row of `<poem>` columns (one per `===`-delimited section). Notes:
|
||||
|
||||
- **Line breaks and blank lines are preserved verbatim** — that's the point of using a fenced block; Pandoc passes the body through untouched, and each column is wrapped in `<poem>` so MediaWiki keeps the line breaks.
|
||||
- **N columns**: use N−1 `===` separators. Two is the common case (original / translation).
|
||||
- **Emphasis in Markdown**: write `*italic*`, `**bold**`, or `***bold-italic***` (asterisk style, single line) — these are converted to wikitext. Raw inline HTML such as `<b>…</b>` or `<br>` also still works (it's HTML-unescaped on the way out), but you shouldn't need it.
|
||||
- This is a shared transform (`lib/wiki.py`), so it works for any pipeline, not just songs. Column width/gap styling currently lives in that transform.
|
||||
|
||||
## Renderer behaviour
|
||||
|
||||
For each `.md` with `wiki.publish: true` and not under `wip/`:
|
||||
|
||||
1. Parse and validate frontmatter.
|
||||
2. Strip the leading `# Heading` from the body if present.
|
||||
3. Pipe the body through `pandoc -f markdown -t mediawiki`.
|
||||
3. Pipe the body through `pandoc -f markdown -t mediawiki`, then expand any `columns` fenced blocks into side-by-side `<poem>` columns.
|
||||
4. Substitute `TAG` placeholders with resolved `[[Page|Display]]` wikilinks (or fallback text).
|
||||
5. Prepend the auto-generated banner: `{{Auto-generated|source=<source URL>|commit=<sha>}}`.
|
||||
6. If `lrc` is declared: upload the LRC file if its SHA1 has changed; append a `[[Media:…]]` link.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# pipelines/tech
|
||||
|
||||
Publishes tech blog posts from `mikkeli/tech-blogs` to `https://wiki.novoyuuparosk.org`.
|
||||
Publishes tech blog posts from `novoyuuparosk-wiki/tech-blogs` to `https://wiki.novoyuuparosk.org`.
|
||||
|
||||
- Source-file contract (frontmatter and body conventions): [SCHEMA.md](SCHEMA.md)
|
||||
- Cross-cutting setup (wiki URL, bot identity, runner, Gitea secrets/variables): [repo root README](../../README.md)
|
||||
@@ -10,7 +10,7 @@ Publishes tech blog posts from `mikkeli/tech-blogs` to `https://wiki.novoyuuparo
|
||||
- YAML frontmatter parsing per [SCHEMA.md](SCHEMA.md)
|
||||
- Pandoc-based markdown → wikitext body rendering (h1 elements preserved)
|
||||
- Auto-generated banner (`{{Auto-generated|source=...|commit=<sha>}}`)
|
||||
- Category injection: `[[Category:Tech blog]]` on every page; `[[Category:Blog:<year>]]` from `date` field; `[[Category:<tag>]]` per tag entry
|
||||
- Category injection: `[[Category:Tech blog]]` on every page; `[[Category:Blog:<year>]]` from `date` field (falling back to the action's execution year); `[[Category:<tag>]]` per tag entry
|
||||
- No title prefix — pages live at their bare title
|
||||
- MediaWiki bot API write with idempotency — no-op if wiki content matches generated output
|
||||
- Files without `wiki.publish: true` silently skipped
|
||||
@@ -18,7 +18,7 @@ Publishes tech blog posts from `mikkeli/tech-blogs` to `https://wiki.novoyuuparo
|
||||
|
||||
## Source repo
|
||||
|
||||
Source repo: `mikkeli/tech-blogs` (not yet initialised — pipeline code is ready).
|
||||
Source repo: `novoyuuparosk-wiki/tech-blogs` (not yet initialised — pipeline code is ready).
|
||||
|
||||
The pipeline triggers on push to `master` and supports `workflow_dispatch` for manual runs.
|
||||
|
||||
@@ -28,7 +28,7 @@ The pipeline triggers on push to `master` and supports `workflow_dispatch` for m
|
||||
|
||||
## Invocation
|
||||
|
||||
The pipeline runs as a self-contained Gitea Actions workflow in `mikkeli/tech-blogs` (`.gitea/workflows/publish.yml`). It clones this repo at runtime to get the renderer.
|
||||
The pipeline runs as a self-contained Gitea Actions workflow in `novoyuuparosk-wiki/tech-blogs` (`.gitea/workflows/publish.yml`). It clones this repo at runtime to get the renderer.
|
||||
|
||||
Triggers:
|
||||
- `push` to `master` (path-filtered as above)
|
||||
@@ -65,14 +65,14 @@ Shared code lives in `lib/wiki.py` (repo root).
|
||||
|---|---|---|
|
||||
| No title prefix | Tech posts live at bare titles — no namespace needed | 2026-06-10 |
|
||||
| Fixed category | Every post gets `Category:Tech blog` | 2026-06-10 |
|
||||
| Year category | `Category:Blog:<year>` from `date` field; omitted if no date | 2026-06-10 |
|
||||
| Year category | `Category:Blog:<year>` from `date` field; falls back to the execution year if no date | 2026-06-10 |
|
||||
| Tag categories | Each tag → `[[Category:<tag>]]` with no prefix | 2026-06-10 |
|
||||
| WIP exclusion | None — `wiki.publish: false` is the only gate | 2026-06-10 |
|
||||
| Publish mode | Always `--all` | 2026-06-10 |
|
||||
| Idempotency | Commit SHA in banner | 2026-06-10 |
|
||||
| Manual-page protection | Bot skips pages without `{{Auto-generated` banner | 2026-06-10 |
|
||||
| Source repo | `mikkeli/tech-blogs` — pipeline ships before repo is initialised | 2026-06-10 |
|
||||
| Source repo | `novoyuuparosk-wiki/tech-blogs` — pipeline ships before repo is initialised | 2026-06-10 |
|
||||
|
||||
## Status
|
||||
|
||||
v1 pipeline ready. Source repo (`mikkeli/tech-blogs`) not yet initialised.
|
||||
v1 pipeline ready. Source repo (`novoyuuparosk-wiki/tech-blogs`) not yet initialised.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SCHEMA — tech blog source files
|
||||
|
||||
Source-file contract for the tech blog pipeline. Files in `mikkeli/tech-blogs` must follow this contract to be picked up by the auto-publisher.
|
||||
Source-file contract for the tech blog pipeline. Files in `novoyuuparosk-wiki/tech-blogs` must follow this contract to be picked up by the auto-publisher.
|
||||
|
||||
The schema lives entirely in the YAML frontmatter block at the top of each `.md` file. The body is plain markdown, rendered to MediaWiki wikitext by Pandoc.
|
||||
|
||||
@@ -45,7 +45,7 @@ wiki:
|
||||
|
||||
#### `date` (ISO 8601 date)
|
||||
|
||||
Publication date in `YYYY-MM-DD` format. Drives `[[Category:Blog:<year>]]` injection. If omitted, no year category is added.
|
||||
Publication date in `YYYY-MM-DD` format. Drives `[[Category:Blog:<year>]]` injection. If omitted, the year falls back to the time the publish action runs.
|
||||
|
||||
#### `tags` (list of strings)
|
||||
|
||||
@@ -69,7 +69,7 @@ For each `.md` with `wiki.publish: true`:
|
||||
1. Parse and validate frontmatter.
|
||||
2. Pipe the body through `pandoc -f markdown -t mediawiki`.
|
||||
4. Prepend the auto-generated banner: `{{Auto-generated|source=<source URL>|commit=<sha>}}`.
|
||||
5. Append category tags: `[[Category:Tech blog]]`; `[[Category:Blog:<year>]]` if `date` is set; `[[Category:<tag>]]` for each tag.
|
||||
5. Append category tags: `[[Category:Tech blog]]`; `[[Category:Blog:<year>]]` (from `date`, or the action's execution year if `date` is absent); `[[Category:<tag>]]` for each tag.
|
||||
6. Read the current wiki page content; if identical, skip the write (idempotency).
|
||||
7. If the page exists without `{{Auto-generated`, skip with a warning (manual page protection).
|
||||
8. Otherwise, write with an edit summary referencing the source commit.
|
||||
@@ -80,7 +80,7 @@ For each `.md` with `wiki.publish: true`:
|
||||
- Pandoc-based markdown → wikitext rendering (h1 elements preserved)
|
||||
- Banner template injection
|
||||
- `Category:Tech blog` on every published page
|
||||
- `Category:Blog:<year>` from `date` field
|
||||
- `Category:Blog:<year>` from `date` field, falling back to the execution year
|
||||
- `Category:<tag>` per entry in `tags`
|
||||
- Idempotent writes
|
||||
- Manual-page protection
|
||||
|
||||
@@ -41,9 +41,10 @@ def build_wikitext(fm: dict, body_wikitext: str, source_url: str, source_ref: st
|
||||
banner = f"{{{{Auto-generated|source={source_url}|commit={source_ref}}}}}"
|
||||
cat_parts = ["[[Category:Tech blog]]"]
|
||||
date_val = fm.get("date")
|
||||
if date_val:
|
||||
year = str(date_val)[:4]
|
||||
cat_parts.append(f"[[Category:Blog:{year}]]")
|
||||
# Fall back to the action's execution year when no date is declared, so
|
||||
# every post still lands in a Blog:<year> category.
|
||||
year = str(date_val)[:4] if date_val else str(datetime.now().year)
|
||||
cat_parts.append(f"[[Category:Blog:{year}]]")
|
||||
for tag in (fm.get("tags") or []):
|
||||
cat_parts.append(f"[[Category:{tag}]]")
|
||||
categories = "\n".join(cat_parts) + "\n"
|
||||
|
||||
Reference in New Issue
Block a user