ci: build image and release artifact on push to main
Modeled on kejadlen/ketchup's ci.yml, minus its deploy and
snapshot jobs; deploy waits until where the database lives is decided.

Assisted-by: GLM-5.3 via pi
change lvrsknlunpntvqnwxsrymmvurqkrtqkp
commit 125b79f82c3be024ab3a1801006a2e8c2194fd00
author Alpha Chen <alpha@kejadlen.dev>
date
parent owzovkot
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..ecf73c9
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,21 @@
+.git
+.jj
+.github
+.claude
+.direnv
+.envrc
+.quire
+.ramekin
+# data/ is the real address book; log/ holds unhandled captures with
+# every header. Neither belongs in an image.
+data
+log
+tmp
+docs
+test
+servers
+clients
+monica
+baikal
+work
+carddav.mobileconfig
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..311cb68
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,90 @@
+name: CI
+
+on:
+  push:
+    branches: [main]
+  pull_request:
+    branches: [main]
+
+permissions: {}
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+jobs:
+  test:
+    name: Test
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read # checkout
+    steps:
+      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+        with:
+          persist-credentials: false
+      - uses: ruby/setup-ruby@e65c17d16e57e481586a6a5a0282698790062f92 # v1
+        with:
+          bundler-cache: true
+      - run: bundle exec rake
+
+  zizmor:
+    name: Audit workflows
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read # checkout
+      security-events: write # upload SARIF to code scanning
+    steps:
+      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+        with:
+          persist-credentials: false
+      - uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
+
+  build:
+    name: Build
+    needs: test
+    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write # create GitHub release
+      packages: write # push container image to GHCR
+    outputs:
+      version: ${{ steps.meta.outputs.version }}
+      image: ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.version }}
+    steps:
+      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+        with:
+          persist-credentials: false
+      - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
+      - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      - id: short-sha
+        run: echo "sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
+      - id: change-id
+        run: echo "id=$(git cat-file -p HEAD | grep '^change-id ' | cut -d' ' -f2)" >> "$GITHUB_OUTPUT"
+      - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
+        id: meta
+        with:
+          images: ghcr.io/${{ github.repository }}
+          tags: |
+            type=raw,value={{date 'YYYYMMDD-HHmm'}}-${{ steps.short-sha.outputs.sha }}
+      - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
+        with:
+          context: .
+          push: true
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+          build-args: |
+            COMMIT_SHA=${{ steps.short-sha.outputs.sha }}
+            CHANGE_ID=${{ steps.change-id.outputs.id }}
+            BUILD_DATE=${{ github.event.head_commit.timestamp }}
+          cache-from: type=gha
+          cache-to: type=gha,mode=max
+      - env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          STEPS_META_OUTPUTS_VERSION: ${{ steps.meta.outputs.version }}
+        # --target names the commit explicitly: persist-credentials is
+        # false, so the tag cannot ride the leftover git credentials.
+        run: gh release create "${STEPS_META_OUTPUTS_VERSION}" --target "$GITHUB_SHA" --generate-notes
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..990aaf6
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,31 @@
+FROM ruby:4.0-slim AS build
+
+RUN apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+
+COPY Gemfile Gemfile.lock ./
+RUN bundle config set without development && bundle install
+
+COPY . .
+
+FROM ruby:4.0-slim
+
+ARG COMMIT_SHA
+ARG CHANGE_ID
+ARG BUILD_DATE
+ENV COMMIT_SHA=${COMMIT_SHA}
+ENV CHANGE_ID=${CHANGE_ID}
+ENV BUILD_DATE=${BUILD_DATE}
+# Set TZ at runtime — Date.today drives the birthday list's rollover.
+
+WORKDIR /app
+
+COPY --from=build /usr/local/bundle /usr/local/bundle
+COPY --from=build /app /app
+
+EXPOSE 9292
+
+# 0.0.0.0 reaches only the container's own network namespace — exposure
+# to clients is tailscale serve's job, in front of the published port.
+CMD ["rackup", "-o", "0.0.0.0"]