diff --git a/.github/renovate.json b/.github/renovate.json index 54ea4d4..840b8da 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,23 +1,24 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base", - "docker:enableMajor", - "default:automergeDigest" - ], - "packageRules": [ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base", + "docker:enableMajor", + "default:automergeDigest", + "helpers:pinGitHubActionDigestsToSemver" + ], + "internalChecksFilter": "strict", + "packageRules": [ { - "matchDatasources": ["docker"], - "matchPackageNames": [ - "linuxserver/swag", - "actions/checkout", - "docker/setup-buildx-actio", - "docker/login-action", - "docker/metadata-action", - "docker/build-push-action" - ], - "matchUpdateTypes": ["minor", "patch", "pin", "digest"], + "description": "Keep ghcr.io/linuxserver/nginx digest-pinned and automerge updates when checks pass", + "matchManagers": ["dockerfile"], + "matchPackageNames": ["ghcr.io/linuxserver/nginx"], + "pinDigests": true, + "automerge": true + }, + { + "description": "Automerge GitHub Actions updates, including majors, when checks pass", + "matchManagers": ["github-actions"], "automerge": true } ] - } +} diff --git a/.github/scripts/test-image.sh b/.github/scripts/test-image.sh new file mode 100755 index 0000000..078f82b --- /dev/null +++ b/.github/scripts/test-image.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# Runtime checks for the Notifiarr nginx image. +# Usage: test-image.sh [bot] +# Does not publish ports or remove any container it did not create. +set -euo pipefail + +image=${1:?usage: test-image.sh [bot]} +mode=${2:-app} + +fail() { + echo "FAIL: $*" >&2 + exit 1 +} + +run() { + docker run --rm --network bridge --entrypoint "$1" "$image" "${@:2}" +} + +echo "image: $image" +echo "mode: $mode" + +version=$(run terser --version) +echo "terser $version" +[[ "$version" =~ [0-9]+\.[0-9]+ ]] || fail "unexpected terser version: $version" + +js='function hello(name) { var message = "Hello " + name; console.log(message); }' +minified=$(printf '%s\n' "$js" | docker run --rm -i --network bridge --entrypoint terser "$image" --compress --mangle | tr -d '[:space:]') +[[ -n "$minified" ]] || fail "terser produced no output" +[[ ${#minified} -lt ${#js} ]] || fail "terser did not shrink the sample: $minified" +echo "terser minify ok (${#js} -> ${#minified})" + +run node -e 'if (!process.version) process.exit(1)' +echo "node ok" + +run sh -c 'php -m | grep -qx ddtrace' +echo "php ddtrace ok" + +run sh -c 'echo hi | gzip | gzip -dc | grep -qx hi' +echo "gzip ok" +run curl --version >/dev/null +echo "curl ok" + +run sh -c 'getent group unraiddocker | grep -q ":281:" && id abc | grep -q "(unraiddocker)"' +echo "unraiddocker group ok" + +if [[ "$mode" == bot ]]; then + run ssh -V + run docker --version + echo "bot packages ok" +fi + +name="notifiarr-nginx-citest-$$" +if docker container inspect "$name" >/dev/null 2>&1; then + fail "container $name already exists; refusing to replace it" +fi + +cleanup() { + docker rm -f "$name" >/dev/null 2>&1 || true +} +trap cleanup EXIT INT TERM + +docker run -d --name "$name" --network bridge \ + -e PUID=1000 \ + -e PGID=1000 \ + -e TZ=Etc/UTC \ + "$image" >/dev/null + +code= +for _ in $(seq 1 40); do + running=$(docker inspect -f '{{.State.Running}}' "$name" 2>/dev/null || echo false) + if [[ "$running" != true ]]; then + docker logs "$name" >&2 || true + fail "nginx container exited during startup" + fi + code=$(docker exec "$name" curl -s -o /dev/null -w '%{http_code}' --max-time 3 http://127.0.0.1/ || true) + case "$code" in + 200|301|302|303|307|308|401|403|404) + echo "nginx http $code" + exit 0 + ;; + esac + sleep 2 +done + +docker logs "$name" >&2 || true +fail "nginx did not answer on port 80 (last status: ${code:-none})" diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index cbffe24..eb0ec1e 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,80 +1,102 @@ name: Docker -# This all came from github. I wrote none of it, but I did remove some bits that didn't work. - on: push: - branches: [ "main" ] - # Publish semver tags as releases. - tags: [ 'v*.*.*' ] + branches: ["main"] + tags: ["v*.*.*"] pull_request: - branches: [ "main" ] + branches: ["main"] env: - # Use docker.io for Docker Hub if empty REGISTRY: ghcr.io - # github.repository as / IMAGE_NAME: ${{ github.repository }} jobs: build: - runs-on: ubuntu-latest permissions: contents: read packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. + # Provenance attestation when the image is pushed. id-token: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - # Workaround: https://github.com/docker/build-push-action/issues/461 - name: Setup Docker buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' - uses: docker/login-action@v3 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@v5 + - name: Build image + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} + load: true + push: false + provenance: false + tags: nginx:ci labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Build and push bot Docker image - id: build-and-push-botmanager - uses: docker/build-push-action@v5 + cache-to: | + type=gha,mode=max + type=local,dest=/tmp/buildx-nginx,mode=max + + - name: Test image + run: bash .github/scripts/test-image.sh nginx:ci + + - name: Build botmanager image + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . - build-args: + load: true + push: false + provenance: false + build-args: | + INSTALL_PACKAGES=openssh-client docker gzip + tags: nginx:ci-bot + labels: ${{ steps.meta.outputs.labels }} + cache-from: | + type=local,src=/tmp/buildx-nginx + type=gha + cache-to: | + type=gha,mode=max + type=local,dest=/tmp/buildx-bot,mode=max + + - name: Test botmanager image + run: bash .github/scripts/test-image.sh nginx:ci-bot bot + + - name: Push image + if: github.event_name != 'pull_request' + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=local,src=/tmp/buildx-nginx + + - name: Push botmanager image + if: github.event_name != 'pull_request' + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: . + push: true + build-args: | INSTALL_PACKAGES=openssh-client docker gzip - push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }}_botmanager labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=local,src=/tmp/buildx-bot diff --git a/LICENSE b/LICENSE index 6906ae7..1523baf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Notifiarr +Copyright (c) 2023-2026 Notifiarr Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal