# Local Gitea CI (fallback)

> **Status:** fallback / opt-in. The primary CI target is now
> `gitea.lan` with its host-mode `act_runner`. The per-stage CI gate
> closes against `gitea.lan`, not against this stack. Use this
> directory when you want to validate `.gitea/workflows/*` without
> reaching `gitea.lan` — for example, when iterating on a workflow
> file from a flight without LAN access — or when isolating a runner
> issue from production-shaped infrastructure.

Self-contained Gitea + Actions runner for verifying
`.gitea/workflows/*` honestly before pushing to `gitea.lan`. Runs
natively on arm64 (Apple Silicon) — every image below has an arm64
variant, so Docker pulls the right architecture and the runner
executes workflow steps without QEMU emulation.

## Prerequisites

- Docker (Colima or Docker Desktop)
- `python3`, `curl`, `bash` — all built into macOS

## First time

```sh
make -C tools/local-ci up
```

This:

1. brings up the Gitea container;
2. creates an admin user (`galaxy` / `galaxy-dev`);
3. creates the `galaxy/galaxy` repo;
4. fetches a runner registration token from the Gitea API;
5. brings up the runner with that token (the runner persists its
   credentials in a Docker volume and ignores the token on subsequent
   restarts).

The script is idempotent — re-running it is safe.

## Pushing a branch

```sh
make -C tools/local-ci push
```

This adds a `local-gitea` remote on the first run and then pushes the
current `HEAD`. Equivalent manual flow:

```sh
git remote add local-gitea \
    http://galaxy:galaxy-dev@localhost:3000/galaxy/galaxy.git
git push local-gitea HEAD
```

The Tier 1 workflow fires on `push` to any branch and the Tier 2
workflow fires on tags matching `v*`. Watch runs at:

<http://localhost:3000/galaxy/galaxy/actions>

## Operational targets

| Target           | What it does                                 |
| ---------------- | -------------------------------------------- |
| `make up`        | Bring up Gitea + runner (idempotent)         |
| `make down`      | Stop both containers (state preserved)       |
| `make logs`      | Tail logs from both containers               |
| `make status`    | Show container status                        |
| `make push`      | Push current `HEAD` to local Gitea           |
| `make clean`     | Stop and wipe all local state (full reset)   |

## What's in the box

| Component  | Image                              | Role                                        |
| ---------- | ---------------------------------- | ------------------------------------------- |
| Gitea      | `gitea/gitea:1.23`                 | Server with SQLite backend                  |
| act_runner | `gitea/act_runner:0.6.1`           | Single-capacity runner registered on boot   |
| Workflow   | `catthehacker/ubuntu:act-latest`   | Image spawned per job (multi-arch)          |

The runner mounts the host Docker socket and spawns workflow
containers on the same Docker network as Gitea, so
`actions/checkout` reaches the server at `http://gitea:3000` from
inside spawned containers.

## Caveats

- Gitea's `ROOT_URL` is set to `http://gitea:3000/` so spawned
  workflow containers reach the server through the compose network.
  The web UI works at `http://localhost:3000` via port mapping, but
  copy-paste URLs in the UI may show `gitea:3000` instead of
  `localhost:3000`. Harmless for local dev; switch the host part by
  hand when copying.
- The runner is single-capacity (`runner.capacity: 1` in
  `config.yaml`). Concurrent jobs queue. Bump if you need parallel
  jobs.
- First push from a fresh checkout uploads the full repo history
  (~tens of MB). Subsequent pushes are deltas.
- `actions/upload-artifact@v4` requires Gitea ≥ 1.21 — we pin
  `1.23` to stay above the cutoff.
- Workflow steps run as `root` inside the spawned container; this
  matches the upstream catthehacker behaviour. Keep that in mind if
  you add steps that touch host-mounted directories.
- On Apple Silicon the runner image and its catthehacker child run
  natively as arm64. Some pre-built tools that ship in the image are
  amd64-only and would fall back to QEMU; `setup-go`, `setup-node`,
  and `pnpm/action-setup` all download arm64 binaries themselves, so
  the workflow steps we care about stay native.
