Compare commits

..

7 Commits

Author SHA1 Message Date
paste 25737953d8 Rewrite example release workflow to the proven subpath-safe pattern
CI / build-and-smoke (push) Successful in 4s
Manual clone + docker build + docker cp instead of actions/checkout and
'uses:' (both broken by the /git subpath). Pins packager @v1.
2026-06-25 22:51:19 -05:00
paste b659386bd2 CI: use docker cp instead of bind mounts (sibling-container path trap)
CI / build-and-smoke (push) Successful in 5s
Under act_runner, inner 'docker run' hits the host daemon via the mounted
socket, so -v "$PWD/test:/work" mounts a nonexistent host path (empty).
Stream files in/out with docker cp instead. Same pattern will drive the
GlueXML release workflow.
2026-06-25 22:48:49 -05:00
paste c615a5fb92 CI: clone manually with /git base instead of actions/checkout
CI / build-and-smoke (push) Failing after 36s
The instance generates clone URLs without its /git subpath, so
actions/checkout 404s. Manual clone against the correct base is the
reliable workaround until the server ROOT_URL is corrected.
2026-06-25 22:43:42 -05:00
paste 7b9a70f4fe Pin GITHUB_SERVER_URL to /git subpath so checkout/API resolve
CI / build-and-smoke (push) Failing after 35s
act_runner reports GITHUB_SERVER_URL without the instance's /git subpath,
so actions/checkout cloned https://octowow.st/paste/... (404). Pin the
correct base at job level until the server ROOT_URL is fixed.
2026-06-25 22:39:38 -05:00
paste c414984ba3 Add act_runner setup (Docker Compose) for octowow.st
CI / build-and-smoke (push) Failing after 37s
2026-06-25 22:16:17 -05:00
paste 8bef637053 update readme
CI / build-and-smoke (push) Failing after 31s
2026-06-25 21:56:39 -05:00
paste 922aea9bc1 Add MPQ verify modes, harden CI assertions, fix CRLF/docstring
CI / build-and-smoke (push) Failing after 31s
- mpqpack gains --list and --cat (StormLib-backed) for reading archives back
- CI now asserts the archive lists the expected file and that version
  substitution landed inside the MPQ, not just that a file exists
- Dockerfile strips CRLF from entrypoint/package so local Windows builds work
- drop invalid escape in package.py docstring
2026-06-25 21:54:09 -05:00
10 changed files with 224 additions and 34 deletions
+32 -7
View File
@@ -11,25 +11,50 @@ jobs:
build-and-smoke:
runs-on: ubuntu-latest
steps:
# octowow.st is served under /git, but Gitea generates clone URLs without
# it (ROOT_URL/proxy misconfig), so actions/checkout 404s. Clone manually
# against the correct base. Remove once the server ROOT_URL is fixed.
- name: Checkout
uses: actions/checkout@v4
run: |
git init -q .
git remote add origin "https://gitea:${{ secrets.GITHUB_TOKEN }}@octowow.st/git/${{ github.repository }}.git"
git fetch -q --depth 1 origin "${{ github.ref }}"
git checkout -q FETCH_HEAD
- name: Build image
run: docker build -t mpq-packager:ci .
# NOTE: act_runner runs these `docker` commands against the HOST daemon via
# the mounted socket, so bind-mounting the job's files (-v "$PWD:/work")
# doesn't work — the source path isn't on the host. Use `docker cp` to
# stream files in/out of a created container instead. The GlueXML release
# workflow uses this same pattern.
- name: Package the fixture
run: |
set -euo pipefail
docker run --rm \
cid=$(docker create \
-e INPUT_MANIFEST=mpq.yaml \
-e INPUT_VERSION=v0.0.0-ci \
-e INPUT_OUT_DIR=/work/dist \
-e GITHUB_WORKSPACE=/work \
-v "$PWD/test:/work" \
mpq-packager:ci
mpq-packager:ci)
docker cp test/. "$cid:/work"
docker start -a "$cid"
docker cp "$cid:/work/dist/patch-Z.mpq" ./patch-Z.mpq
docker rm "$cid" >/dev/null
- name: Assert artifact exists
- name: Assert archive is valid and contains the expected file
run: |
set -euo pipefail
test -s test/dist/patch-Z.mpq
echo "OK: $(stat -c%s test/dist/patch-Z.mpq) bytes"
test -s patch-Z.mpq
echo "size: $(stat -c%s patch-Z.mpq) bytes"
# Read the archive back with StormLib (mpqpack --list/--cat) inside a
# sleeping container we exec into.
cid=$(docker run -d --entrypoint sleep mpq-packager:ci 120)
docker cp patch-Z.mpq "$cid:/patch-Z.mpq"
docker exec "$cid" mpqpack --list /patch-Z.mpq | tee list.txt
grep -q 'Interface\\AddOns\\Fixture\\Hello.lua' list.txt
docker exec "$cid" mpqpack --cat /patch-Z.mpq 'Interface\AddOns\Fixture\Hello.lua' \
| grep -q 'VERSION = "v0.0.0-ci"'
docker rm -f "$cid" >/dev/null
echo "OK: archive valid, path + substitution confirmed"
+6
View File
@@ -2,3 +2,9 @@ dist/
test/dist/
*.mpq
*.o
# local validation helpers (not needed in CI)
test/stub_mpqpack.py
test/mpqpack.cmd
# runner secrets/state
runner/.env
runner/data/
+3 -1
View File
@@ -37,6 +37,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
COPY --from=build /usr/local/bin/mpqpack /usr/local/bin/mpqpack
COPY package.py /opt/mpq-packager/package.py
COPY entrypoint.sh /opt/mpq-packager/entrypoint.sh
RUN chmod +x /opt/mpq-packager/entrypoint.sh
# Strip any CRLF (Windows checkouts) so the shebang and bash parse correctly.
RUN sed -i 's/\r$//' /opt/mpq-packager/entrypoint.sh /opt/mpq-packager/package.py \
&& chmod +x /opt/mpq-packager/entrypoint.sh
ENTRYPOINT ["/opt/mpq-packager/entrypoint.sh"]
-2
View File
@@ -116,5 +116,3 @@ docker run --rm \
regardless of MPQ residency — packaging them here does not bypass it.
- Compression is zlib, which vanilla 1.12.1 decompresses. The output is MPQ v1
specifically; v2+ headers are not read by the 1.12 loader.
- See ClassicAPI `docs/MPQBuilding.md` for the empirical background on the
naming constraint and load-order behaviour.
+41 -22
View File
@@ -3,7 +3,15 @@
# and attaches it to a matching Gitea release.
#
# Requires: Actions enabled on the repo, and a runner advertising `ubuntu-latest`
# (or change runs-on to a label your act_runner registers).
# with Docker available.
#
# NOTE on octowow.st: the instance is served under the /git subpath, but Gitea
# generates URLs without it, which breaks actions/checkout and `uses:` action
# resolution. So this workflow avoids both: it clones manually against the
# correct base and runs the packager via `docker build` + `docker cp` (bind
# mounts don't work either — the inner docker uses the host daemon). Once the
# server's ROOT_URL is fixed, this can be simplified to actions/checkout +
# `uses: paste/mpq-packager@v1`.
name: Release MPQ
on:
@@ -15,36 +23,47 @@ jobs:
package:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Checkout (manual; instance under /git)
run: |
git init -q .
git remote add origin "https://gitea:${{ secrets.GITHUB_TOKEN }}@octowow.st/git/${{ github.repository }}.git"
git fetch -q --depth 1 origin "${{ github.ref }}"
git checkout -q FETCH_HEAD
- name: Build packager image
run: |
git clone -q --depth 1 --branch v1 \
"https://gitea:${{ secrets.GITHUB_TOKEN }}@octowow.st/git/paste/mpq-packager.git" /tmp/packager
docker build -q -t mpq-packager /tmp/packager
- name: Build patch MPQ
id: pack
uses: paste/mpq-packager@v1
with:
manifest: mpq.yaml
version: ${{ github.ref_name }}
run: |
set -euo pipefail
cid=$(docker create \
-e INPUT_MANIFEST=mpq.yaml \
-e INPUT_VERSION=${{ github.ref_name }} \
-e INPUT_OUT_DIR=/work/dist \
-e GITHUB_WORKSPACE=/work \
mpq-packager)
docker cp . "$cid:/work"
docker start -a "$cid"
docker cp "$cid:/work/dist/patch-Z.mpq" ./patch-Z.mpq
docker rm "$cid" >/dev/null
ls -l patch-Z.mpq
- name: Create release and upload asset
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
TAG="${GITHUB_REF_NAME}"
MPQ="${{ steps.pack.outputs.mpq-path }}"
NAME="${{ steps.pack.outputs.mpq-name }}"
echo "Creating release $TAG"
API="https://octowow.st/git/api/v1/repos/${{ github.repository }}"
TAG="${{ github.ref_name }}"
RID=$(curl -fsSL -X POST "$API/releases" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"draft\":false,\"prerelease\":false}" \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')
echo "Uploading $NAME to release $RID"
curl -fsSL -X POST "$API/releases/$RID/assets?name=$NAME" \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
curl -fsSL -X POST "$API/releases/$RID/assets?name=patch-Z.mpq" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$MPQ"
echo "Done."
--data-binary "@patch-Z.mpq"
echo "Released $TAG with patch-Z.mpq"
+1 -1
View File
@@ -23,7 +23,7 @@ Manifest schema (all keys optional except `contents`):
extensions: [".lua", ".xml", ".toc"]
contents:
- src: . # path in the repo (file or dir)
dest: Interface/GlueXML # in-MPQ path (forward slashes; converted to \)
dest: Interface/GlueXML # in-MPQ path (forward slashes become backslashes)
ignore: # glob patterns, matched against repo-relative paths
- ".git/**"
- ".gitea/**"
+5
View File
@@ -0,0 +1,5 @@
# Copy to `.env` (same folder) and fill in. Do NOT commit .env — it holds the token.
# Get the token at: https://octowow.st/git/user/settings/actions/runners
# -> "Create new Runner" -> copy the registration token.
RUNNER_TOKEN=paste-registration-token-here
RUNNER_NAME=octo-windows-runner
+68
View File
@@ -0,0 +1,68 @@
# act_runner setup (octowow.st)
Stands up a self-hosted Gitea Actions runner as a container. One runner
registered at the **user level** serves all of your repos (`GlueXML`,
`mpq-packager`, …).
## Prerequisites on the runner host
- **Docker** running. On Windows/macOS that means Docker Desktop with the Linux
engine (WSL2 backend on Windows). Settings → Advanced → keep "Allow the
default Docker socket to be used" enabled so the socket mount works.
## 1. Get a registration token
In the Gitea web UI, as your user:
> avatar → **Settings** → **Actions** → **Runners** → **Create new Runner**
Copy the **registration token** (a long string). This is user-scoped, so the
runner picks up jobs from any of your repos.
Direct link: <https://octowow.st/git/user/settings/actions/runners>
> A repo-scoped token (repo → Settings → Actions → Runners) also works if you
> only want the runner tied to one repo.
## 2. Configure
```sh
cp .env.example .env
# edit .env: paste RUNNER_TOKEN, set RUNNER_NAME
```
## 3. Start
```sh
docker compose up -d
docker compose logs -f # watch it register
```
The runner should appear as **Idle / online** in the Runners list within a few
seconds.
## 4. Enable Actions on each repo
Per repo: **Settings → Actions →** enable. New repos have it off by default.
## 5. Verify
Push a tag (or run a workflow) and watch it execute under the **Actions** tab.
For `mpq-packager`'s release flow, a `v*` tag on a consuming repo builds the MPQ
and attaches it to a release.
## Notes
- **First job is slow** — it pulls `catthehacker/ubuntu:act-latest` (~1 GB) the
first time; cached afterward.
- **`runs-on` labels** must match `GITEA_RUNNER_LABELS`. We map
`ubuntu-latest``catthehacker/ubuntu:act-latest`. Add more pairs
(comma-separated) to support other labels.
- **Docker actions** (e.g. `uses: paste/mpq-packager@v1`) work out of the box:
act_runner builds and runs them via the mounted host socket.
- **Workflows that run `docker` CLI in a step** (like this repo's own
`.gitea/workflows/ci.yml`) need the job container to reach Docker too. The
release workflow does not — it only *uses* the Docker action — so it's fine.
Ask if you want the CI's docker-in-job case wired up.
- **Re-registering**: delete `./data` and `docker compose up -d` again with a
fresh token.
+26
View File
@@ -0,0 +1,26 @@
# act_runner for octowow.st, run as a container (Docker Compose).
# Copy this folder to the runner host, fill in .env (see .env.example),
# then: docker compose up -d
#
# The runner auto-registers on first start using the env vars below, then
# persists its credentials in ./data so restarts don't re-register.
services:
act_runner:
image: gitea/act_runner:latest
container_name: octo-act-runner
restart: always
environment:
# Gitea base URL — note the /git path on this instance.
GITEA_INSTANCE_URL: https://octowow.st/git
# Registration token from Settings -> Actions -> Runners -> "Create new Runner".
GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}
GITEA_RUNNER_NAME: ${RUNNER_NAME:-octo-runner}
# runs-on label -> image mapping. ubuntu-latest uses the de-facto act image.
GITEA_RUNNER_LABELS: ubuntu-latest:docker://catthehacker/ubuntu:act-latest
volumes:
# Host Docker socket: act_runner spawns job/action containers as siblings.
# On Docker Desktop (Windows/Mac) this path is provided by the Linux engine.
- /var/run/docker.sock:/var/run/docker.sock
# Persist runner registration + state.
- ./data:/data
+42 -1
View File
@@ -80,9 +80,50 @@ static unsigned walk(const char *dir, int add) {
return count;
}
/* --list <mpq>: enumerate archive contents (names + sizes). */
static int cmd_list(const char *mpq) {
HANDLE h;
if (!SFileOpenArchive(mpq, 0, STREAM_FLAG_READ_ONLY, &h)) die("open", mpq);
SFILE_FIND_DATA fd;
HANDLE find = SFileFindFirstFile(h, "*", &fd, NULL);
if (find) {
do {
printf("%-50s %u\n", fd.cFileName, fd.dwFileSize);
} while (SFileFindNextFile(find, &fd));
SFileFindClose(find);
}
SFileCloseArchive(h);
return 0;
}
/* --cat <mpq> <internal-name>: dump one file's bytes to stdout. */
static int cmd_cat(const char *mpq, const char *name) {
HANDLE h, f;
if (!SFileOpenArchive(mpq, 0, STREAM_FLAG_READ_ONLY, &h)) die("open", mpq);
if (!SFileOpenFileEx(h, name, 0, &f)) die("openfile", name);
char buf[65536];
DWORD got;
while (SFileReadFile(f, buf, sizeof buf, &got, NULL) || got) {
fwrite(buf, 1, got, stdout);
if (got < sizeof buf) break;
}
SFileCloseFile(f);
SFileCloseArchive(h);
return 0;
}
int main(int argc, char **argv) {
if (argc >= 3 && !strcmp(argv[1], "--list"))
return cmd_list(argv[2]);
if (argc >= 4 && !strcmp(argv[1], "--cat"))
return cmd_cat(argv[2], argv[3]);
if (argc < 3) {
fprintf(stderr, "usage: %s <output.mpq> <staging-dir> [maxFiles]\n", argv[0]);
fprintf(stderr,
"usage: %s <output.mpq> <staging-dir> [maxFiles]\n"
" %s --list <mpq>\n"
" %s --cat <mpq> <internal-name>\n",
argv[0], argv[0], argv[0]);
return 2;
}
const char *out = argv[1];