- Dockerfile 72.6%
- Just 21.9%
- Shell 5.5%
|
All checks were successful
release / build-agent (push) Successful in 20s
release / build-ci-base-infra (push) Successful in 34s
qa / qa (push) Successful in 49s
release / build-bun (push) Successful in 1m19s
release / build-ci-base (push) Successful in 2m37s
release / build-android-ci (push) Successful in 2m24s
Debian trixie has no openjdk-17 (ships JDK 21); use openjdk-21-jdk-headless, which also matches the geneathing Gradle daemon pin. Verify cargo-ndk via `cargo ndk --version` (the bare binary rejects --version). Image builds clean locally and the toolchain (JDK 21, rust android targets, cargo-ndk 3.5.4, NDK 27, SDK 36, just, jq) is present. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| biome | ||
| docker | ||
| husky | ||
| just | ||
| pi-skills | ||
| tsconfig | ||
| .gitignore | ||
| README.md | ||
forge-base
Reusable Docker base images, callable Forgejo workflows, justfile recipes,
and tooling configs shared across personal projects on forge.jacquin.app.
Solo-friendly — every consumer (human or agent) inherits the same baseline without copy-paste drift.
Repo must stay public — Forgejo cross-repo
uses:requires the callee to be publicly readable (no token-based cross-repo auth as of Forgejo 15.0).
What's in v0.4.1
| Path | Purpose |
|---|---|
docker/bun.Dockerfile |
Bun + git + just + ca-certs base. Multi-arch. |
docker/ci-base.Dockerfile |
Rust + just + tombi + rumdl. Multi-arch. General-purpose CI image. |
docker/ci-base-infra.Dockerfile |
Terraform + Ansible + just + gitleaks. amd64-only. For IaC repos. |
docker/agent.Dockerfile |
CI coding-agent runtime — Pi + umans provider + forge CLI. amd64-only. |
docker/android-ci.Dockerfile |
Rust + cargo-ndk + JDK 17 + Android SDK/NDK + just. amd64-only. For Android apps with a Rust core. |
.forgejo/workflows/qa-bun.yml |
Reusable workflow — Bun project QA pipeline (typecheck/lint/fmt-check/test). |
.forgejo/workflows/qa-generic.yml |
Reusable workflow — Language-agnostic QA pipeline (fmt-check/lint/test). |
.forgejo/workflows/build-bun-tarball.yml |
Reusable workflow — Bun release artifact build + package. |
.forgejo/workflows/release-generic.yml |
Reusable workflow — Dual-arch build + package + Forgejo release. |
.forgejo/workflows/pi-agent.yml |
Reusable workflow — AI coding bot. Label an issue → Pi implements + opens PR → reviewer pass. |
.forgejo/workflows/deploy.yml |
Reusable workflow — Trigger proxmox-iac deployment. |
.forgejo/workflows/destroy.yml |
Reusable workflow — Trigger proxmox-iac teardown. |
pi-skills/ |
Generic Pi skills (forgejo-cli, forgejo-branch-checkout) baked into the agent image. |
just/{shared,bun,rust}.just |
Reusable just recipes. Inherited via import — see below. |
husky/pre-push |
Full QA gate (just qa) — blocks git push on test/typecheck failure. |
tsconfig/base.json |
Strict TypeScript baseline. |
biome/base.json |
Biome lint/format defaults. |
.forgejo/workflows/release.yml |
This repo's self-release pipeline — builds + publishes all Docker images on tag v*. |
.forgejo/workflows/qa.yml |
This repo's self-CI — validates justfile + Dockerfile + workflow YAML. |
Versioning
Tag every meaningful change. Downstream projects pin forge-base@vX.Y.Z.
SemVer applies — bump major when a recipe contract or workflow input changes.
Justfile inheritance
Forge-base provides shared justfile recipes that projects import instead of
copy-pasting. This eliminates drift — change qa, lint, fmt-check etc. in
one place and every consumer gets the fix.
How it works
Every CI image (ci-base, ci-base-infra, bun) bakes the just recipes at
/opt/forge-base/just/. The reusable workflows copy them into
.forge-base/just/ before calling just. For local dev, use git subtree.
In both cases the project justfile imports from the same relative path:
import '.forge-base/just/shared.just' # provides ci-package, _default
import '.forge-base/just/rust.just' # provides qa, fmt-check, lint, test, ci-setup, ci-build
Recipes defined after the import override the imported ones — just's
standard rule. This means a project can keep its import lines and only define
what differs.
Before vs after
Before (copy-paste, ~140 lines per project):
# Every Rust repo had its own copy of these identical recipes:
qa:
@echo "=== Formatting ==="
@just fmt
@just lint
@just test
@echo "All quality checks passed!"
fmt-check:
cargo fmt --all -- --check
clippy:
cargo clippy -- -D warnings
lint: clippy lint-toml lint-md
lint-toml:
tombi check .
lint-md:
rumdl check .
test:
cargo test
ci-setup:
cargo fetch
ci-build:
cargo build --release
# ... plus 15 project-specific recipes
After (inheritance, ~60 lines):
import '.forge-base/just/shared.just'
import '.forge-base/just/rust.just'
# Project-specific recipes only
run *ARGS:
cargo run -- {{ARGS}}
build:
cargo build
install:
cargo build --release
cp target/release/myapp ~/.local/bin/
# Overrides (defined after import → win over the base)
ci-setup:
apt-get update && apt-get install -y musl-tools
rustup target add x86_64-unknown-linux-musl
ci-build:
cargo build --release --target x86_64-unknown-linux-musl
What each file provides
| File | Recipes |
|---|---|
shared.just |
_default, ci-package |
rust.just |
qa, fmt, fmt-check, clippy, lint, lint-toml, lint-md, test, ci-setup, ci-build |
bun.just |
qa, dev, start, typecheck, lint, lint-fix, fmt, fmt-check, test, ci-setup, ci-build |
Local dev — git subtree
git subtree add --prefix=.forge-base \
https://forge.jacquin.app/charles/forge-base.git v0.4.1 --squash
Bump version: git subtree pull --prefix=.forge-base ... v0.4.1 --squash.
CI — automatic
The reusable workflows handle this. They copy /opt/forge-base/just/* into
.forge-base/just/ before calling just. No project configuration needed.
Consumption
CI — reusable workflow refs (Forgejo 15+)
Note:
cache-keyis computed inside the reusable workflow. Do NOT pass${{ hashFiles(...) }}viawith:— Forgejo'sworkflow_calldoes not supporthashFiles()in input values.
Bun project — QA
name: qa
on:
push:
pull_request:
branches: [main]
jobs:
qa:
uses: charles/forge-base/.forgejo/workflows/qa-bun.yml@v0.4.3
with:
image_tag: v0.4.3
Bun project — Release
name: release
on:
push:
tags: [v*]
jobs:
build-x86_64:
uses: charles/forge-base/.forgejo/workflows/build-bun-tarball.yml@v0.4.3
with:
arch: x86_64
image_tag: v0.4.3
artifact_name: dist-x86_64
build-aarch64:
uses: charles/forge-base/.forgejo/workflows/build-bun-tarball.yml@v0.4.3
with:
arch: aarch64
image_tag: v0.4.3
artifact_name: dist-aarch64
Rust / Generic project — QA
name: qa
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
qa:
uses: charles/forge-base/.forgejo/workflows/qa-generic.yml@v0.4.3
with:
container: forge.jacquin.app/charles/forge-base/ci-base:v0.4.1
cache-path: |
~/.cargo/registry
~/.cargo/git
target
Rust / Generic project — Release
name: release
on:
push:
tags: [v*]
jobs:
release:
uses: charles/forge-base/.forgejo/workflows/release-generic.yml@v0.4.3
with:
container: forge.jacquin.app/charles/forge-base/ci-base:v0.4.1
cache-path: |
~/.cargo/registry
~/.cargo/git
target
IaC project — QA + Deploy
# qa.yml
name: qa
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
qa:
uses: charles/forge-base/.forgejo/workflows/qa-generic.yml@v0.4.3
with:
container: forge.jacquin.app/charles/forge-base/ci-base-infra:v0.4.1
# deploy.yml
name: deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: charles/forge-base/.forgejo/workflows/deploy.yml@v0.4.3
with:
iac_dir: iac/prod
service_vars: '{"myapp_ct_id": "246", "myapp_ct_ip": "192.168.1.246/24"}'
secrets:
FORGEJO_TOKEN: ${{ secrets.FORGE_TOKEN }}
CI — AI coding bot (pi-agent)
Label an issue and a Pi agent implements it, opens a PR, then reviews its own
diff. Add .forgejo/workflows/pi-agent.yml to the consumer repo:
name: pi-agent
on:
issues:
types: [labeled]
concurrency:
group: pi-agent-issue-${{ github.event.issue.number }}
cancel-in-progress: true
jobs:
agent:
if: ${{ github.event.label.name == 'pi-agent' }}
uses: charles/forge-base/.forgejo/workflows/pi-agent.yml@v0.4.3
with:
image_tag: v0.4.3
bot_name: my-bot
bot_email: my-bot@example.com
Then add a .pi-agent/ directory to the repo:
.pi-agent/
├── settings.json # Pi config (provider/model/compaction)
├── ci-coder.md # coder system prompt — stack conventions + git workflow
├── ci-reviewer.md # reviewer system prompt
└── skills/ # PROJECT-specific Pi skills (auto-discovered, optional)
Generic skills (forgejo-cli, forgejo-branch-checkout) are baked into the
agent image — do not copy them into .pi-agent/skills/. Inputs config_dir
(default .pi-agent), label (default pi-agent) and model (default
umans-coder) are overridable.
Docker — remote ref
# Bun projects
FROM forge.jacquin.app/charles/forge-base/bun:v0.4.1
Pinning
@vX.Y.Z (tag) or @<sha> both work. Tags are mutable so SHA-pinning is
safer for prod-critical pipelines; tags are fine for everything else and
make Renovate-style auto-bumps trivial.
Justfile + tsconfig + biome — git subtree
git subtree add --prefix=.forge-base \
https://forge.jacquin.app/charles/forge-base.git v0.4.1 --squash
Then in justfile:
import '.forge-base/just/shared.just'
import '.forge-base/just/bun.just' # or rust.just
In tsconfig.json:
{ "extends": "./.forge-base/tsconfig/base.json" }
In biome.json:
{ "extends": ["./.forge-base/biome/base.json"] }
In .husky/pre-push:
#!/usr/bin/env sh
exec sh .forge-base/husky/pre-push "$@"
Bump version: git subtree pull --prefix=.forge-base ... v0.4.1 --squash.
Justfile contract for downstream projects
Reusable workflows assume the caller's justfile exposes:
| Recipe | Used by | Notes |
|---|---|---|
ci-setup |
qa-bun, qa-generic, build-bun-tarball, release-generic | Install workspace deps (e.g. bun install, cargo fetch). |
typecheck |
qa-bun | bun x turbo run typecheck (Bun only). |
fmt-check |
qa-bun, qa-generic | Check formatting without modifying files. |
lint |
qa-bun, qa-generic | Run all linters. |
test |
qa-bun, qa-generic | Run test suite. |
ci-build |
build-bun-tarball, release-generic | Build release artifacts. |
ci-package |
build-bun-tarball, release-generic | Pack into dist/${PROJECT_NAME}-${VERSION}-${ARCH}-linux.tar.gz. Reads ARCH, VERSION, PROJECT_NAME env vars. |
agent-setup |
pi-agent | Optional. Install the project's language toolchain inside the agent run. Skipped if undefined. |
just/bun.just and just/rust.just define these recipes. Import them and
override only what differs for your project.
Docker images
| Image | Base | Platforms | Key tools |
|---|---|---|---|
forge-base/bun |
debian:bookworm-slim | amd64, arm64 | Bun, just |
forge-base/ci-base |
debian:trixie-slim | amd64, arm64 | Rust (stable + clippy + rustfmt), just, tombi, rumdl |
forge-base/ci-base-infra |
alpine:3.21 | amd64 | Terraform, Ansible, just, gitleaks, yamlfmt, shellcheck, rumdl |
forge-base/agent |
node:22-alpine | amd64 | Pi agent, forge CLI, just |
forge-base/android-ci |
debian:trixie-slim | amd64 | Rust (+ android targets), cargo-ndk, JDK 17, Android SDK 36 + NDK, just, jq |
Repo conventions
Per global Forgejo Actions naming:
- Workflow names:
qa(push + PR),release(tagv*) - Job IDs:
qa; for releasesbuild-x86_64,build-aarch64,publish - Architecture naming:
x86_64/aarch64(Rust target-triple convention)
Container registry secret
The release workflow pushes images to Forgejo's container registry. Add a
repo secret PACKAGE_TOKEN containing a Forgejo PAT with write:package
scope. The workflow logs in as the repo owner.