Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
# Lets release.yml call this workflow so a tag can never cut a release
# from checks that differ from what a normal PR has to pass.
workflow_call:

jobs:
lint:
name: Lint scripts and validate compose
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Discover shell scripts
run: |
set -euo pipefail
{
find . -not -path './.git/*' -type f -name '*.sh'
find . -not -path './.git/*' -type f ! -name '*.*' \
-exec sh -c 'head -n1 "$1" | grep -qE "^#!.*(bash|sh)" && echo "$1"' _ {} \;
} | sort -u > shell_scripts.txt
echo "Discovered $(wc -l < shell_scripts.txt) script(s):"
cat shell_scripts.txt

- name: Syntax check (bash -n)
run: |
set -euo pipefail
while IFS= read -r f; do
echo "bash -n $f"
bash -n "$f"
done < shell_scripts.txt

- name: Ensure shellcheck is available
run: |
if ! command -v shellcheck >/dev/null 2>&1; then
sudo apt-get update && sudo apt-get install -y shellcheck
fi
shellcheck --version

- name: shellcheck (error severity)
run: |
set -euo pipefail
# This codebase has never been linted, so a strict run fails on
# pre-existing style findings. Gate on -S error (genuine breakage)
# for now; tightening to `warning` once the backlog is cleaned up
# is a tracked follow-up, not done in this workflow.
xargs shellcheck -S error < shell_scripts.txt

- name: Validate compose files
run: |
set -euo pipefail
find . -not -path './.git/*' -iname 'docker-compose*.yml' | while IFS= read -r f; do
dir=$(dirname "$f")
base=$(basename "$f")
echo "Validating $f"
# prod/docker-compose.yml requires P4PASSWD with no default (by
# design, prod has no baked-in credentials); a placeholder is
# enough to let config parse without starting anything.
(cd "$dir" && P4PASSWD=ci-placeholder-password docker compose -f "$base" config -q)
done
77 changes: 77 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Cut a release by pushing a tag: `git tag v0.1.0 && git push origin v0.1.0`.
# The tag run re-runs the same script and compose validation as CI (so a tag
# can never cut a release from a broken tree), then produces a GitHub Release
# on the Releases page with auto-generated notes from the commit history, and
# publishes the production image to ghcr.io/butterstack/perforce-docker
# tagged with the version and `latest`.
# Merging to main releases nothing - only a `v*` tag push triggers this.
name: release
on:
push:
tags: ["v*"]
permissions:
contents: write
packages: write
jobs:
ci:
uses: ./.github/workflows/ci.yml

release:
needs: ci
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7

- name: Publish release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${GITHUB_REF_NAME}" \
--title "${GITHUB_REF_NAME}" \
--generate-notes \
--verify-tag

image:
needs: ci
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# No docker/setup-qemu-action step here (unlike the connector image):
# QEMU exists to let one amd64 runner cross-build a second
# architecture, and this image only targets linux/amd64 - see why
# below. Add it back if linux/arm64 ever becomes buildable.
#
# helix-p4d has no arm64 build in Perforce's own apt repo (the repo's
# dists/noble/Release lists "Architectures: amd64 i386" only, and its
# binary-arm64/Packages path 404s), confirmed by an actual local arm64
# build attempt failing with "Unable to locate package helix-p4d". So,
# unlike the connector image, this publishes linux/amd64 only. Do not
# add linux/arm64 to the platforms list below without first confirming
# Perforce has shipped an arm64 helix-p4d package - adding it back
# blind reproduces the connector's v0.1.0 issue #5 (buildx silently
# emitting an amd64-only manifest under a multi-arch tag).
#
# There is no multi-stage build here to target: prod/Dockerfile is a
# single FROM ubuntu:24.04 stage, so there is no "runtime"-style stage
# name to pin.
- uses: docker/build-push-action@v7
with:
context: prod
file: prod/Dockerfile
build-contexts: |
shared=shared
platforms: linux/amd64
push: true
provenance: true
sbom: true
tags: |
ghcr.io/butterstack/perforce-docker:${{ github.ref_name }}
ghcr.io/butterstack/perforce-docker:latest
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ cd prod
P4PASSWD=YourSecurePassword123% docker compose up -d
```

`docker compose up` builds the image locally from `prod/Dockerfile`. To use
the prebuilt image from GitHub Container Registry instead (see
[Releases](#releases) below), pull it and run it directly:

```bash
docker pull ghcr.io/butterstack/perforce-docker:v0.1.0
docker run -d --name perforce \
-p 1666:1666 -p 8090:8090 \
-e P4PASSWD=YourSecurePassword123% \
-v perforce_data:/data \
ghcr.io/butterstack/perforce-docker:v0.1.0
```

Connect:
```
Server: ssl:localhost:1666
Expand Down Expand Up @@ -283,6 +296,25 @@ Create a dedicated `service` type user for MCP (no password expiry, revocable ti

See [AGENTS.md](AGENTS.md) for the full setup guide - creating the service account, generating tickets, Docker Compose networking, and an LLM-friendly command reference.

## Releases

Every push to `main` runs CI (script syntax/lint checks, compose file validation) but publishes nothing.

A release is cut by pushing a version tag:

```bash
git tag v0.1.0
git push origin v0.1.0
```

The tag push re-runs the same validation as CI, creates a GitHub Release with notes generated from the commit history, and publishes the production image (`prod/Dockerfile`) to GitHub Container Registry, tagged with both the version and `latest`:

```bash
docker pull ghcr.io/butterstack/perforce-docker:v0.1.0
```

**Architecture: `linux/amd64` only.** Perforce's own apt repository does not ship an `arm64` build of `helix-p4d` (its `noble` release lists `amd64` and `i386` only), so this image cannot be built for `arm64` today. Running it on Apple Silicon or another arm64 host requires emulation (e.g. Docker Desktop/OrbStack's `linux/amd64` emulation), which works but is slower than a native image. If you don't want to rely on an image built by us, or need `arm64`, clone the repo and build it yourself with `docker compose up` as shown above - the compose build honors your host's native architecture.

## License

MIT. See [LICENSE](LICENSE).
Expand Down