Set up a CI test repository¶
Nerthus.Core (until cutover). This page describes the frozen system that runs today and is deleted at cutover. Replaced by: not yet written.
This guide seeds a disposable lore repository to exercise the pipeline templates —
settlement, map checkup, the close reminder, the session report, and the
/zamknij-sesję close — against a real GitLab project, without ever pointing them at
the live lore repo. One script, ci/New-NerthusCiTestRepo.ps1, builds it. The
test bed is repozytorium-fabularne-prodkopia, in the same
it group as the module itself, nerthus.core.
Read this before you run anything on this page
This guide used to name nerthus.testrepo, a public project you could raze at will.
That project does not exist — glab api projects/margonem%2Fnerthus%2Fit%2Fnerthus.testrepo
answers 404 Project Not Found, on a credential that reads repozytorium-fabularne-prodkopia in the
same breath, and the page called it public, so a 404 is absence rather than permissions.
What it names now is not disposable, and three measured facts follow from that
(repozytorium-fabularne-prodkopia, id 85880243, read 2026-08-29):
- It is private. The reasoning further down about a public bed mirroring production does
not hold here: an
include: project:and a runner clone both need a credential. - Its
mainis protected. The reseed loop this page is built on force-pushes over the default branch, and a protectedmainrefuses that. Step 1 below does not run against this project as it stands. Unprotectingmainon a shared corpus is a Rada act with a blast radius, not a step in a how-to. - It is the shared dev corpus. Every host in the dev fleet converges on it, it holds a real clone of the campaign rather than a fixture, and other people and other tooling read it. A page that used to say push freely is now talking about somebody else's tree.
So: use this page's .env and wiring halves against it, and do not run the -Force reseed
unless you have been told, by a person, that razing that history is wanted.
Prerequisites¶
- A module checkout whose daemon builds and boots — the script starts a real
ephemeral
nerthusdfrom it.pwshandgitonPATH. - A test project with
mainunprotected. Reseeding force-pushes over the default branch (step 3), and a protectedmainrejects a force-push.repozytorium-fabularne-prodkopiahasmainprotected today, so the reseed loop below does not run against it unmodified — see the box at the top. A public bed would also mirror production, because the module clone URL is public and aninclude: project:and a runner clone then resolve with no per-user role; a private bed needs a credential for both.
Put the credentials in .env¶
Every script below reads its credentials from a gitignored .env at the root of the module (nerthus.core) checkout, so the
disposable test-bed secrets stay off the command line. The file, its full key catalogue, the
loader, and the precedence rules are owned by Set up credentials — create
it there with the test project's values (GITLAB_PROJECT_PATH = repozytorium-fabularne-prodkopia's path; leave
GITLAB_TRIGGER_TOKEN blank — the bootstrap in step 3 mints it), then load it:
1. Seed and push¶
Run the script from a module checkout. Point -Remote at the test repo's clone URL and
-ModuleProject at the module's project path — not a clone URL; the path is what
GitLab needs in the generated include: project: lines. -Force razes whatever history
is on the remote and force-pushes the fresh seed:
./ci/New-NerthusCiTestRepo.ps1 `
-Remote git@gitlab.com:margonem/nerthus/it/repozytorium-fabularne-prodkopia.git `
-ModuleProject margonem/nerthus/it/nerthus.core `
-Force
The include: ref auto-detects to the module checkout's current branch, so the test
bed pulls the branch-under-test's templates, and the script also pins
NERTHUS_MODULE_REF to that branch so the runner clones the same driver. Pass
-ModuleRef '' to force GitLab's default-branch behavior once the templates have merged,
or -ModuleRef main explicitly.
-Force is the disposable-repo default. Without it:
- A bare run (
./ci/New-NerthusCiTestRepo.ps1) seeds and commits into a temp directory only, touching no remote — it prints the path. -Remote <url>alone adds theoriginremote (a local, reversible act) but leaves the push to you; pushing is outward-facing.-Pushpushes without force — a rejected non-fast-forward is an obstacle, so use-Forceon a repo you mean to reseed.
The example uses the SSH clone URL and pushes with your ambient key. On a headless box that
holds only the PAT, use the https clone URL instead: -Token defaults from
GITLAB_ACCESS_TOKEN, and the push goes over an oauth2:<token>@ URL — no SSH key, and
origin stays the clean URL (the token never lands in .git/config).
2. What lands in the repo¶
The commit carries the adopted state — .nerthus/schema.json, the config, the data
tables, and the generated nerthus.entities.md index — and none of the daemon's
transient runtime files (the layout gitignores runtime/log/state/local/cache under
.nerthus/). Alongside it, a .gitlab-ci.yml that includes all five templates against
margonem/nerthus/it/nerthus.core:
include:
- project: 'margonem/nerthus/it/nerthus.core'
ref: '<branch-under-test>'
file: 'ci/settle.gitlab-ci.yml'
- project: 'margonem/nerthus/it/nerthus.core'
ref: '<branch-under-test>'
file: 'ci/map-checkup.gitlab-ci.yml'
- project: 'margonem/nerthus/it/nerthus.core'
ref: '<branch-under-test>'
file: 'ci/report.gitlab-ci.yml'
- project: 'margonem/nerthus/it/nerthus.core'
ref: '<branch-under-test>'
file: 'ci/close.gitlab-ci.yml' # Discord /zamknij-sesję (trigger-fired)
- project: 'margonem/nerthus/it/nerthus.core'
ref: '<branch-under-test>'
file: 'ci/reminder.gitlab-ci.yml' # close-reminder (scheduled)
The ref: line is omitted when -ModuleRef is empty, pinning the includes to the
module's default branch instead.
3. Wire GitLab and the edge¶
Seeding does not touch GitLab, Cloudflare, or Discord — the script prints the wiring checklist
on exit. For the test bed, one command stands up all three legs from .env:
./vm/Initialize-NerthusCiTestBed.ps1 -WhatIf # preview every underlying call
./vm/Initialize-NerthusCiTestBed.ps1 # provision GitLab + deploy Worker + register commands
It chains, in order: Set-NerthusGitLabPipelines (CI/CD variables, the three schedules, and the
pipeline trigger token — captured with -PassThruTriggerToken into
GITLAB_TRIGGER_TOKEN), then Publish-NerthusReportWorker (deploys the Worker with
the trigger token and the Discord app vars from .env, overriding the committed production
literals), then Register-NerthusDiscordCommands (the four slash/message commands). It leaves
main unprotected — the test-bed default (a close pushes main; -Force reseeds over it).
The one manual step it cannot do is set the Discord Interactions Endpoint URL to the Worker.
The underlying provisioning — every flag, and the production protected-main variant — is owned
by the two pipeline how-tos; run them directly instead of the bootstrap when you need that control:
- The scheduled jobs — variables, the settle / map-checkup / close-reminder schedules,
and the trigger token — follow Set up the GitLab pipelines. The seed
hands both ticks to the primary (
schedules.*.owner = fleet), so a forced CI schedule runs and exits as a no-op until that owner readsci. - The event-driven report and
/zamknij-sesjęclose — the Cloudflare Worker and its push credentials — follow Set up session reports.
Two things differ on a test repo:
- The module clone URL is public, so
NERTHUS_MODULE_URLneeds no credential — set it tohttps://gitlab.com/margonem/nerthus/it/nerthus.core.gitand you do not pass-ModuleUrlto the seed script at all. - Keep
mainunprotected while you reseed (step 2's prerequisite). Productionmainis unprotected too, so the report and close variables are set unprotected; keep them that way on the test bed. A Protected variable would arrive empty on the unprotected ref and the clone would fail withThe empty string is not a valid path.
4. Reseed and iterate¶
The repo is disposable by design. Re-run step 1's -Force command to raze and reseed
after each change to the templates or the fixture. Because the include: ref and
NERTHUS_MODULE_REF both auto-detect the module checkout's current branch, checking out a
feature branch and reseeding is all it takes to point the whole test bed at that branch's
drivers — no GitLab edit.
5. Test a run¶
With the repo wired, trigger a job from the ▶ play button on the schedule (CI/CD →
Schedules), not the New pipeline button — the scheduled jobs' rules require pipeline
source schedule, so a web run silently produces no job. The mechanics of a safe test
run, and the exit-code triage when one goes red, are in
Set up the GitLab pipelines §5–6.
For the trigger-fired legs (the Discord report / /zamknij-sesję close), an opt-in Pester
harness fires a real trigger pipeline at the test project and polls it to a terminal state:
It skips entirely without NERTHUS_E2E_FIRE=1 and the .env trigger credentials, so the normal
suite never touches the network. It fires at the GitLab trigger, not Discord — the .env holds
only the Ed25519 public key, so a live signed interaction cannot be forged locally.
See also¶
- Set up the GitLab pipelines — the settle / map-checkup / reminder templates, schedules, and variables the test bed wires
- Set up session reports — the report and
/zamknij-sesjęclose front-end the test bed also carries - Adoption & import — the adopted state the seed commits
- Run and write tests — the Pester fixture the seed script builds on