diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fa50b72..73ac8ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build SimpleLogin-AIO +name: CI / SimpleLogin-AIO on: push: @@ -7,18 +7,20 @@ on: - 'Dockerfile' - 'rootfs/**' - 'scripts/**' - - 'simplelogin-aio.xml' - 'upstream.toml' - - '.github/workflows/build.yml' + - 'simplelogin-aio.xml' + - 'renovate.json' + - '.github/workflows/**' pull_request: branches: [ main ] paths: - 'Dockerfile' - 'rootfs/**' - 'scripts/**' - - 'simplelogin-aio.xml' - 'upstream.toml' - - '.github/workflows/build.yml' + - 'simplelogin-aio.xml' + - 'renovate.json' + - '.github/workflows/**' workflow_dispatch: inputs: run_smoke_test: @@ -42,7 +44,102 @@ concurrency: cancel-in-progress: true jobs: - validate: + detect-changes: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + build_related: ${{ steps.filter.outputs.build_related }} + xml_related: ${{ steps.filter.outputs.xml_related }} + renovate_related: ${{ steps.filter.outputs.renovate_related }} + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + + - name: Classify changed files + id: filter + env: + EVENT_NAME: ${{ github.event_name }} + BEFORE_SHA: ${{ github.event.before || '' }} + BASE_SHA: ${{ github.event.pull_request.base.sha || '' }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + GITHUB_SHA_VALUE: ${{ github.sha }} + run: | + if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then + echo "build_related=true" >> "${GITHUB_OUTPUT}" + echo "xml_related=true" >> "${GITHUB_OUTPUT}" + echo "renovate_related=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + if [[ "${EVENT_NAME}" == "pull_request" ]]; then + base="${BASE_SHA}" + head="${HEAD_SHA}" + else + base="${BEFORE_SHA}" + head="${GITHUB_SHA_VALUE}" + fi + + if ! git rev-parse --git-dir >/dev/null 2>&1; then + echo "Git metadata is unavailable; treating the workflow as fully impacted." + echo "build_related=true" >> "${GITHUB_OUTPUT}" + echo "xml_related=true" >> "${GITHUB_OUTPUT}" + echo "renovate_related=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + if [[ -z "${base}" || "${base}" =~ ^0+$ ]]; then + if ! changed_files="$(git show --pretty='' --name-only "${head}" 2>/dev/null)"; then + echo "Unable to inspect commit range; treating the workflow as fully impacted." + echo "build_related=true" >> "${GITHUB_OUTPUT}" + echo "xml_related=true" >> "${GITHUB_OUTPUT}" + echo "renovate_related=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi + else + if ! changed_files="$(git diff --name-only "${base}" "${head}" 2>/dev/null)"; then + echo "Unable to diff commit range; treating the workflow as fully impacted." + echo "build_related=true" >> "${GITHUB_OUTPUT}" + echo "xml_related=true" >> "${GITHUB_OUTPUT}" + echo "renovate_related=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi + fi + + printf 'Changed files:\n%s\n' "${changed_files}" + + build_related=false + xml_related=false + renovate_related=false + + while IFS= read -r path; do + [[ -z "${path}" ]] && continue + case "${path}" in + Dockerfile|upstream.toml|rootfs/*|scripts/*) + build_related=true + ;; + simplelogin-aio.xml) + xml_related=true + ;; + renovate.json) + renovate_related=true + ;; + .github/workflows/*) + build_related=true + renovate_related=true + ;; + esac + done <<< "${changed_files}" + + echo "build_related=${build_related}" >> "${GITHUB_OUTPUT}" + echo "xml_related=${xml_related}" >> "${GITHUB_OUTPUT}" + echo "renovate_related=${renovate_related}" >> "${GITHUB_OUTPUT}" + + validate-repo: + needs: detect-changes + if: ${{ needs.detect-changes.outputs.build_related == 'true' || needs.detect-changes.outputs.xml_related == 'true' || needs.detect-changes.outputs.renovate_related == 'true' }} runs-on: ubuntu-latest permissions: contents: read @@ -50,21 +147,80 @@ jobs: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Validate scripts and metadata + - name: Validate shell and python scripts + if: ${{ needs.detect-changes.outputs.build_related == 'true' }} run: | bash -n scripts/smoke-test.sh - python3 -m py_compile scripts/check-upstream.py + PYTHONPYCACHEPREFIX=/tmp/simplelogin-aio-pyc python3 -m py_compile scripts/check-upstream.py find rootfs -type f -name '*.sh' -print0 | xargs -0 -n1 bash -n find rootfs -type f -path '*/run' -print0 | xargs -0 -n1 bash -n + + - name: Validate XML + if: ${{ needs.detect-changes.outputs.build_related == 'true' || needs.detect-changes.outputs.xml_related == 'true' }} + run: | python3 - <<'PY' import xml.etree.ElementTree as ET - ET.parse("simplelogin-aio.xml") - print("Parsed simplelogin-aio.xml successfully") + ET.parse('simplelogin-aio.xml') + print('simplelogin-aio.xml parsed successfully') PY + - name: Validate Renovate config + if: ${{ needs.detect-changes.outputs.renovate_related == 'true' }} + run: npx --yes --package renovate renovate-config-validator renovate.json + + pinned-actions: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Enforce pinned action SHAs + run: | + python3 - <<'PY' + import pathlib + import re + import sys + + workflow_dir = pathlib.Path(".github/workflows") + pattern = re.compile(r"^\s*uses:\s*([^@\s]+)@([^\s#]+)") + sha_pattern = re.compile(r"^[0-9a-f]{40}$") + failures = [] + + for path in sorted(workflow_dir.glob("*.yml")): + for lineno, line in enumerate(path.read_text().splitlines(), start=1): + match = pattern.match(line) + if not match: + continue + target, ref = match.groups() + if target.startswith("./"): + continue + if not sha_pattern.fullmatch(ref): + failures.append(f"{path}:{lineno}: action is not pinned to a full SHA -> {line.strip()}") + + if failures: + print("\n".join(failures), file=sys.stderr) + sys.exit(1) + print("All workflow actions are pinned to full commit SHAs.") + PY + + dependency-review: + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Dependency review + uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 + smoke-test: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_smoke_test == true }} - needs: validate + if: ${{ needs.detect-changes.outputs.build_related == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_smoke_test == true) }} + needs: + - detect-changes + - validate-repo + - pinned-actions runs-on: ubuntu-latest permissions: contents: read @@ -79,9 +235,9 @@ jobs: uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . + platforms: linux/amd64 load: true tags: simplelogin-aio:ci - platforms: linux/amd64 cache-from: type=gha cache-to: type=gha,mode=max @@ -99,9 +255,11 @@ jobs: docker logs simplelogin-aio-smoke || true publish: - if: ${{ github.event_name != 'pull_request' && ((github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && inputs.publish_image == true)) }} + if: ${{ needs.detect-changes.outputs.build_related == 'true' && github.event_name != 'pull_request' && ((github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && inputs.publish_image == true)) }} needs: - - validate + - detect-changes + - validate-repo + - pinned-actions - smoke-test runs-on: ubuntu-latest permissions: @@ -126,30 +284,81 @@ jobs: run: | image="${REGISTRY}/${IMAGE_NAME}" sha_tag="${image}:sha-${GITHUB_SHA}" - version="$(awk -F= '/^ARG UPSTREAM_VERSION=/{print $2; exit}' Dockerfile)" + version="$(sed -n 's/^ARG UPSTREAM_VERSION=//p' Dockerfile | head -n1)" version_no_v="${version#v}" - IFS='.' read -r major minor patch <<< "${version_no_v}" { + echo "upstream_version=${version}" echo "tags<> "${GITHUB_OUTPUT}" - - name: Build and push amd64 image + - name: Build and push uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . - push: true - tags: ${{ steps.prep.outputs.tags }} platforms: linux/amd64 + push: true cache-from: type=gha cache-to: type=gha,mode=max + tags: ${{ steps.prep.outputs.tags }} + labels: | + org.opencontainers.image.source=https://github.com/JSONbored/simplelogin-aio + org.opencontainers.image.title=simplelogin-aio + org.opencontainers.image.version=${{ steps.prep.outputs.upstream_version || '' }} + io.jsonbored.upstream.name=SimpleLogin + + sync-awesome-unraid: + if: ${{ needs.detect-changes.outputs.xml_related == 'true' && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + needs: + - detect-changes + - validate-repo + - pinned-actions + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check sync configuration + env: + SYNC_TOKEN: ${{ secrets.SYNC_TOKEN }} + run: | + if [[ -z "${SYNC_TOKEN}" ]]; then + echo "SYNC_ENABLED=false" >> "${GITHUB_ENV}" + echo "SYNC_TOKEN is not configured; skipping sync." + else + echo "SYNC_ENABLED=true" >> "${GITHUB_ENV}" + fi + + - name: Checkout Source Repository + if: ${{ env.SYNC_ENABLED == 'true' }} + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Checkout Target Repository + if: ${{ env.SYNC_ENABLED == 'true' }} + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + repository: JSONbored/awesome-unraid + token: ${{ secrets.SYNC_TOKEN }} + path: target-repo + + - name: Copy and commit template + if: ${{ env.SYNC_ENABLED == 'true' }} + run: | + cp simplelogin-aio.xml target-repo/simplelogin-aio.xml + cd target-repo + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add simplelogin-aio.xml + git diff --quiet && git diff --staged --quiet || (git commit -m "chore: auto-sync simplelogin-aio.xml from upstream" && git push) diff --git a/.github/workflows/sync-template.yml b/.github/workflows/sync-template.yml deleted file mode 100644 index ced2b5f..0000000 --- a/.github/workflows/sync-template.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Sync Template To Awesome-Unraid - -on: - push: - branches: [ main ] - paths: - - 'simplelogin-aio.xml' - - '.github/workflows/sync-template.yml' - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - sync: - runs-on: ubuntu-latest - env: - TEMPLATE_XML: simplelogin-aio.xml - TEMPLATE_ICON_PATH: ${{ vars.TEMPLATE_ICON_PATH }} - TARGET_REPOSITORY: ${{ vars.AWESOME_UNRAID_REPOSITORY || 'JSONbored/awesome-unraid' }} - TARGET_XML_NAME: ${{ vars.AWESOME_UNRAID_XML_NAME || 'simplelogin-aio.xml' }} - TARGET_ICON_NAME: ${{ vars.AWESOME_UNRAID_ICON_NAME }} - steps: - - name: Check sync configuration - env: - ENABLE_AIO_AUTOMATION: ${{ vars.ENABLE_AIO_AUTOMATION }} - SYNC_TOKEN: ${{ secrets.SYNC_TOKEN }} - run: | - if [[ "${ENABLE_AIO_AUTOMATION}" != "true" || -z "${TARGET_REPOSITORY}" || -z "${TARGET_XML_NAME}" || -z "${SYNC_TOKEN}" ]]; then - echo "SYNC_ENABLED=false" >> "${GITHUB_ENV}" - echo "Template sync is not fully configured; skipping." - else - echo "SYNC_ENABLED=true" >> "${GITHUB_ENV}" - fi - - - name: Checkout Source Repository - if: ${{ env.SYNC_ENABLED == 'true' }} - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Checkout Target Repository - if: ${{ env.SYNC_ENABLED == 'true' }} - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - repository: ${{ env.TARGET_REPOSITORY }} - token: ${{ secrets.SYNC_TOKEN }} - path: target-repo - - - name: Copy and Commit Template - if: ${{ env.SYNC_ENABLED == 'true' }} - run: | - cp "${TEMPLATE_XML}" "target-repo/${TARGET_XML_NAME}" - if [[ -n "${TEMPLATE_ICON_PATH}" && -n "${TARGET_ICON_NAME}" && -f "${TEMPLATE_ICON_PATH}" ]]; then - mkdir -p target-repo/icons - cp "${TEMPLATE_ICON_PATH}" "target-repo/icons/${TARGET_ICON_NAME}" - fi - cd target-repo - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add "${TARGET_XML_NAME}" - if [[ -n "${TARGET_ICON_NAME}" && -f "icons/${TARGET_ICON_NAME}" ]]; then - git add "icons/${TARGET_ICON_NAME}" - fi - git diff --quiet && git diff --staged --quiet || (git commit -m "chore: auto-sync AIO template assets" && git push) diff --git a/renovate.json b/renovate.json index bd9884b..310fbe4 100644 --- a/renovate.json +++ b/renovate.json @@ -6,7 +6,6 @@ ":dependencyDashboard" ], "pinDigests": true, - "pinTags": false, "labels": ["dependencies"], "packageRules": [ { diff --git a/rootfs/etc/cont-init.d/01-bootstrap-databases.sh b/rootfs/etc/cont-init.d/01-bootstrap-databases.sh index 7f76cd0..f70439a 100644 --- a/rootfs/etc/cont-init.d/01-bootstrap-databases.sh +++ b/rootfs/etc/cont-init.d/01-bootstrap-databases.sh @@ -3,10 +3,13 @@ set -euo pipefail echo "Checking internal database requirements..." +chmod 755 /appdata mkdir -p /appdata/postgres /appdata/redis /appdata/dkim /appdata/sl/upload /pgp /run/postgresql chown -R redis:redis /appdata/redis chown -R postgres:postgres /appdata/postgres /run/postgresql chown -R simplelogin:simplelogin /appdata/sl /pgp +chmod 755 /appdata/sl +chmod 700 /appdata/postgres /appdata/redis /pgp chmod 700 /pgp rm -rf /code/static/upload diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index d3397ab..3e7fd18 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -10,12 +10,19 @@ KEEP_SMOKE_ARTIFACTS="${KEEP_SMOKE_ARTIFACTS:-0}" TMP_APPDATA="$(mktemp -d /tmp/simplelogin-aio-appdata.XXXXXX)" TMP_PGP="$(mktemp -d /tmp/simplelogin-aio-pgp.XXXXXX)" +chmod 755 "$TMP_APPDATA" cleanup_needed=1 cleanup() { local exit_code=$? if [[ "$cleanup_needed" -eq 1 ]]; then docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true + if [[ -d "$TMP_APPDATA" ]]; then + docker run --rm -v "${TMP_APPDATA}:/cleanup" --entrypoint sh "$IMAGE_TAG" -c 'rm -rf /cleanup/* /cleanup/.[!.]* /cleanup/..?* 2>/dev/null || true' >/dev/null 2>&1 || true + fi + if [[ -d "$TMP_PGP" ]]; then + docker run --rm -v "${TMP_PGP}:/cleanup" --entrypoint sh "$IMAGE_TAG" -c 'rm -rf /cleanup/* /cleanup/.[!.]* /cleanup/..?* 2>/dev/null || true' >/dev/null 2>&1 || true + fi rm -rf "$TMP_APPDATA" "$TMP_PGP" elif [[ "$exit_code" -ne 0 ]]; then echo "Smoke test failed; preserving artifacts for debugging."