mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
Merge remote-tracking branch 'upstream/main' into agent/i8-release-article
This commit is contained in:
278 files changed
+1286
-171
No files matched your search
@@ -0,0 +1,3 @@
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- ubuntu-26.04
|
||||
@@ -43,7 +43,7 @@ concurrency:
|
||||
jobs:
|
||||
build:
|
||||
name: 💿 Image
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
@@ -78,7 +78,7 @@ concurrency:
|
||||
jobs:
|
||||
sync:
|
||||
name: 🪞 Mirror
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
@@ -10,7 +10,7 @@ permissions:
|
||||
jobs:
|
||||
lint:
|
||||
name: 🧹 Lint
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
INFINITO_LINT_RUNNER: host
|
||||
|
||||
@@ -343,7 +343,7 @@ jobs:
|
||||
- test-deploy-chunk-2
|
||||
- test-deploy-chunk-3
|
||||
if: ${{ always() && github.ref == 'refs/heads/main' && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
issues: write
|
||||
actions: read
|
||||
@@ -374,7 +374,7 @@ jobs:
|
||||
- lint
|
||||
- security-codeql
|
||||
if: ${{ always() }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: 🛑 Fail if any upstream job failed or was cancelled
|
||||
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
|
||||
|
||||
@@ -30,7 +30,7 @@ env:
|
||||
jobs:
|
||||
check-ci-images:
|
||||
name: 🔎 Check CI images
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
outputs:
|
||||
missing: ${{ steps.check.outputs.missing }}
|
||||
ci_tag: ${{ steps.citag.outputs.ci_tag }}
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
needs.build-ci-images-if-missing.result == 'success' ||
|
||||
needs.build-ci-images-if-missing.result == 'skipped'
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
@@ -163,7 +163,7 @@ env:
|
||||
jobs:
|
||||
discover:
|
||||
name: 🔍 Chunk ${{ inputs.index }}${{ inputs.marker }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 60
|
||||
outputs:
|
||||
apps: ${{ steps.discover.outputs.apps }}
|
||||
@@ -251,7 +251,7 @@ jobs:
|
||||
deploy:
|
||||
name: "${{ matrix.label }}"
|
||||
if: needs.discover.outputs.apps != '' && needs.discover.outputs.apps != '[]'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 355
|
||||
needs: discover
|
||||
|
||||
@@ -308,6 +308,11 @@ jobs:
|
||||
shell: bash
|
||||
run: ./scripts/github/runner/relocate_docker_dataroot.sh
|
||||
|
||||
- name: 🛡️ Tear down host AppArmor profiles (dev-host parity)
|
||||
if: ${{ !env.ACT }}
|
||||
shell: bash
|
||||
run: make security-apparmor-teardown
|
||||
|
||||
- name: 🔍 Resolve repository owner
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -23,7 +23,7 @@ permissions:
|
||||
jobs:
|
||||
dns:
|
||||
name: 🌐 DNS
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: 🧹 Free disk space
|
||||
@@ -41,6 +41,11 @@ jobs:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v7.0.0
|
||||
|
||||
- name: 🛡️ Tear down host AppArmor profiles (dev-host parity)
|
||||
if: ${{ !env.ACT }}
|
||||
shell: bash
|
||||
run: make security-apparmor-teardown
|
||||
|
||||
- name: 🔑 Login to GHCR
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
|
||||
@@ -10,7 +10,7 @@ permissions:
|
||||
jobs:
|
||||
test-install-make:
|
||||
name: 🛠️ Makefile
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
NIX_CONFIG: |
|
||||
|
||||
@@ -10,7 +10,7 @@ permissions:
|
||||
jobs:
|
||||
test-install-pkgmgr:
|
||||
name: 📦 PKGMGR
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
NIX_CONFIG: |
|
||||
|
||||
@@ -21,7 +21,7 @@ permissions:
|
||||
jobs:
|
||||
prepare:
|
||||
name: 🧑💻 Workspace Matrix
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
matrix: ${{ steps.matrix.outputs.matrix }}
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
|
||||
test-workspace:
|
||||
name: ${{ matrix.icon }} Workspace (${{ matrix.label }})
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 345
|
||||
needs: prepare
|
||||
|
||||
@@ -88,6 +88,11 @@ jobs:
|
||||
- name: ⬇️ Checkout repository
|
||||
uses: actions/checkout@v7.0.0
|
||||
|
||||
- name: 🛡️ Tear down host AppArmor profiles (dev-host parity)
|
||||
if: ${{ !env.ACT }}
|
||||
shell: bash
|
||||
run: make security-apparmor-teardown
|
||||
|
||||
- name: "📥 Load the rescue paths into job env"
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -10,7 +10,7 @@ permissions:
|
||||
jobs:
|
||||
test:
|
||||
name: 🧪 Test
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
INFINITO_TEST_RUNNER: host
|
||||
|
||||
@@ -84,7 +84,7 @@ permissions:
|
||||
jobs:
|
||||
validation:
|
||||
name: ✅ Validation
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: ⬇️ Checkout the calling ref for its tooling
|
||||
uses: actions/checkout@v7.0.0
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
wait-fork-prereq-run:
|
||||
name: ⏳ Wait for fork run
|
||||
if: ${{ inputs.fork_prereqs }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
env:
|
||||
WAIT_REQUIRED: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && !inputs.trusted_pr }}
|
||||
steps:
|
||||
@@ -194,7 +194,7 @@ jobs:
|
||||
name: 🚦 Gate Dependencies
|
||||
needs: wait-fork-prereq-run
|
||||
if: ${{ !cancelled() && inputs.fork_prereqs }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.0
|
||||
- name: ✅ Assert privileged prerequisites are ready
|
||||
|
||||
@@ -12,7 +12,7 @@ permissions:
|
||||
jobs:
|
||||
stale:
|
||||
name: 🏷️ Close inactive issues and pull requests
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
delete-stale-branches:
|
||||
name: 🌿 Delete inactive branches
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
cleanup-ghcr-ci-images:
|
||||
name: 📦 Delete old GHCR CI images
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
benchmark-collection-sources:
|
||||
name: ⚖️ Time Galaxy against the git fallback
|
||||
if: vars.CI_ENABLE_AUTO_UPDATES == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
env:
|
||||
HISTORY_PATH: build/collection-source-history.json
|
||||
steps:
|
||||
|
||||
@@ -22,7 +22,7 @@ concurrency:
|
||||
jobs:
|
||||
cleanup:
|
||||
name: 🧹 Delete old ci-* image versions
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
@@ -61,7 +61,7 @@ concurrency:
|
||||
jobs:
|
||||
sync:
|
||||
name: 🪞 Mirror
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
@@ -19,7 +19,7 @@ env:
|
||||
jobs:
|
||||
check-and-trigger:
|
||||
name: 🔎 Backfill highest missing release tag
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
security-events: write
|
||||
packages: read
|
||||
|
||||
@@ -16,7 +16,7 @@ permissions:
|
||||
jobs:
|
||||
fuzz-yaml-bootstrap:
|
||||
name: 🧬 Fuzz the bootstrap YAML reader
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
|
||||
@@ -26,7 +26,7 @@ permissions: read-all
|
||||
jobs:
|
||||
analysis:
|
||||
name: 🔍 Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
# `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.
|
||||
if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'
|
||||
permissions:
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
update-docker-image-versions:
|
||||
name: 🐳 Update Docker image versions
|
||||
if: vars.CI_ENABLE_AUTO_UPDATES == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v7.0.0
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
update-version-sources:
|
||||
name: 🔖 Update declared version sources
|
||||
if: vars.CI_ENABLE_AUTO_UPDATES == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v7.0.0
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
update-pip-pins:
|
||||
name: 🐍 Update pinned pip requirements
|
||||
if: vars.CI_ENABLE_AUTO_UPDATES == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v7.0.0
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
update-galaxy-pins:
|
||||
name: 🌌 Update pinned Ansible collections
|
||||
if: vars.CI_ENABLE_AUTO_UPDATES == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v7.0.0
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
update-repository-refs:
|
||||
name: 🌿 Update repository refs
|
||||
if: vars.CI_ENABLE_AUTO_UPDATES == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v7.0.0
|
||||
@@ -252,3 +252,45 @@ jobs:
|
||||
UPDATE_PR_BODY: "Automated update of semver-based git `ref:` values in `roles/*/meta/services.yml`. Upstream tags resolved via `git ls-remote --tags`."
|
||||
UPDATE_BASE_BRANCH: main
|
||||
run: bash scripts/github/update/open/role_prs.sh roles
|
||||
|
||||
update-runner-labels:
|
||||
name: 🏃 Update GitHub-hosted runner labels
|
||||
if: vars.CI_ENABLE_AUTO_UPDATES == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v7.0.0
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: 🔧 Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: 🔄 Update runner labels
|
||||
run: bash scripts/github/update/runner_labels.sh
|
||||
|
||||
- name: 🔎 Check for changes
|
||||
id: diff
|
||||
run: bash scripts/github/resolve/check_git_changes.sh .github/workflows
|
||||
|
||||
- name: ⚙️ Generate app token
|
||||
if: steps.diff.outputs.changed == 'true'
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
client-id: ${{ secrets.BOT_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: 📂 Open PR if runner labels changed
|
||||
if: steps.diff.outputs.changed == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
|
||||
UPDATE_BRANCH_PREFIX: update/runner-labels
|
||||
UPDATE_COMMIT_MESSAGE: "update(ci): raise GitHub-hosted runner labels"
|
||||
UPDATE_PR_TITLE: "update(ci): raise GitHub-hosted runner labels"
|
||||
UPDATE_PR_BODY: "Automated raise of every `runs-on: ubuntu-NN.NN` in `.github/workflows/` to the newest GA x64 Ubuntu image listed in https://github.com/actions/runner-images. Its CI run is the proof that the stack works on the new image."
|
||||
UPDATE_BASE_BRANCH: main
|
||||
run: bash scripts/github/update/open/pr.sh .github/workflows
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
cancel-branch-runs:
|
||||
name: 🚫 Cancel superseded branch runs
|
||||
if: ${{ github.event_name == 'push' && (github.ref_name != 'main' || (github.ref_name == 'main' && startsWith(github.event.head_commit.message, 'Release version '))) }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
cancel-pull-request-runs:
|
||||
name: 🚫 Cancel superseded pull request runs
|
||||
if: ${{ github.event_name == 'pull_request_target' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
|
||||
@@ -20,7 +20,7 @@ permissions:
|
||||
jobs:
|
||||
cancel-branch-runs:
|
||||
if: ${{ github.event.ref_type == 'branch' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
|
||||
@@ -40,7 +40,7 @@ permissions:
|
||||
jobs:
|
||||
cleanup:
|
||||
name: 🗑️ Delete private GHCR mirror packages
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
detect-pr-scope:
|
||||
name: 🔎 Detect PR scope
|
||||
if: ${{ github.event.pull_request.draft == false && (github.event_name == 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository) }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
outputs:
|
||||
scope: ${{ steps.resolve.outputs.scope }}
|
||||
run_ci_orchestrator: ${{ steps.resolve.outputs.run_ci_orchestrator }}
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
- detect-pr-scope
|
||||
- validation
|
||||
if: ${{ needs.detect-pr-scope.outputs.run_ci_orchestrator == 'false' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: ✅ Confirm lightweight PR scope
|
||||
shell: bash
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
- detect-pr-scope
|
||||
- validation
|
||||
if: ${{ needs.detect-pr-scope.outputs.run_ci_orchestrator == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
outputs:
|
||||
whitelist: ${{ steps.resolve.outputs.whitelist }}
|
||||
steps:
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
- detect-pr-scope
|
||||
- validation
|
||||
if: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && needs.detect-pr-scope.outputs.run_ci_orchestrator == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
outputs:
|
||||
checkout_ref: ${{ steps.out.outputs.checkout_ref }}
|
||||
image_tag: ${{ steps.out.outputs.image_tag }}
|
||||
|
||||
@@ -15,7 +15,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
cancel-pr-runs:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
close-when-disabled:
|
||||
name: 🚫 Close Dependabot PR while gated
|
||||
if: github.event.pull_request.user.login == 'dependabot[bot]' && vars.CI_ENABLE_AUTO_UPDATES != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: 📪 Close PR
|
||||
env:
|
||||
|
||||
@@ -21,7 +21,7 @@ permissions:
|
||||
jobs:
|
||||
run-policy:
|
||||
name: 🧭 Decide CI policy
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
name: ⏭️ Skip CI
|
||||
needs: run-policy
|
||||
if: needs.run-policy.outputs.should_run != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: ⏭️ Explain skip
|
||||
shell: bash
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
if: needs.run-policy.outputs.should_run == 'true'
|
||||
needs: run-policy
|
||||
name: 🏷️ Detect release tag
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
outputs:
|
||||
is_version: ${{ steps.pick.outputs.is_version }}
|
||||
version_tag: ${{ steps.pick.outputs.version_tag }}
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
if: needs.run-policy.outputs.should_run == 'true'
|
||||
name: ⭐ Resolve new-role priority
|
||||
needs: run-policy
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
outputs:
|
||||
priority: ${{ steps.resolve.outputs.priority }}
|
||||
steps:
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
- run-policy
|
||||
- detect-release
|
||||
- ci-orchestrator
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
outputs:
|
||||
allowed: ${{ steps.guard.outputs.allowed }}
|
||||
steps:
|
||||
|
||||
@@ -1,5 +1,59 @@
|
||||
# Changelog
|
||||
|
||||
## [16.2.1] - 2026-10-20
|
||||
|
||||
Fixed:
|
||||
|
||||
- Cache: the package cache frontend starts again; 16.2.0 broke its nginx
|
||||
- Snipe-IT: SSO login works again on v8.8.1 behind oauth2-proxy
|
||||
- Snipe-IT: the SSO header is only trusted with the proxy's shared secret
|
||||
- Wazuh: seeded bcrypt salts no longer leak a fast password digest
|
||||
|
||||
Version jumps (net since 16.2.0):
|
||||
|
||||
- *web-app-socialhome*: 2026.10.17 to 2026.10.20
|
||||
|
||||
Upgrade notes:
|
||||
|
||||
- Snipe-IT: re-run *infinito administration inventory provision* once
|
||||
|
||||
Contributors:
|
||||
|
||||
- [Kevin Veen-Birkenbach](https://veen.world): cache, Snipe-IT SSO, bcrypt
|
||||
|
||||
## [16.2.0] - 2026-10-20
|
||||
|
||||
Added:
|
||||
|
||||
- Compose: *make compose-network-recreate* rebuilds the stack network safely
|
||||
- CI: the updater keeps the GitHub-hosted runner labels current
|
||||
|
||||
Fixed:
|
||||
|
||||
- Backup: hard links, ACLs and xattrs survive every backup and recover hop
|
||||
- NFS client: clients on Linux 6.19+ no longer fail writes on ganesha < 9.11
|
||||
- AppArmor: the teardown keeps docker-default, so containers keep starting
|
||||
- Wazuh: hosts can be put into WireGuard meshes like every other role
|
||||
- CI: GitHub-hosted runners are pinned to ubuntu-24.04
|
||||
|
||||
Version jumps (net since 16.1.0):
|
||||
|
||||
- *svc-db-rabbitmq*: 4.3-management-alpine to 4.4-management-alpine
|
||||
- *web-app-n8n*: 2.44.4 to 2.45.0
|
||||
- *web-app-pixelfed*: 20261019 to 20261020
|
||||
- *web-app-snipe-it*: v8.8.0-alpine to v8.8.1-alpine
|
||||
- *web-app-taiga*: RabbitMQ 4.3-management-alpine to 4.4-management-alpine
|
||||
- *eslint* (dev): ^10.12.0 to ^10.13.0
|
||||
|
||||
Upgrade notes:
|
||||
|
||||
- Backup: a target without xattr support now fails instead of losing them
|
||||
- Dev: worktree hosts apply the IP pool with *make compose-network-recreate*
|
||||
|
||||
Contributors:
|
||||
|
||||
- [Kevin Veen-Birkenbach](https://veen.world): backups, NFS, AppArmor, CI
|
||||
|
||||
## [16.1.0] - 2026-10-19
|
||||
|
||||
Added:
|
||||
|
||||
@@ -227,6 +227,11 @@ compose-inner-run:
|
||||
compose-inventory-refresh:
|
||||
@bash scripts/tests/deploy/local/reset/inventory.sh
|
||||
|
||||
.PHONY: compose-network-recreate
|
||||
# Recreate the stack's default network after an IPAM change, keeping volumes and reattaching foreign stacks.
|
||||
compose-network-recreate: install
|
||||
@"$${PYTHON}" -m cli.administration.deploy.development recreate
|
||||
|
||||
.PHONY: compose-playwright
|
||||
# Rerun a role-local Playwright spec against the live running stack (no redeploy).
|
||||
# Usage: make compose-playwright role=<role> [pw="--grep <pattern>"] [keep=true]
|
||||
|
||||
@@ -27,6 +27,7 @@ alias i8co2='m console'
|
||||
alias i8coape='m compose-app-exec'
|
||||
alias i8codo='m compose-down'
|
||||
alias i8cone='m compose-entity-purge'
|
||||
alias i8conere='m compose-network-recreate'
|
||||
alias i8coni='m compose-inner-run'
|
||||
alias i8conire='m compose-inventory-refresh'
|
||||
alias i8copa='m compose-playwright'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[1]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -53,6 +53,7 @@ def _build_parser(base_pkg: str) -> argparse.ArgumentParser:
|
||||
"run",
|
||||
"exec",
|
||||
"logs",
|
||||
"recreate",
|
||||
]
|
||||
|
||||
for _, mod in _iter_command_modules(base_pkg, command_names):
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
from .common import make_compose
|
||||
from .up import handler as up_handler
|
||||
|
||||
|
||||
def foreign_endpoints(rows: str, project: str) -> list[str]:
|
||||
"""Return the running containers of other projects on the network.
|
||||
|
||||
Args:
|
||||
rows: ``docker ps`` lines of ``<name>\\t<compose project>``.
|
||||
project: compose project that owns the network.
|
||||
"""
|
||||
names = []
|
||||
for row in rows.splitlines():
|
||||
name, _, owner = row.partition("\t")
|
||||
if name and owner != project:
|
||||
names.append(name)
|
||||
return names
|
||||
|
||||
|
||||
def _docker(*args: str, capture: bool = False) -> str:
|
||||
result = subprocess.run(
|
||||
["docker", *args], check=True, capture_output=capture, text=True
|
||||
)
|
||||
return result.stdout if capture else ""
|
||||
|
||||
|
||||
def add_parser(sub: argparse._SubParsersAction) -> None:
|
||||
p = sub.add_parser(
|
||||
"recreate",
|
||||
help="Recreate the default network, keeping volumes and foreign stacks.",
|
||||
)
|
||||
p.set_defaults(_handler=handler)
|
||||
|
||||
|
||||
def handler(args: argparse.Namespace) -> int:
|
||||
compose = make_compose()
|
||||
rendered = compose.run(["config", "--format", "json"], capture=True).stdout
|
||||
config = json.loads(rendered)
|
||||
project = config["name"]
|
||||
network = config["networks"]["default"]["name"]
|
||||
|
||||
rows = _docker(
|
||||
"ps",
|
||||
"--filter",
|
||||
f"network={network}",
|
||||
"--format",
|
||||
'{{.Names}}\t{{.Label "com.docker.compose.project"}}',
|
||||
capture=True,
|
||||
)
|
||||
detached = foreign_endpoints(rows, project)
|
||||
for name in detached:
|
||||
print(f">>> Detaching {name} from {network}")
|
||||
_docker("network", "disconnect", network, name)
|
||||
|
||||
rc = 1
|
||||
try:
|
||||
compose.run(["down", "--remove-orphans"])
|
||||
rc = up_handler(argparse.Namespace(skip_entry_init=False, when_down=False))
|
||||
finally:
|
||||
if rc != 0 and detached:
|
||||
print(f">>> Still detached from {network}: {' '.join(detached)}")
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
||||
for name in detached:
|
||||
print(f">>> Attaching {name} to {network}")
|
||||
_docker("network", "connect", network, name)
|
||||
return 0
|
||||
@@ -3,3 +3,5 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[4]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -78,7 +78,7 @@ expanding `volume` per docker volume.
|
||||
|
||||
## Safety
|
||||
|
||||
Each recover mirrors a snapshot with `rsync -a --delete` and first runs the
|
||||
Each recover mirrors a snapshot with `rsync -aHAX --delete` and first runs the
|
||||
role's backup unit as a rollback point. `--no-safety-backup` skips that
|
||||
(only when the target holds nothing worth saving: fresh host / empty /
|
||||
disposable). `--preview` prints the exact commands without running them.
|
||||
|
||||
@@ -72,7 +72,7 @@ def plan(root: Path) -> tuple[list[tuple[str, str]], list[str]]:
|
||||
|
||||
def _pull_cmd(source: str) -> list[str]:
|
||||
remote = source if ":" in source else f"{source}:{paths.BACKUP_ROOT}"
|
||||
return ["rsync", "-a", f"{remote}/", f"{PULL_STAGE}/"]
|
||||
return ["rsync", "-aHAX", f"{remote}/", f"{PULL_STAGE}/"]
|
||||
|
||||
|
||||
def _is_device(source: str) -> bool:
|
||||
|
||||
@@ -47,7 +47,7 @@ def commands(
|
||||
return (
|
||||
[
|
||||
["ssh", host, shlex.join(remote)],
|
||||
["rsync", "-a", f"{host}:{REMOTE_ROOT}/", f"{LOCAL_ROOT}/"],
|
||||
["rsync", "-aHAX", f"{host}:{REMOTE_ROOT}/", f"{LOCAL_ROOT}/"],
|
||||
],
|
||||
LOCAL_ROOT,
|
||||
)
|
||||
|
||||
@@ -58,4 +58,4 @@ flags:
|
||||
skip the pre-recover safety backup of the current target
|
||||
(only when it holds nothing worth saving: fresh host / empty / disposable)
|
||||
|
||||
mechanism: "rsync -a --delete, preceded by a pre-recover safety backup by default"
|
||||
mechanism: "rsync -aHAX --delete, preceded by a pre-recover safety backup by default"
|
||||
@@ -10,3 +10,5 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[4]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -4,6 +4,15 @@ from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[3]
|
||||
|
||||
__all__ = [
|
||||
"PROJECT_ROOT",
|
||||
"build_mappings",
|
||||
"find_roles",
|
||||
"main",
|
||||
"output_graph",
|
||||
"process_role",
|
||||
]
|
||||
|
||||
from . import __main__ as _main # noqa: E402
|
||||
|
||||
build_mappings = _main.build_mappings
|
||||
|
||||
@@ -3,3 +3,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[4]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -3,3 +3,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[4]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -3,3 +3,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[4]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -3,3 +3,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[4]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -3,3 +3,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[4]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -0,0 +1,7 @@
|
||||
"""CLI entry point package for the GitHub-hosted runner label update."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[4]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from utils.update.fetch import document
|
||||
from utils.update.runner import IMAGES_README, bump_workflows, newest_ubuntu_label
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Raise every workflow's runs-on Ubuntu label to the newest GA "
|
||||
"GitHub-hosted x64 image."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--repo-root",
|
||||
type=Path,
|
||||
default=PROJECT_ROOT,
|
||||
help="Repository root whose .github/workflows are updated",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
label = newest_ubuntu_label(document(IMAGES_README))
|
||||
if label is None:
|
||||
print(f"No GA x64 Ubuntu label found in {IMAGES_README}.")
|
||||
return 1
|
||||
|
||||
repo_root = args.repo_root.resolve()
|
||||
changed = bump_workflows(repo_root / ".github" / "workflows", label)
|
||||
if not changed:
|
||||
print(f"Every workflow already runs on {label} or newer.")
|
||||
return 0
|
||||
|
||||
print(f"Raised runs-on to {label} in:")
|
||||
for path in changed:
|
||||
print(f"- {path.relative_to(repo_root)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -3,3 +3,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[4]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -1,3 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[2]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -1,3 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[3]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
Vendored
+2
@@ -9,3 +9,5 @@ resulting `.env` at the repo root. Implementation lives in
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[3]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__all__: list[str] = []
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[5]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -1,3 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[6]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -1,3 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[6]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -1,3 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[6]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -1,3 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[5]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -1,3 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[5]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__all__: list[str] = []
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[5]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
@@ -5,6 +5,8 @@ from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[3]
|
||||
|
||||
__all__ = ["PROJECT_ROOT", "detect_runtime"]
|
||||
|
||||
|
||||
def detect_runtime() -> str:
|
||||
"""
|
||||
|
||||
@@ -49,11 +49,11 @@ Some CI failures (most notably OOM-kills such as [#162](https://github.com/infin
|
||||
|
||||
### GitHub-Hosted Runner Specs
|
||||
|
||||
All [workflows](../../../../.github/workflows/) target `runs-on: ubuntu-latest`, which maps to the GitHub-hosted standard Linux runner. The effective ceiling depends on the repository's visibility and plan:
|
||||
All [workflows](../../../../.github/workflows/) target `runs-on: ubuntu-26.04`, the GitHub-hosted standard Linux runner pinned to one Ubuntu release. GitHub still refreshes that image weekly. The `🏃 Update GitHub-hosted runner labels` job of [cron-update.yml](../../../../.github/workflows/cron-update.yml) opens a pull request when a newer GA x64 Ubuntu image is listed in [actions/runner-images](https://github.com/actions/runner-images), and `tests/lint/repository/test_workflow_runner_pinned.py` rejects `*-latest` labels. The effective ceiling depends on the repository's visibility and plan:
|
||||
|
||||
| Repository / plan | vCPU | RAM | SSD |
|
||||
|------------------------------------------|------|--------|-------|
|
||||
| Public repo (this repo, `ubuntu-latest`) | 4 | 16 GB | 14 GB |
|
||||
| Public repo (this repo, `ubuntu-26.04`) | 4 | 16 GB | 14 GB |
|
||||
| Private repo on Free / Pro / Team | 2 | 7 GB | 14 GB |
|
||||
| Private repo on Enterprise | 4 | 16 GB | 14 GB |
|
||||
|
||||
@@ -70,7 +70,7 @@ For the authoritative specs, see the upstream docs for [standard hosted runners]
|
||||
Pick the profile that matches the runner you want to mimic:
|
||||
|
||||
```sh
|
||||
# Public repo runner (ubuntu-latest, 4 vCPU / 16 GB): the default CI target
|
||||
# Public repo runner (ubuntu-26.04, 4 vCPU / 16 GB): the default CI target
|
||||
INFINITO_MEM_LIMIT=16g INFINITO_MEMSWAP_LIMIT=16g INFINITO_CPUS=4 make <target>
|
||||
|
||||
# Private repo runner on Free/Pro/Team (2 vCPU / 7 GB)
|
||||
|
||||
Generated
+4
-4
@@ -10,7 +10,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.64.1",
|
||||
"eslint": "^10.12.0",
|
||||
"eslint": "^10.13.0",
|
||||
"eslint-plugin-playwright": "^2.12.1",
|
||||
"globals": "^17.13.0",
|
||||
"stylelint": "^17.16.0"
|
||||
@@ -848,9 +848,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "10.12.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.12.0.tgz",
|
||||
"integrity": "sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==",
|
||||
"version": "10.13.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.13.0.tgz",
|
||||
"integrity": "sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.64.1",
|
||||
"eslint": "^10.12.0",
|
||||
"eslint": "^10.13.0",
|
||||
"eslint-plugin-playwright": "^2.12.1",
|
||||
"globals": "^17.13.0",
|
||||
"stylelint": "^17.16.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Maintainer: Kevin Veen-Birkenbach <info@veen.world>
|
||||
|
||||
pkgname=infinito-nexus
|
||||
pkgver=16.1.0
|
||||
pkgver=16.2.1
|
||||
pkgrel=1
|
||||
pkgdesc="Meta package for Infinito.Nexus host dependencies (dev + CI)."
|
||||
arch=('any')
|
||||
|
||||
@@ -1,3 +1,61 @@
|
||||
infinito-nexus (16.2.1-1) unstable; urgency=medium
|
||||
|
||||
Fixed:
|
||||
|
||||
- Cache: the package cache frontend starts again; 16.2.0 broke its nginx
|
||||
- Snipe-IT: SSO login works again on v8.8.1 behind oauth2-proxy
|
||||
- Snipe-IT: the SSO header is only trusted with the proxy's shared secret
|
||||
- Wazuh: seeded bcrypt salts no longer leak a fast password digest
|
||||
|
||||
Version jumps (net since 16.2.0):
|
||||
|
||||
- *web-app-socialhome*: 2026.10.17 to 2026.10.20
|
||||
|
||||
Upgrade notes:
|
||||
|
||||
- Snipe-IT: re-run *infinito administration inventory provision* once
|
||||
|
||||
Contributors:
|
||||
|
||||
- [Kevin Veen-Birkenbach](https://veen.world): cache, Snipe-IT SSO, bcrypt
|
||||
|
||||
-- Kevin Veen-Birkenbach <kevin@veen.world> Tue, 20 Oct 2026 22:35:05 +0200
|
||||
|
||||
infinito-nexus (16.2.0-1) unstable; urgency=medium
|
||||
|
||||
Added:
|
||||
|
||||
- Compose: *make compose-network-recreate* rebuilds the stack network safely
|
||||
- CI: the updater keeps the GitHub-hosted runner labels current
|
||||
|
||||
Fixed:
|
||||
|
||||
- Backup: hard links, ACLs and xattrs survive every backup and recover hop
|
||||
- NFS client: clients on Linux 6.19+ no longer fail writes on ganesha < 9.11
|
||||
- AppArmor: the teardown keeps docker-default, so containers keep starting
|
||||
- Wazuh: hosts can be put into WireGuard meshes like every other role
|
||||
- CI: GitHub-hosted runners are pinned to ubuntu-24.04
|
||||
|
||||
Version jumps (net since 16.1.0):
|
||||
|
||||
- *svc-db-rabbitmq*: 4.3-management-alpine to 4.4-management-alpine
|
||||
- *web-app-n8n*: 2.44.4 to 2.45.0
|
||||
- *web-app-pixelfed*: 20261019 to 20261020
|
||||
- *web-app-snipe-it*: v8.8.0-alpine to v8.8.1-alpine
|
||||
- *web-app-taiga*: RabbitMQ 4.3-management-alpine to 4.4-management-alpine
|
||||
- *eslint* (dev): ^10.12.0 to ^10.13.0
|
||||
|
||||
Upgrade notes:
|
||||
|
||||
- Backup: a target without xattr support now fails instead of losing them
|
||||
- Dev: worktree hosts apply the IP pool with *make compose-network-recreate*
|
||||
|
||||
Contributors:
|
||||
|
||||
- [Kevin Veen-Birkenbach](https://veen.world): backups, NFS, AppArmor, CI
|
||||
|
||||
-- Kevin Veen-Birkenbach <kevin@veen.world> Tue, 20 Oct 2026 21:11:50 +0200
|
||||
|
||||
infinito-nexus (16.1.0-1) unstable; urgency=medium
|
||||
|
||||
Added:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: infinito-nexus
|
||||
Version: 16.1.0
|
||||
Version: 16.2.1
|
||||
Release: 1%{?dist}
|
||||
Summary: Meta package for Infinito.Nexus host dependencies
|
||||
|
||||
@@ -58,6 +58,58 @@ install -d %{buildroot}%{_docdir}/%{name}
|
||||
%doc %{_docdir}/%{name}/DEPENDENCIES
|
||||
|
||||
%changelog
|
||||
* Tue Oct 20 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 16.2.1-1
|
||||
- Fixed:
|
||||
|
||||
- - Cache: the package cache frontend starts again; 16.2.0 broke its nginx
|
||||
- - Snipe-IT: SSO login works again on v8.8.1 behind oauth2-proxy
|
||||
- - Snipe-IT: the SSO header is only trusted with the proxy's shared secret
|
||||
- - Wazuh: seeded bcrypt salts no longer leak a fast password digest
|
||||
|
||||
- Version jumps (net since 16.2.0):
|
||||
|
||||
- - *web-app-socialhome*: 2026.10.17 to 2026.10.20
|
||||
|
||||
- Upgrade notes:
|
||||
|
||||
- - Snipe-IT: re-run *infinito administration inventory provision* once
|
||||
|
||||
- Contributors:
|
||||
|
||||
- - [Kevin Veen-Birkenbach](https://veen.world): cache, Snipe-IT SSO, bcrypt
|
||||
|
||||
* Tue Oct 20 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 16.2.0-1
|
||||
- Added:
|
||||
|
||||
- - Compose: *make compose-network-recreate* rebuilds the stack network safely
|
||||
- - CI: the updater keeps the GitHub-hosted runner labels current
|
||||
|
||||
- Fixed:
|
||||
|
||||
- - Backup: hard links, ACLs and xattrs survive every backup and recover hop
|
||||
- - NFS client: clients on Linux 6.19+ no longer fail writes on ganesha < 9.11
|
||||
- - AppArmor: the teardown keeps docker-default, so containers keep starting
|
||||
- - Wazuh: hosts can be put into WireGuard meshes like every other role
|
||||
- - CI: GitHub-hosted runners are pinned to ubuntu-24.04
|
||||
|
||||
- Version jumps (net since 16.1.0):
|
||||
|
||||
- - *svc-db-rabbitmq*: 4.3-management-alpine to 4.4-management-alpine
|
||||
- - *web-app-n8n*: 2.44.4 to 2.45.0
|
||||
- - *web-app-pixelfed*: 20261019 to 20261020
|
||||
- - *web-app-snipe-it*: v8.8.0-alpine to v8.8.1-alpine
|
||||
- - *web-app-taiga*: RabbitMQ 4.3-management-alpine to 4.4-management-alpine
|
||||
- - *eslint* (dev): ^10.12.0 to ^10.13.0
|
||||
|
||||
- Upgrade notes:
|
||||
|
||||
- - Backup: a target without xattr support now fails instead of losing them
|
||||
- - Dev: worktree hosts apply the IP pool with *make compose-network-recreate*
|
||||
|
||||
- Contributors:
|
||||
|
||||
- - [Kevin Veen-Birkenbach](https://veen.world): backups, NFS, AppArmor, CI
|
||||
|
||||
* Mon Oct 19 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 16.1.0-1
|
||||
- Added:
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[2]
|
||||
@@ -23,18 +23,17 @@ _STD_TO_BCRYPT_B64 = bytes.maketrans(
|
||||
)
|
||||
|
||||
|
||||
def _seeded_salt(seed: str, secret: bytes) -> bytes:
|
||||
"""A fixed bcrypt salt derived from the seed and the secret.
|
||||
def _seeded_salt(seed: str) -> bytes:
|
||||
"""A fixed bcrypt salt derived from the seed alone.
|
||||
|
||||
Args:
|
||||
seed: caller-chosen, non-empty string that separates otherwise equal secrets.
|
||||
secret: the UTF-8 encoded secret being hashed.
|
||||
|
||||
Returns:
|
||||
``$2b$12$`` plus 22 bcrypt-base64 characters encoding the first 16
|
||||
bytes of sha256(seed + secret).
|
||||
bytes of sha256(seed).
|
||||
"""
|
||||
digest = hashlib.sha256(seed.encode("utf-8") + secret).digest()[:16]
|
||||
digest = hashlib.sha256(seed.encode("utf-8")).digest()[:16]
|
||||
encoded = base64.b64encode(digest)[:22].translate(_STD_TO_BCRYPT_B64)
|
||||
return b"$2b$12$" + encoded
|
||||
|
||||
@@ -61,7 +60,7 @@ class FilterModule:
|
||||
if salt_seed is None:
|
||||
salt = bcrypt.gensalt()
|
||||
elif isinstance(salt_seed, str) and salt_seed:
|
||||
salt = _seeded_salt(salt_seed, secret)
|
||||
salt = _seeded_salt(salt_seed)
|
||||
else:
|
||||
raise ValueError(
|
||||
"bcrypt_hash: salt_seed must be a non-empty string when given"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[2]
|
||||
|
||||
__all__ = ["PROJECT_ROOT"]
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "infinito-nexus"
|
||||
version = "16.1.0"
|
||||
version = "16.2.1"
|
||||
description = "Infinito.Nexus"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
@@ -9,6 +9,7 @@ The transfer is hard-link deduplicated against the previous snapshot, so each pl
|
||||
|
||||
This role asserts that the mount, source, and target configuration values are present, deploys the rsync-driver script, and installs a systemd mount unit plus a oneshot service.
|
||||
The oneshot service is triggered automatically when the configured mount path appears, so the only operator action is to physically plug the device in.
|
||||
The filesystem mounted at the configured mount path must store owners, POSIX ACLs and extended attributes (for example ext4 or xfs). The backup runs `rsync -aHAXP`, which fails on FAT or exFAT.
|
||||
|
||||
## Cosmos
|
||||
|
||||
@@ -97,7 +98,7 @@ Run `files/python/recover.py` on the host with the backup device attached:
|
||||
recover.py /dev/sdX1 /mnt/usb-recover /var/lib/infinito/backup [--snapshot <timestamp>]
|
||||
```
|
||||
|
||||
The script opens the LUKS device (interactive passphrase prompt; `--passphrase-stdin` for automation), mounts it, picks the newest device snapshot across every machine hash (a fresh host after total loss has a new machine id), mirrors it into the target (`rsync -a --delete`) and unmounts/closes the device again. No pre-recover service backup runs: the device itself is the backup this role maintains. Afterwards restore individual applications from the backup root with the matching role's `recover.py` (`svc-bkp-volume-2-local` for docker volumes, `svc-bkp-nfs-2-local` for NFS exports).
|
||||
The script opens the LUKS device (interactive passphrase prompt; `--passphrase-stdin` for automation), mounts it, picks the newest device snapshot across every machine hash (a fresh host after total loss has a new machine id), mirrors it into the target (`rsync -aHAX --delete`) and unmounts/closes the device again. No pre-recover service backup runs: the device itself is the backup this role maintains. Afterwards restore individual applications from the backup root with the matching role's `recover.py` (`svc-bkp-volume-2-local` for docker volumes, `svc-bkp-nfs-2-local` for NFS exports).
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Opens the LUKS device (interactive passphrase prompt, or stdin with
|
||||
``--passphrase-stdin``), mounts it, picks the newest device snapshot
|
||||
(any machine hash: a fresh host after total loss has a new
|
||||
/etc/machine-id) and mirrors it into the target
|
||||
(``rsync -a --delete``), then unmounts and closes the device again.
|
||||
(``rsync -aHAX --delete``), then unmounts and closes the device again.
|
||||
No pre-recover service backup runs: the device itself is the backup
|
||||
this role maintains.
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ def main():
|
||||
try:
|
||||
rsync_command = [
|
||||
"rsync",
|
||||
"-aP",
|
||||
"-aHAXP",
|
||||
"--numeric-ids",
|
||||
"--delete",
|
||||
"--delete-excluded",
|
||||
|
||||
@@ -44,7 +44,7 @@ flowchart TD
|
||||
UNIT["svc-bkp-nfs-2-local.<version>.<domain>.service"] --> LOCK["ExecStartPre: sys-lock against the manipulation group"]
|
||||
LOCK --> SCRIPT["ExecStart: script.sh <export_base> <backups_dir><br>backup-nfs-to-local <state>/backup"]
|
||||
SCRIPT -->|export_base missing| ALARM["ERROR exit 1<br>OnFailure alarm"]
|
||||
SCRIPT --> RSYNC["rsync -a --delete<br>--exclude /<state>/backup (no backups-in-backups)<br>--link-dest previous generation (hard links)"]
|
||||
SCRIPT --> RSYNC["rsync -aHAX --delete<br>--exclude /<state>/backup (no backups-in-backups)<br>--link-dest previous generation (hard links)"]
|
||||
RSYNC -->|exit 24: live export churn| WARN["WARN, snapshot kept"]
|
||||
RSYNC -->|any other failure| CLEAN["generation removed, exit != 0"]
|
||||
RSYNC --> TREE["<backups_dir>/<sha256(machine-id)>/<br>backup-nfs-to-local/<YYYYmmddHHMMSS>/files/..."]
|
||||
@@ -105,7 +105,7 @@ recover.py <backups>/<machine-hash>/backup-nfs-to-local/<generation>/files/<stat
|
||||
```
|
||||
|
||||
1. Stop every stack consuming the subtree: `docker stack rm <stack>`.
|
||||
2. Run the script; it first starts the role's deployed backup unit (a fresh differential `backup-nfs-to-local` generation of the live export), then mirrors the snapshot into the target (`rsync -a --delete`, with the shared `<state>/backup` root protected from deletion and never copied in). `--no-safety-backup` skips the unit run when the target holds nothing worth saving.
|
||||
2. Run the script; it first starts the role's deployed backup unit (a fresh differential `backup-nfs-to-local` generation of the live export), then mirrors the snapshot into the target (`rsync -aHAX --delete`, with the shared `<state>/backup` root protected from deletion and never copied in). `--no-safety-backup` skips the unit run when the target holds nothing worth saving.
|
||||
3. Redeploy the stack; the NFS clients re-mount and pick up the restored state.
|
||||
|
||||
The target subtree must already exist; the script refuses to create export subtrees implicitly.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
Runs the role's deployed backup unit first (the usual differential
|
||||
``backup-nfs-to-local`` generation of the live export), then mirrors the
|
||||
snapshot into the target (``rsync -a --delete``). Run on the host
|
||||
snapshot into the target (``rsync -aHAX --delete``). Run on the host
|
||||
serving the export with every consuming stack stopped; NFS clients
|
||||
re-mount on redeploy.
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ trap cleanup_failed_generation ERR
|
||||
|
||||
mkdir -p "${DEST_DIR}"
|
||||
|
||||
rsync_args=(-a --numeric-ids --delete)
|
||||
rsync_args=(-aHAX --numeric-ids --delete)
|
||||
if [[ -n "${EXCLUDE_REL}" ]]; then
|
||||
rsync_args+=(--exclude "/${EXCLUDE_REL#/}")
|
||||
fi
|
||||
|
||||
@@ -120,7 +120,7 @@ def pull_backups(hostname: str, backups_dir: str) -> None:
|
||||
Path(local_backup_destination_path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
rsync_command = (
|
||||
f"rsync -a --partial --info=stats2 "
|
||||
f"rsync -aHAX --partial --info=stats2 "
|
||||
f"--numeric-ids --delete --delete-excluded --timeout=300 "
|
||||
f'-e "ssh {SSH_OPTS}" '
|
||||
f'--rsync-path="sudo rsync" '
|
||||
|
||||
@@ -78,7 +78,7 @@ flowchart TD
|
||||
TREE --> RECOVER["files/python/recover.py volume<br>resolves Mountpoint + Options"]
|
||||
RECOVER -->|"Options set"| PROBE{"mountpoint -q<br>(ssh for --docker-host)"}
|
||||
PROBE -->|unmounted| REFUSE["SystemExit: a restore would land<br>on the node disk and be shadowed<br>on the next mount"]
|
||||
PROBE -->|mounted| RSYNC["rsync -a --delete through the<br>mounted export onto the server"]
|
||||
PROBE -->|mounted| RSYNC["rsync -aHAX --delete through the<br>mounted export onto the server"]
|
||||
DRILL["DR drill [9/9]<br>mounts the export at the volume<br>mountpoint with the volume's own opts"] --> RECOVER
|
||||
RSYNC --> VERIFY["marker asserted on the<br>NFS server's disk"]
|
||||
```
|
||||
@@ -304,7 +304,7 @@ recover.py <backups>/<machine-hash>/backup-docker-to-local/<generation>/<volume>
|
||||
```
|
||||
|
||||
1. Stop the consuming project (`docker compose down` / `docker stack rm <stack>`).
|
||||
2. Run the script; it first starts the role's deployed backup unit (a fresh differential baudolo generation of every volume and database), resolves the volume's mountpoint and mirrors the snapshot into it (`rsync -a --delete`). `--no-safety-backup` skips the unit run when the target holds nothing worth saving.
|
||||
2. Run the script; it first starts the role's deployed backup unit (a fresh differential baudolo generation of every volume and database), resolves the volume's mountpoint and mirrors the snapshot into it (`rsync -aHAX --delete`). `--no-safety-backup` skips the unit run when the target holds nothing worth saving.
|
||||
3. Restore the databases with the `--databases` mode above **while the project is still down**, then start it again; on swarm, NFS-backed volumes are restored via `svc-bkp-nfs-2-local`'s `recover.py` instead (see below).
|
||||
|
||||
## Credits
|
||||
|
||||
@@ -9,7 +9,7 @@ Both modes run the role's deployed backup unit first, unless
|
||||
``--no-safety-backup`` says the target holds nothing worth saving.
|
||||
|
||||
Volume mode mirrors the snapshot into the volume's mountpoint
|
||||
(``rsync -a --delete``); stop the consuming project first. With
|
||||
(``rsync -aHAX --delete``); stop the consuming project first. With
|
||||
``--docker-host ssh://user@host`` it works on a remote host.
|
||||
|
||||
Database mode (``--databases``) replays every dump with
|
||||
|
||||
@@ -12,7 +12,7 @@ rabbitmq:
|
||||
bond: 1
|
||||
image: rabbitmq
|
||||
name: rabbitmq
|
||||
version: "4.3-management-alpine"
|
||||
version: "4.4-management-alpine"
|
||||
min_storage: 289MB
|
||||
backup:
|
||||
database_routine: true
|
||||
|
||||
@@ -30,7 +30,7 @@ buildkit:
|
||||
enabled: false
|
||||
shared: false
|
||||
image: moby/buildkit
|
||||
version: "v0.34.0"
|
||||
version: "v0.34.1"
|
||||
# nocheck: dynamic-flag
|
||||
act-runner:
|
||||
enabled: false
|
||||
|
||||
@@ -50,6 +50,12 @@ Solid `1:1` edges are fixed relationships; dashed `0..1` edges are conditional (
|
||||
Generations written to the share before the bind stay under the
|
||||
export's `backup/` directory, hidden from the nodes; remove them there
|
||||
by hand.
|
||||
- **Directory delegations off against old ganesha:** When the server
|
||||
runs nfs-ganesha older than 9.11, every client and the controller set
|
||||
the kernel's `nfsv4` parameter `directory_delegations` to `N`, at
|
||||
runtime and in `/etc/modprobe.d`. The parameter is kernel-wide, so on
|
||||
a lab whose nodes share one kernel it covers every NFS mount of that
|
||||
host until the module reloads.
|
||||
|
||||
## Quick Setup
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
param=/sys/module/nfsv4/parameters/directory_delegations
|
||||
|
||||
[ -e "${param}" ] || exit 0
|
||||
[ "$(cat "${param}")" = N ] && exit 0
|
||||
echo N >"${param}"
|
||||
echo CHANGED
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
- name: "🔍 Read the NFS server's installed packages"
|
||||
ansible.builtin.package_facts:
|
||||
delegate_to: "{{ groups['svc-storage-nfs-server'] | first }}"
|
||||
delegate_facts: true
|
||||
run_once: true
|
||||
|
||||
- name: "🔧 Stop requesting directory delegations from a ganesha that rejects them"
|
||||
when: >-
|
||||
(hostvars[groups['svc-storage-nfs-server'] | first].ansible_facts.packages['nfs-ganesha'] | first).version
|
||||
is version('9.11', '<')
|
||||
block:
|
||||
- name: "🔧 Keep directory delegations off whenever nfsv4 loads"
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/modprobe.d/infinito-nfs-directory-delegations.conf
|
||||
line: "options nfsv4 directory_delegations=N"
|
||||
create: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: "🔧 Turn directory delegations off in the running kernel"
|
||||
ansible.builtin.script:
|
||||
cmd: "{{ lookup('path_absolute', 'roles/svc-storage-nfs-client/files/shell/directory_delegations.sh') }}"
|
||||
executable: /bin/bash
|
||||
register: svc_storage_nfs_client_directory_delegations
|
||||
changed_when: "'CHANGED' in svc_storage_nfs_client_directory_delegations.stdout"
|
||||
@@ -51,6 +51,10 @@
|
||||
failed_when:
|
||||
- svc_storage_nfs_client_probe is failed
|
||||
|
||||
- name: "🔧 Align the NFS client's directory delegations with the ganesha server"
|
||||
ansible.builtin.include_tasks: directory_delegations.yml
|
||||
when: lookup('nfs_flavor') == 'ganesha'
|
||||
|
||||
- name: "🔍 Probe-write a marker to confirm the mount is RW"
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ SVC_STORAGE_NFS_CLIENT_PROBE_DIR }}/.svc-storage-nfs-client-probe.{{ inventory_hostname }}"
|
||||
|
||||
@@ -59,3 +59,7 @@
|
||||
retries: 12
|
||||
delay: 5
|
||||
until: _ctrl_nfs_mount is succeeded
|
||||
|
||||
- name: "🔧 Align the controller's directory delegations with the ganesha server"
|
||||
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/svc-storage-nfs-client/tasks/directory_delegations.yml') }}"
|
||||
when: lookup('nfs_flavor') == 'ganesha'
|
||||
@@ -4,6 +4,7 @@ LOG {
|
||||
components {
|
||||
INIT = FULL_DEBUG;
|
||||
DISPATCH = DEBUG;
|
||||
NFS_V4 = DEBUG;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,10 @@
|
||||
- name: ⬇️ Pull the Playwright image once for every role of this run
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- timeout
|
||||
- -k
|
||||
- "60"
|
||||
- "{{ lookup('timeout', 1800) | int }}"
|
||||
- container
|
||||
- pull
|
||||
- "{{ TEST_E2E_PLAYWRIGHT_IMAGE }}"
|
||||
|
||||
@@ -67,7 +67,7 @@ n8n:
|
||||
backup:
|
||||
no_stop_required: false
|
||||
image: docker.n8n.io/n8nio/n8n
|
||||
version: "2.44.4"
|
||||
version: "2.45.0"
|
||||
update:
|
||||
type: registry_tags
|
||||
image: docker.n8n.io/n8nio/n8n
|
||||
|
||||
@@ -142,7 +142,7 @@ rabbit:
|
||||
retries: 5
|
||||
name: opentalk-rabbit
|
||||
image: rabbitmq
|
||||
version: 4.3.6
|
||||
version: 4.4.0
|
||||
min_storage: 314MB
|
||||
cpus: "0.5"
|
||||
mem_reservation: 256m
|
||||
|
||||
@@ -49,7 +49,7 @@ pixelfed:
|
||||
shared: true
|
||||
enabled: true # nocheck: dynamic-flag
|
||||
image: zknt/pixelfed
|
||||
version: "20261019"
|
||||
version: "20261020"
|
||||
name: pixelfed
|
||||
min_storage: 588MB
|
||||
backup:
|
||||
|
||||
@@ -88,9 +88,14 @@ Solid `1:1` edges are fixed relationships; dashed `0..1` edges are conditional (
|
||||
When the oauth2-proxy SSO flavor is active, Snipe-IT's native
|
||||
`loginViaRemoteUser()` is enabled via the `Setting` model
|
||||
(`login_remote_user_enabled`, `login_remote_user_header_name =
|
||||
HTTP_X_FORWARDED_PREFERRED_USERNAME`). nginx gates `/login` through
|
||||
oauth2-proxy/Keycloak and injects the verified
|
||||
`X-Forwarded-Preferred-Username` header, which Snipe-IT matches against
|
||||
INFINITO_SSO_USER`). nginx gates `/login` through
|
||||
oauth2-proxy/Keycloak, injects the verified
|
||||
`X-Forwarded-Preferred-Username` header and sends the
|
||||
`sso_proxy_secret` credential as `X-Infinito-Sso-Proxy` with every
|
||||
request. Apache in the
|
||||
container copies the username into the `INFINITO_SSO_USER` server
|
||||
variable only when that secret matches `INFINITO_SSO_PROXY_SECRET`.
|
||||
Snipe-IT matches the username against
|
||||
`users.username` (the user must pre-exist and be activated — sync them
|
||||
via LDAP) to mint a native `snipeit_session`. Visitors without a matching
|
||||
activated user silently fall through to the normal login form (no error).
|
||||
|
||||
@@ -3,3 +3,5 @@ ARG SNIPE_IT_VERSION
|
||||
FROM ${SNIPE_IT_IMAGE}:${SNIPE_IT_VERSION}
|
||||
|
||||
RUN sed -i 's#php artisan migrate --force#php artisan migrate --force --isolated#' /startup.sh
|
||||
|
||||
RUN printf 'SetEnvIf X-Forwarded-Preferred-Username "^(.+)$" INFINITO_SSO_USER=$1\nSetEnvIfExpr "-z env(\047INFINITO_SSO_PROXY_SECRET\047) || req(\047X-Infinito-Sso-Proxy\047) != env(\047INFINITO_SSO_PROXY_SECRET\047)" !INFINITO_SSO_USER\n' > /etc/apache2/conf.d/infinito-sso.conf
|
||||
@@ -2,14 +2,6 @@
|
||||
// nocheck: mirrored-unit-test - boots Snipe-IT's Laravel kernel through
|
||||
// bootstrap/app.php and writes the trusted-reverse-proxy login settings through
|
||||
// App\Models\Setting; the models only exist once the install has booted
|
||||
// Configure Snipe-IT trusted-reverse-proxy login via the Laravel `Setting`
|
||||
// model (same path the UI uses). The oauth2-proxy in front of Snipe-IT
|
||||
// authenticates the user against Keycloak and nginx injects the verified
|
||||
// identity into the `X-Forwarded-Preferred-Username` request header, which
|
||||
// PHP exposes as $_SERVER['HTTP_X_FORWARDED_PREFERRED_USERNAME'].
|
||||
// LoginController::loginViaRemoteUser() then matches users.username and mints
|
||||
// a native snipeit_session. The password/LDAP form stays available as a
|
||||
// fallback (login_common_disabled is kept at 0).
|
||||
require "vendor/autoload.php";
|
||||
$app = require "bootstrap/app.php";
|
||||
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
||||
|
||||
@@ -10,3 +10,9 @@ credentials:
|
||||
algorithm: alphanumeric
|
||||
validation: "^[A-Za-z0-9]{32,}$"
|
||||
length: 48
|
||||
sso_proxy_secret:
|
||||
description: "Shared secret the reverse proxy sends with every request; Apache in the Snipe-IT container only turns the trusted identity header into the login
|
||||
variable when it matches."
|
||||
algorithm: alphanumeric
|
||||
validation: "^[A-Za-z0-9]{32,}$"
|
||||
length: 48
|
||||
@@ -58,7 +58,7 @@ snipe-it:
|
||||
enabled: true
|
||||
swarm:
|
||||
enabled: true
|
||||
version: v8.8.0-alpine
|
||||
version: v8.8.1-alpine
|
||||
custom: true
|
||||
name: snipe-it
|
||||
image: snipe/snipe-it
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
name: sys-stk-full
|
||||
vars:
|
||||
compose_handlers_flush: true
|
||||
webserver_location_extra_configuration: 'proxy_set_header {{ SNIPE_IT_SSO_PROXY_HEADER }} "{{ SNIPE_IT_SSO_PROXY_SECRET }}";'
|
||||
|
||||
- name: "👔 Snipe-IT manager-side operations (container exec)"
|
||||
include_tasks: 01_install/manager_ops.yml
|
||||
|
||||
@@ -26,3 +26,18 @@
|
||||
ansible.builtin.shell:
|
||||
cmd: "container exec {{ SNIPE_IT_CONTAINER_ADDRESS }} php artisan optimize:clear"
|
||||
changed_when: true
|
||||
|
||||
- name: "🔒 snipe-it: the SSO header bridge logs in only proxy-secret requests"
|
||||
ansible.builtin.shell:
|
||||
cmd: >-
|
||||
container exec {{ SNIPE_IT_CONTAINER_ADDRESS }} sh -c
|
||||
'{{ 30 | curl }} -s -o /dev/null -w "%{http_code}" -H "X-Forwarded-Preferred-Username: $1" {{ item.proxy_header }} http://localhost/login'
|
||||
_ {{ lookup('users', 'administrator').username | quote }}
|
||||
loop:
|
||||
- { label: "forged header without the proxy secret", proxy_header: "", expected: "200" }
|
||||
- { label: "header with the proxy secret", proxy_header: '-H "{{ SNIPE_IT_SSO_PROXY_HEADER }}: $INFINITO_SSO_PROXY_SECRET"', expected: "302" }
|
||||
loop_control:
|
||||
label: "{{ item.label }}"
|
||||
register: snipe_it_sso_bridge_probe
|
||||
changed_when: false
|
||||
failed_when: snipe_it_sso_bridge_probe.stdout != item.expected
|
||||
@@ -78,6 +78,7 @@ SECURE_COOKIES={{ SNIPE_IT_TLS_ENABLED | string | lower }}
|
||||
API_TOKEN_EXPIRATION_YEARS=40
|
||||
|
||||
APP_TRUSTED_PROXIES=**
|
||||
INFINITO_SSO_PROXY_SECRET={{ SNIPE_IT_SSO_PROXY_SECRET }}
|
||||
ALLOW_IFRAMING=false
|
||||
REFERRER_POLICY=same-origin
|
||||
ENABLE_CSP=false
|
||||
|
||||
Loaded 100 of 278 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user