12 Commits

Author SHA1 Message Date
mikkeli 4e7d84d665 fix(ci): point kaniko-act builder at the only working copy
The org copy this workflow produced cannot build (no /tmp), so pointing
its own container at it made the workflow unrunnable.
2026-08-11 20:41:09 +09:00
mikkeli e4ae304954 docs: record act cache staleness and the kaniko-act /tmp limitation 2026-08-11 20:38:27 +09:00
wiki-runner image bot bdca12837e build: pin runner image to 4f134af 2026-08-11 11:35:27 +00:00
mikkeli 4f134af7bc fix(ci): build in the known-good kaniko-act
/ build (push) Successful in 1m5s
/ pin (push) Successful in 10s
The org rebuild exits 0 but produces an image without /tmp, so the
source fetch fails. Runner image still publishes to the org namespace;
only the builder stays put.
2026-08-11 20:34:07 +09:00
mikkeli 905a1051a8 ci: move container images to the org registry namespace
/ build (push) Failing after 2s
/ pin (push) Has been skipped
Build destination, both builder-image references and the three publish
pins now point at novoyuuparosk-wiki/*. The publish pins name a tag that
does not exist in the new namespace until this run's build job pushes it
and the pin job rewrites them, so publishes are briefly broken mid-run.
2026-08-11 20:32:45 +09:00
mikkeli 4919ce9450 ci: add dispatch-only kaniko-act build, targeting the org registry
Bootstrap step for moving container images off the personal namespace.
Builds using the existing mikkeli/kaniko-act; the org copy is what this
run produces.
2026-08-11 20:31:04 +09:00
mikkeli 03be97799d docs: record why this repo must stay public
Correcting a wrong claim in the previous commit: sharing an owner with
the caller does not grant read access to a private callee. act_runner
clones the reusable workflow anonymously, so the repo has to be public.

Also notes the act cache key, which hid this for months, and the stale
Dubrowski bot identity.
2026-08-11 20:26:33 +09:00
wiki-runner image bot 01901b3821 build: pin runner image to 6dc9f68 2026-08-11 10:48:50 +00:00
mikkeli 6dc9f68b12 chore: repoint pipeline at the novoyuuparosk-wiki org
/ build (push) Successful in 1m3s
/ pin (push) Successful in 2s
This repo and the three source repos moved from the `mikkeli` account
into the `novoyuuparosk-wiki` org. Rewrites cover repo paths only —
the `mikkeli:` HTTP basic-auth usernames stay, since FAPAT/PKGRW_PAT
remain personally owned, as do the `3005/mikkeli/*` image paths
(Gitea cannot transfer packages).

Bot identity moves to `Mikkeli@giteaBot`; both accounts hold the bot
right, so edits stay flagged as bot edits.
2026-08-11 19:47:35 +09:00
mikkeli 83a5d86115 Merge branch 'automation/columns-md-emphasis'
Convert Markdown emphasis (*italic*, **bold**, ***bold-italic***) to wikitext inside columns blocks.
2026-06-14 21:13:31 +09:00
mikkeli 2072db4980 feat: Markdown emphasis inside columns blocks
Inside a columns fenced block (verbatim, so Pandoc doesn't process
Markdown), convert *italic* / **bold** / ***bold-italic*** to wikitext
emphasis. Asterisk style, single line; precedence bold-italic > bold >
italic. Raw inline HTML still works via the existing HTML-unescape path.

Docs + example updated to use ** ** rather than <b>.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:13:31 +09:00
mikkeli 00527e46b1 Merge branch 'automation/columns-shorthand'
Side-by-side columns shorthand: a 'columns' fenced block (columns split by a line of ===) is expanded post-Pandoc in shared lib/wiki.py into a flex div of poem columns. Universal across pipelines; no wiki template or PHP extension.
2026-06-14 21:02:35 +09:00
14 changed files with 150 additions and 53 deletions
+7 -4
View File
@@ -14,6 +14,9 @@ jobs:
# 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:
@@ -30,12 +33,12 @@ jobs:
set -eu
HOST="${URL_TO_GITEA#http://}"
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
IMAGE="${HOST}/mikkeli/novoyuuparosk-wiki-runner"
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/mikkeli/novoyuuparosk-auto-wiki/archive/${GITHUB_SHA}.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
@@ -64,7 +67,7 @@ jobs:
# 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/mikkeli/novoyuuparosk-wiki-runner:latest
image: pi5-16.local:3005/novoyuuparosk-wiki/novoyuuparosk-wiki-runner:latest
steps:
- name: Repoint publish workflows at the new image tag
env:
@@ -77,7 +80,7 @@ jobs:
HOST="${URL_TO_GITEA#http://}"
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
git clone "http://mikkeli:${FAPAT}@${HOST}/mikkeli/novoyuuparosk-auto-wiki" repo
git clone "http://mikkeli:${FAPAT}@${HOST}/novoyuuparosk-wiki/novoyuuparosk-auto-wiki" repo
cd repo
for f in .gitea/workflows/publish-songs.yml \
+65
View File
@@ -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}"
+4 -4
View File
@@ -10,7 +10,7 @@ jobs:
publish:
runs-on: ubuntu-latest
container:
image: pi5-16.local:3005/mikkeli/novoyuuparosk-wiki-runner:b2714ab
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 \
+4 -4
View File
@@ -19,7 +19,7 @@ jobs:
publish:
runs-on: ubuntu-latest
container:
image: pi5-16.local:3005/mikkeli/novoyuuparosk-wiki-runner:b2714ab
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
+4 -4
View File
@@ -10,7 +10,7 @@ jobs:
publish:
runs-on: ubuntu-latest
container:
image: pi5-16.local:3005/mikkeli/novoyuuparosk-wiki-runner:b2714ab
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 \
+23 -11
View File
@@ -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 live |
| [`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,7 +53,7 @@ Container network mode: `host` — required so job containers can reach `localho
### Job container image
All pipelines share a single pre-built Docker image, served from the Gitea registry at `pi5-16.local:3005/mikkeli/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.
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 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`.
@@ -57,12 +61,13 @@ A follow-up `pin` job then rewrites the `image:` pin in each `publish-*.yml` to
## 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`
@@ -83,6 +88,13 @@ 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 |
@@ -92,9 +104,9 @@ 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.)
+5 -4
View File
@@ -5,10 +5,11 @@
# (busybox lives under /busybox; /bin/sh is already symlinked) nor /tmp.
# This wrapper adds exactly those two. Nothing else changes.
#
# Bootstrap: the first build of this image is done manually on the host
# (docker build -f ci/Dockerfile.kaniko-act -t <registry>/mikkeli/kaniko-act:<ver> .)
# because no builder image exists yet. Later version bumps can be built by
# the build-image workflow itself, using the previous kaniko-act.
# 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
+20 -4
View File
@@ -15,6 +15,20 @@ AUTO_BANNER_PREFIX = "{{Auto-generated"
_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."""
@@ -35,13 +49,15 @@ def expand_columns(wikitext: str) -> str:
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, and content is
HTML-unescaped so inline markup written in the fence (e.g. <b>…</b>) renders
rather than appearing as literal text.
<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 = html.unescape(match.group(1))
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>"
+3 -3
View File
@@ -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 -1
View File
@@ -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.
+4 -4
View File
@@ -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 |
+4 -4
View File
@@ -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.
@@ -136,12 +136,12 @@ For parallel content — e.g. an original and its translation — write a fenced
````markdown
```columns
<b>原題</b>
**原題**
一行目
二行目
===
<b>Title</b>
**Title**
first line
second line
@@ -152,7 +152,7 @@ The renderer turns this into a flexbox row of `<poem>` columns (one per `===`-de
- **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 N1 `===` separators. Two is the common case (original / translation).
- **Inline markup is allowed**: HTML such as `<b>…</b>` or `<br>` written inside the block is HTML-unescaped on the way out, so it renders rather than showing as literal text.
- **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
+5 -5
View File
@@ -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)
@@ -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)
@@ -71,8 +71,8 @@ Shared code lives in `lib/wiki.py` (repo root).
| 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 -1
View File
@@ -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.