Files
stack/.gitea/workflows/deploy.yml
kert b4087a1110
Some checks failed
CI / lint (pull_request) Successful in 34s
CI / notebooks-smoke (pull_request) Successful in 1m29s
CI / test (pull_request) Failing after 2m8s
CI / lint (push) Successful in 34s
CI / test (push) Failing after 2m13s
CI / notebooks-smoke (push) Successful in 1m41s
fix(ci): Deploy detects changed images from the pushed diff, not head_commit.modified
The per-image jobs in deploy.yml were gated on
contains(github.event.head_commit.modified, …). That list only describes
the tip commit of a push and Gitea leaves it empty for merge commits, so
every PR landed with a merge commit skipped all six image builds; the
llm image for #1120 had to be built and deployed by hand.

A new 'changes' job fetches the push's previous tip (github.event.before)
into the shallow checkout and diffs it against the new tip, so the whole
pushed range counts: a merge commit's branch, or all commits of a direct
multi-commit push. Each image job now needs that job and gates on its
boolean output; an image without path filters follows every push. When
the previous tip is unknown or unfetchable (first push, history rewrite)
every image builds rather than none.

Tests cover the generated shape and run the detector script in a scratch
repository for the merge-commit, multi-commit and unknown-tip cases.
2026-10-24 18:23:29 -04:00

398 lines
16 KiB
YAML

# DO NOT EDIT — generated by gen_config.py from stack.toml
# Re-generate: uv run python dev/scripts/gen_config.py
name: Deploy
on:
push:
branches: [main]
jobs:
changes:
runs-on: ubuntu-latest
outputs:
notebooks: ${{ steps.filter.outputs.notebooks }}
zotero: ${{ steps.filter.outputs.zotero }}
docs: ${{ steps.filter.outputs.docs }}
api: ${{ steps.filter.outputs.api }}
llm: ${{ steps.filter.outputs.llm }}
mc: ${{ steps.filter.outputs.mc }}
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Detect changed paths
id: filter
env:
BEFORE: ${{ github.event.before }}
run: |
# `github.event.head_commit.modified` only describes the tip commit
# and is empty for merge commits, so a PR merged with a merge commit
# never matched any path filter. Diff the pushed range instead:
# the branch tip before the push against the new one. When that
# previous tip is unknown or unfetchable (first push, history
# rewrite) build everything rather than nothing.
changed=__all__
if [ -n "$BEFORE" ] && [ "$BEFORE" != "0000000000000000000000000000000000000000" ] \
&& git fetch --quiet --depth=1 origin "$BEFORE"; then
changed="$(git diff --name-only "$BEFORE" "$GITHUB_SHA")" || changed=__all__
fi
echo "changed since $BEFORE:"
printf '%s\n' "$changed"
match() {
name="$1"; shift
hit=false
if [ "$changed" = __all__ ]; then
hit=true
else
while IFS= read -r f; do
[ -n "$f" ] || continue
for g in "$@"; do
case "$f" in $g) hit=true ;; esac
done
done <<< "$changed"
fi
echo "$name=$hit" >> "$GITHUB_OUTPUT"
echo "$name: $hit"
}
match notebooks 'notebooks/*' 'infra/marimo/*' 'infra/images/notebooks.Dockerfile'
match zotero 'infra/images/zotero.Dockerfile' 'infra/images/zotero/*'
match docs 'docs/*' 'infra/images/docs.Dockerfile'
match api 'infra/images/api.Dockerfile' 'src/*' 'pyproject.toml'
match llm 'infra/images/llm.Dockerfile' 'src/*' 'pyproject.toml'
match mc 'infra/rustfs/*' 'infra/images/mc.Dockerfile'
notebooks:
runs-on: ubuntu-latest
needs: [changes]
if: needs.changes.outputs.notebooks == 'true'
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
env:
CRANE_INSECURE: "true"
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
- name: Scan notebooks
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o notebooks-scan.json local/notebooks:build
- name: Compute short SHA
run: echo "SHORT_SHA=$(echo $GITHUB_SHA | head -c 8)" >> "$GITHUB_ENV"
- name: Build notebooks
run: docker build -f infra/images/notebooks.Dockerfile -t local/notebooks:build .
- name: Push notebooks
run: |
docker save local/notebooks:build -o /tmp/notebooks.tar
crane push /tmp/notebooks.tar git:3000/homelab/stack/notebooks:${{ env.SHORT_SHA }} --insecure
crane push /tmp/notebooks.tar git:3000/homelab/stack/notebooks:latest --insecure
- name: File failure issue
if: failure()
env:
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
# Jobs that never install uv (image builds, infra checks) still
# need it here — otherwise the filer dies with "uv: command not
# found" and the failure is never filed.
command -v uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv sync --no-dev --quiet 2>/dev/null || true
uv run python -m api.diag.ci \
--workflow "Deploy" --job "notebooks" \
--run "${{ github.run_number }}" \
--sha "${{ github.sha }}" \
--ref "${{ github.ref }}" || true
zotero:
runs-on: ubuntu-latest
needs: [changes]
if: needs.changes.outputs.zotero == 'true'
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
env:
CRANE_INSECURE: "true"
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
- name: Scan zotero
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o zotero-scan.json local/zotero:build
- name: Compute short SHA
run: echo "SHORT_SHA=$(echo $GITHUB_SHA | head -c 8)" >> "$GITHUB_ENV"
- name: Build zotero
run: docker build -f infra/images/zotero.Dockerfile -t local/zotero:build .
- name: Push zotero
run: |
docker save local/zotero:build -o /tmp/zotero.tar
crane push /tmp/zotero.tar git:3000/homelab/stack/zotero:${{ env.SHORT_SHA }} --insecure
crane push /tmp/zotero.tar git:3000/homelab/stack/zotero:latest --insecure
- name: File failure issue
if: failure()
env:
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
# Jobs that never install uv (image builds, infra checks) still
# need it here — otherwise the filer dies with "uv: command not
# found" and the failure is never filed.
command -v uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv sync --no-dev --quiet 2>/dev/null || true
uv run python -m api.diag.ci \
--workflow "Deploy" --job "zotero" \
--run "${{ github.run_number }}" \
--sha "${{ github.sha }}" \
--ref "${{ github.ref }}" || true
docs:
runs-on: ubuntu-latest
needs: [changes]
if: needs.changes.outputs.docs == 'true'
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
env:
CRANE_INSECURE: "true"
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
- name: Scan docs
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o docs-scan.json local/docs:build
- name: Compute short SHA
run: echo "SHORT_SHA=$(echo $GITHUB_SHA | head -c 8)" >> "$GITHUB_ENV"
- name: Build docs
run: docker build -f infra/images/docs.Dockerfile -t local/docs:build .
- name: Push docs
run: |
docker save local/docs:build -o /tmp/docs.tar
crane push /tmp/docs.tar git:3000/homelab/stack/docs:${{ env.SHORT_SHA }} --insecure
crane push /tmp/docs.tar git:3000/homelab/stack/docs:latest --insecure
- name: File failure issue
if: failure()
env:
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
# Jobs that never install uv (image builds, infra checks) still
# need it here — otherwise the filer dies with "uv: command not
# found" and the failure is never filed.
command -v uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv sync --no-dev --quiet 2>/dev/null || true
uv run python -m api.diag.ci \
--workflow "Deploy" --job "docs" \
--run "${{ github.run_number }}" \
--sha "${{ github.sha }}" \
--ref "${{ github.ref }}" || true
api:
runs-on: ubuntu-latest
needs: [changes]
if: needs.changes.outputs.api == 'true'
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
env:
CRANE_INSECURE: "true"
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
- name: Scan api
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o api-scan.json local/api:build
- name: Compute short SHA
run: echo "SHORT_SHA=$(echo $GITHUB_SHA | head -c 8)" >> "$GITHUB_ENV"
- name: Build api
run: docker build -f infra/images/api.Dockerfile -t local/api:build .
- name: Push api
run: |
docker save local/api:build -o /tmp/api.tar
crane push /tmp/api.tar git:3000/homelab/stack/api:${{ env.SHORT_SHA }} --insecure
crane push /tmp/api.tar git:3000/homelab/stack/api:latest --insecure
- name: File failure issue
if: failure()
env:
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
# Jobs that never install uv (image builds, infra checks) still
# need it here — otherwise the filer dies with "uv: command not
# found" and the failure is never filed.
command -v uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv sync --no-dev --quiet 2>/dev/null || true
uv run python -m api.diag.ci \
--workflow "Deploy" --job "api" \
--run "${{ github.run_number }}" \
--sha "${{ github.sha }}" \
--ref "${{ github.ref }}" || true
llm:
runs-on: ubuntu-latest
needs: [changes]
if: needs.changes.outputs.llm == 'true'
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
env:
CRANE_INSECURE: "true"
- name: Install trivy
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
- name: Scan llm
run: trivy image --severity HIGH,CRITICAL --exit-code 0 --format json -o llm-scan.json local/llm:build
- name: Compute short SHA
run: echo "SHORT_SHA=$(echo $GITHUB_SHA | head -c 8)" >> "$GITHUB_ENV"
- name: Build llm
run: docker build -f infra/images/llm.Dockerfile -t local/llm:build .
- name: Push llm
run: |
docker save local/llm:build -o /tmp/llm.tar
crane push /tmp/llm.tar git:3000/homelab/stack/llm:${{ env.SHORT_SHA }} --insecure
crane push /tmp/llm.tar git:3000/homelab/stack/llm:latest --insecure
- name: File failure issue
if: failure()
env:
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
# Jobs that never install uv (image builds, infra checks) still
# need it here — otherwise the filer dies with "uv: command not
# found" and the failure is never filed.
command -v uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv sync --no-dev --quiet 2>/dev/null || true
uv run python -m api.diag.ci \
--workflow "Deploy" --job "llm" \
--run "${{ github.run_number }}" \
--sha "${{ github.sha }}" \
--ref "${{ github.ref }}" || true
mc:
runs-on: ubuntu-latest
needs: [changes]
if: needs.changes.outputs.mc == 'true'
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Install crane
run: curl -sL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin crane
- name: Log in to registry
run: crane auth login git:3000 -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
env:
CRANE_INSECURE: "true"
- name: Compute short SHA
run: echo "SHORT_SHA=$(echo $GITHUB_SHA | head -c 8)" >> "$GITHUB_ENV"
- name: Build mc
run: docker build -f infra/images/mc.Dockerfile -t local/mc:build infra/rustfs/
- name: Push mc
run: |
docker save local/mc:build -o /tmp/mc.tar
crane push /tmp/mc.tar git:3000/homelab/stack/mc:${{ env.SHORT_SHA }} --insecure
crane push /tmp/mc.tar git:3000/homelab/stack/mc:latest --insecure
- name: File failure issue
if: failure()
env:
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
# Jobs that never install uv (image builds, infra checks) still
# need it here — otherwise the filer dies with "uv: command not
# found" and the failure is never filed.
command -v uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv sync --no-dev --quiet 2>/dev/null || true
uv run python -m api.diag.ci \
--workflow "Deploy" --job "mc" \
--run "${{ github.run_number }}" \
--sha "${{ github.sha }}" \
--ref "${{ github.ref }}" || true
report:
runs-on: ubuntu-latest
needs: [changes, notebooks, zotero, docs, api, llm, mc]
if: always() && !cancelled()
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Set up uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
env:
UV_INSTALL_DIR: /usr/local/bin
- name: Report
# Only file an issue if at least one upstream build job failed.
# Without this gate api.diag.ci runs unconditionally and files
# a fake "failed" issue on every successful Deploy run.
if: contains(needs.*.result, 'failure')
env:
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
uv sync --no-dev --quiet 2>/dev/null || true
uv run python -m api.diag.ci \
--workflow "Deploy" --job "report" \
--run "${{ github.run_number }}" \
--sha "${{ github.sha }}" \
--ref "${{ github.ref }}" || true