toolframe-core 0.6.2__tar.gz → 0.6.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. toolframe_core-0.6.3/.dockerignore +15 -0
  2. toolframe_core-0.6.3/.github/actions/scan-container/action.yml +44 -0
  3. toolframe_core-0.6.3/.github/dependabot.yml +27 -0
  4. toolframe_core-0.6.3/.github/workflows/container-security.yml +52 -0
  5. toolframe_core-0.6.3/.github/workflows/container.yml +213 -0
  6. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/.github/workflows/publish.yml +11 -0
  7. toolframe_core-0.6.3/.github/workflows/quality.yml +63 -0
  8. toolframe_core-0.6.3/.pre-commit-config.yaml +29 -0
  9. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/CHANGELOG.md +10 -0
  10. toolframe_core-0.6.3/CONTRIBUTING.md +47 -0
  11. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/Cargo.lock +1 -1
  12. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/Cargo.toml +1 -1
  13. toolframe_core-0.6.3/Dockerfile +107 -0
  14. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/PKG-INFO +11 -1
  15. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/README.md +10 -0
  16. toolframe_core-0.6.3/cchk.toml +10 -0
  17. toolframe_core-0.6.3/docs/container.md +247 -0
  18. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/postgresql-state.md +1 -1
  19. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/releasing.md +13 -4
  20. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/pyproject.toml +1 -1
  21. toolframe_core-0.6.3/scripts/check-container-security.py +128 -0
  22. toolframe_core-0.6.3/scripts/check_branch_name.py +59 -0
  23. toolframe_core-0.6.3/scripts/container-healthcheck.py +23 -0
  24. toolframe_core-0.6.3/scripts/harden-container.py +41 -0
  25. toolframe_core-0.6.3/scripts/prepare-container.py +88 -0
  26. toolframe_core-0.6.3/scripts/verify-container.py +335 -0
  27. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/main.rs +8 -8
  28. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/__init__.py +1 -1
  29. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/api.py +1 -0
  30. toolframe_core-0.6.3/tests/test_container_release.py +98 -0
  31. toolframe_core-0.6.3/tests/test_container_security.py +105 -0
  32. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_core_state.py +1 -0
  33. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/uv.lock +1 -1
  34. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/.github/workflows/ci.yml +0 -0
  35. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/.github/workflows/launcher.yml +0 -0
  36. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/.github/workflows/validate.yml +0 -0
  37. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/.gitignore +0 -0
  38. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/LICENSE +0 -0
  39. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/NOTICE +0 -0
  40. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/companion-wheels.txt +0 -0
  41. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0001-explicit-dashboard-access-mode.md +0 -0
  42. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0002-scoped-passwords-and-public-business-entrances.md +0 -0
  43. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0003-public-fallback-on-auth-error-or-disablement.md +0 -0
  44. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0004-separate-auth-ownership-from-dashboard-presentation.md +0 -0
  45. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0005-password-only-login-and-secret-reuse.md +0 -0
  46. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0006-require-explicit-plugin-authorization-contracts.md +0 -0
  47. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0007-independent-mcp-bearer-authentication.md +0 -0
  48. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0008-shared-oidc-authentication.md +0 -0
  49. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0009-replaceable-core-journal.md +0 -0
  50. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0010-multiple-sql-providers.md +0 -0
  51. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0011-safe-new-application-access.md +0 -0
  52. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0012-optional-runtime-tracing.md +0 -0
  53. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/adr/0013-context-logging.md +0 -0
  54. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/architecture.md +0 -0
  55. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/authentication.md +0 -0
  56. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/ci.md +0 -0
  57. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/dependency-graph.md +0 -0
  58. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/glossary.md +0 -0
  59. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/mcp-authentication.md +0 -0
  60. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/object-storage.md +0 -0
  61. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/offline-maintenance.md +0 -0
  62. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/oidc-authentication.md +0 -0
  63. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/runtime.md +0 -0
  64. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/sql-storage.md +0 -0
  65. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/testing.md +0 -0
  66. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/docs/tracing.md +0 -0
  67. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/packaging/launcher/NOTICE +0 -0
  68. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/packaging/launcher/README.md +0 -0
  69. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/packaging/launcher/pyproject.toml +0 -0
  70. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/build-launcher.py +0 -0
  71. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/build-release.sh +0 -0
  72. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/ci/check-launcher-wheel.py +0 -0
  73. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/ci/check-postgres.py +0 -0
  74. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/ci/check-release.py +0 -0
  75. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/ci/dependencies.json +0 -0
  76. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/ci/prepare-launcher.py +0 -0
  77. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/ci/validate.py +0 -0
  78. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/dev-wheelhouse.sh +0 -0
  79. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/verify-auth-upgrade.py +0 -0
  80. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/verify-auth.py +0 -0
  81. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/verify-dependencies.py +0 -0
  82. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/verify-network.py +0 -0
  83. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/verify-operation-history.py +0 -0
  84. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/verify-release.py +0 -0
  85. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/verify-retained-plugins.py +0 -0
  86. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/verify-scaffold.py +0 -0
  87. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/scripts/verify-skill.py +0 -0
  88. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/companions.rs +0 -0
  89. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/_state_postgres.py +0 -0
  90. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/_state_sqlite.py +0 -0
  91. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/access.py +0 -0
  92. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/LICENSE +0 -0
  93. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/SKILL.md +0 -0
  94. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/agents/openai.yaml +0 -0
  95. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/LICENSE +0 -0
  96. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/pyproject.toml +0 -0
  97. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/__init__.py +0 -0
  98. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/docs/usage.md +0 -0
  99. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/migrations/0001_init.sql +0 -0
  100. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/page.py +0 -0
  101. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/plugin.py +0 -0
  102. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/service.py +0 -0
  103. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/evals/cases.json +0 -0
  104. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/references/design.md +0 -0
  105. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/references/operations.md +0 -0
  106. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/bundled_skills/toolframe-plugin-design/references/plugin-api.md +0 -0
  107. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/cli.py +0 -0
  108. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/config.py +0 -0
  109. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/diagnostics.py +0 -0
  110. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/graph.py +0 -0
  111. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/host.py +0 -0
  112. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/maintenance.py +0 -0
  113. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/mcp_access.py +0 -0
  114. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/network.py +0 -0
  115. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/oidc_access.py +0 -0
  116. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/packages.py +0 -0
  117. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/process.py +0 -0
  118. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/redaction.py +0 -0
  119. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/scaffold.py +0 -0
  120. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/skills.py +0 -0
  121. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/state.py +0 -0
  122. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/state_admin.py +0 -0
  123. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/stores.py +0 -0
  124. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/templates.json +0 -0
  125. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/toolframe_core/templates.py +0 -0
  126. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/src/uv.rs +0 -0
  127. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_acceptance.py +0 -0
  128. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_access_policy.py +0 -0
  129. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_architecture.py +0 -0
  130. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_auth.py +0 -0
  131. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_bundles.py +0 -0
  132. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_cli.py +0 -0
  133. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_diagnostics.py +0 -0
  134. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_foundations.py +0 -0
  135. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_host.py +0 -0
  136. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_maintenance.py +0 -0
  137. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_managed_tasks.py +0 -0
  138. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_mcp_auth.py +0 -0
  139. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_mixed_storage.py +0 -0
  140. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_network.py +0 -0
  141. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_object_store.py +0 -0
  142. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_oidc.py +0 -0
  143. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_operation_history.py +0 -0
  144. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_process.py +0 -0
  145. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_redaction.py +0 -0
  146. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_retained_plugins.py +0 -0
  147. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_scaffold.py +0 -0
  148. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_skills.py +0 -0
  149. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_storage_bindings.py +0 -0
  150. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/tests/test_tracing.py +0 -0
  151. {toolframe_core-0.6.2 → toolframe_core-0.6.3}/uv-release-manifest.txt +0 -0
@@ -0,0 +1,15 @@
1
+ **
2
+ !Dockerfile
3
+ !scripts/
4
+ !scripts/container-healthcheck.py
5
+ !scripts/harden-container.py
6
+ !build/
7
+ !build/container/
8
+ !build/container/amd64/
9
+ !build/container/amd64/toolframe
10
+ !build/container/amd64/LICENSE
11
+ !build/container/amd64/NOTICE
12
+ !build/container/arm64/
13
+ !build/container/arm64/toolframe
14
+ !build/container/arm64/LICENSE
15
+ !build/container/arm64/NOTICE
@@ -0,0 +1,44 @@
1
+ name: Scan a Toolframe container
2
+ description: Retain the full vulnerability inventory and enforce publication policy
3
+ inputs:
4
+ image:
5
+ description: Local image tag or public registry reference
6
+ required: true
7
+ platform:
8
+ description: Linux platform to scan
9
+ required: true
10
+ image-source:
11
+ description: Trivy image source (docker or remote)
12
+ required: true
13
+ runs:
14
+ using: composite
15
+ steps:
16
+ - uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567
17
+ with:
18
+ version: v0.76.0
19
+ cache: false
20
+ - name: Scan every severity and keep the package inventory
21
+ shell: bash
22
+ env:
23
+ SCAN_IMAGE: ${{ inputs.image }}
24
+ SCAN_PLATFORM: ${{ inputs.platform }}
25
+ SCAN_SOURCE: ${{ inputs.image-source }}
26
+ run: |
27
+ mkdir -p build/security
28
+ trivy image --image-src "$SCAN_SOURCE" --platform "$SCAN_PLATFORM" \
29
+ --scanners vuln --ignorefile '' --disable-telemetry --list-all-pkgs \
30
+ --timeout 10m --format json --output build/security/scan.json "$SCAN_IMAGE"
31
+ trivy version --format json > build/security/scanner.json
32
+ trivy convert --format cyclonedx --output build/security/sbom.cdx.json build/security/scan.json
33
+ - name: Enforce the security policy
34
+ shell: bash
35
+ run: |
36
+ python3 scripts/check-container-security.py build/security/scan.json \
37
+ --summary build/security/summary.md
38
+ - name: Add findings to the run summary
39
+ if: always()
40
+ shell: bash
41
+ run: |
42
+ if [ -f build/security/summary.md ]; then
43
+ cat build/security/summary.md >> "$GITHUB_STEP_SUMMARY"
44
+ fi
@@ -0,0 +1,27 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: docker
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ day: monday
8
+ time: '04:23'
9
+ timezone: Etc/UTC
10
+ open-pull-requests-limit: 4
11
+ commit-message:
12
+ prefix: fix(container)
13
+ ignore:
14
+ # The 0.6.3 launcher accepts uv >=0.12.2,<0.13.0.
15
+ # Widen this only alongside a compatible launcher release.
16
+ - dependency-name: astral-sh/uv
17
+ versions: ['>=0.13.0']
18
+ - package-ecosystem: github-actions
19
+ directory: /
20
+ schedule:
21
+ interval: weekly
22
+ day: monday
23
+ time: '04:23'
24
+ timezone: Etc/UTC
25
+ open-pull-requests-limit: 4
26
+ commit-message:
27
+ prefix: chore(ci)
@@ -0,0 +1,52 @@
1
+ name: Container security
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '23 3 * * *'
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: container-security-${{ github.ref }}
13
+ cancel-in-progress: false
14
+
15
+ jobs:
16
+ scan:
17
+ name: ${{ matrix.variant }} (${{ matrix.arch }})
18
+ runs-on: ubuntu-24.04
19
+ timeout-minutes: 20
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ variant: [standard, distroless]
24
+ arch: [amd64, arm64]
25
+ include:
26
+ - variant: standard
27
+ tag: latest
28
+ - variant: distroless
29
+ tag: distroless
30
+ steps:
31
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32
+ with:
33
+ persist-credentials: false
34
+ - name: Select the public image
35
+ id: image
36
+ env:
37
+ OWNER: ${{ github.repository_owner }}
38
+ TAG: ${{ matrix.tag }}
39
+ run: echo "ref=ghcr.io/${OWNER,,}/toolframe:$TAG" >> "$GITHUB_OUTPUT"
40
+ - uses: ./.github/actions/scan-container
41
+ with:
42
+ image: ${{ steps.image.outputs.ref }}
43
+ platform: linux/${{ matrix.arch }}
44
+ image-source: remote
45
+ - name: Save findings and the inventory
46
+ if: always()
47
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
48
+ with:
49
+ name: container-security-${{ matrix.variant }}-${{ matrix.arch }}
50
+ path: build/security/
51
+ if-no-files-found: warn
52
+ retention-days: 7
@@ -0,0 +1,213 @@
1
+ name: Container images
2
+
3
+ on:
4
+ pull_request:
5
+ paths:
6
+ - Dockerfile
7
+ - .dockerignore
8
+ - .github/workflows/container.yml
9
+ - .github/workflows/container-security.yml
10
+ - .github/actions/scan-container/**
11
+ - scripts/*container*.py
12
+ - tests/test_container_*.py
13
+ workflow_call:
14
+ inputs:
15
+ publish:
16
+ type: boolean
17
+ default: false
18
+ release_artifacts:
19
+ description: Use the validated launcher artifacts from this workflow run
20
+ type: boolean
21
+ default: false
22
+ workflow_dispatch:
23
+ inputs:
24
+ publish:
25
+ description: Publish both tested variants to GHCR
26
+ type: boolean
27
+ default: false
28
+
29
+ permissions:
30
+ contents: read
31
+
32
+ jobs:
33
+ build:
34
+ name: ${{ matrix.variant }} (${{ matrix.arch }})
35
+ runs-on: ${{ matrix.runner }}
36
+ timeout-minutes: 30
37
+ permissions:
38
+ contents: read
39
+ packages: write
40
+ strategy:
41
+ fail-fast: false
42
+ matrix:
43
+ variant: [standard, distroless]
44
+ arch: [amd64, arm64]
45
+ include:
46
+ - arch: amd64
47
+ runner: ubuntu-24.04
48
+ target: x86_64-unknown-linux-gnu
49
+ - arch: arm64
50
+ runner: ubuntu-24.04-arm
51
+ target: aarch64-unknown-linux-gnu
52
+ outputs:
53
+ version: ${{ steps.metadata.outputs.version }}
54
+ image: ${{ steps.metadata.outputs.image }}
55
+ steps:
56
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
57
+ with:
58
+ persist-credentials: false
59
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
60
+ with:
61
+ python-version: '3.14.7'
62
+ - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
63
+ - name: Read the version and registry namespace
64
+ id: metadata
65
+ env:
66
+ REPOSITORY_OWNER: ${{ github.repository_owner }}
67
+ GH_TOKEN: ${{ github.token }}
68
+ run: |
69
+ version=$(python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["package"]["version"])')
70
+ # PRs exercise packaging with an available public runtime; source CI
71
+ # validates the unreleased Core independently.
72
+ if [ "$GITHUB_EVENT_NAME" = pull_request ]; then
73
+ tag=$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName --jq .tagName)
74
+ version=${tag#v}
75
+ fi
76
+ echo "version=$version" >> "$GITHUB_OUTPUT"
77
+ echo "image=ghcr.io/${REPOSITORY_OWNER,,}/toolframe" >> "$GITHUB_OUTPUT"
78
+ - name: Download the validated launcher from this publication run
79
+ if: inputs.release_artifacts
80
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
81
+ with:
82
+ name: launcher-release-${{ matrix.target }}
83
+ path: build/container-release/
84
+ - name: Stage the verified executable
85
+ env:
86
+ USE_ARTIFACTS: ${{ inputs.release_artifacts }}
87
+ ARCH: ${{ matrix.arch }}
88
+ VERSION: ${{ steps.metadata.outputs.version }}
89
+ run: |
90
+ options=()
91
+ if [ "$USE_ARTIFACTS" = true ]; then
92
+ options+=(--release-dir build/container-release)
93
+ fi
94
+ python scripts/prepare-container.py --architecture "$ARCH" --version "$VERSION" \
95
+ --repository "$GITHUB_REPOSITORY" "${options[@]}"
96
+ - name: Build the native image
97
+ env:
98
+ ARCH: ${{ matrix.arch }}
99
+ VARIANT: ${{ matrix.variant }}
100
+ VERSION: ${{ steps.metadata.outputs.version }}
101
+ run: |
102
+ docker buildx build --platform "linux/$ARCH" --target "$VARIANT" \
103
+ --pull --no-cache-filter runtime \
104
+ --build-arg "TOOLFRAME_VERSION=$VERSION" \
105
+ --build-arg "TOOLFRAME_REVISION=$GITHUB_SHA" \
106
+ --label "org.opencontainers.image.source=https://github.com/$GITHUB_REPOSITORY" \
107
+ --provenance=false --load --tag toolframe-check:local .
108
+ - name: Verify public interfaces, persistence and shutdown
109
+ env:
110
+ ARCH: ${{ matrix.arch }}
111
+ VARIANT: ${{ matrix.variant }}
112
+ run: |
113
+ python scripts/verify-container.py --image toolframe-check:local \
114
+ --variant "$VARIANT" --platform "linux/$ARCH" \
115
+ --output build/container-verification.json
116
+ - name: Save verification evidence
117
+ if: always()
118
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
119
+ with:
120
+ name: container-evidence-${{ matrix.variant }}-${{ matrix.arch }}
121
+ path: build/container-verification*
122
+ if-no-files-found: warn
123
+ retention-days: 7
124
+ - name: Scan the tested image before publication
125
+ uses: ./.github/actions/scan-container
126
+ with:
127
+ image: toolframe-check:local
128
+ platform: linux/${{ matrix.arch }}
129
+ image-source: docker
130
+ - name: Save security evidence
131
+ if: always()
132
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
133
+ with:
134
+ name: container-security-${{ matrix.variant }}-${{ matrix.arch }}
135
+ path: build/security/
136
+ if-no-files-found: warn
137
+ retention-days: 7
138
+ - name: Log in to GHCR
139
+ if: inputs.publish
140
+ uses: docker/login-action@783ae9cac559640a24c9212dfa6cd9e998252f9a
141
+ with:
142
+ registry: ghcr.io
143
+ username: ${{ github.actor }}
144
+ password: ${{ secrets.GITHUB_TOKEN }}
145
+ - name: Push the exact locally tested image
146
+ if: inputs.publish
147
+ env:
148
+ IMAGE: ${{ steps.metadata.outputs.image }}
149
+ ARCH: ${{ matrix.arch }}
150
+ VARIANT: ${{ matrix.variant }}
151
+ run: |
152
+ ref="$IMAGE:build-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$VARIANT-$ARCH"
153
+ docker tag toolframe-check:local "$ref"
154
+ docker push "$ref"
155
+ mkdir -p build/digests
156
+ docker image inspect --format '{{index .RepoDigests 0}}' "$ref" \
157
+ > "build/digests/$VARIANT-$ARCH.txt"
158
+ - name: Save the tested image digest
159
+ if: inputs.publish
160
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
161
+ with:
162
+ name: container-digest-${{ matrix.variant }}-${{ matrix.arch }}
163
+ path: build/digests/*.txt
164
+ if-no-files-found: error
165
+
166
+ publish:
167
+ name: Publish multi-platform tags
168
+ needs: build
169
+ if: inputs.publish
170
+ runs-on: ubuntu-24.04
171
+ timeout-minutes: 10
172
+ permissions:
173
+ contents: read
174
+ packages: write
175
+ concurrency:
176
+ group: container-publish-${{ github.repository }}
177
+ cancel-in-progress: false
178
+ steps:
179
+ - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
180
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
181
+ with:
182
+ pattern: container-digest-*
183
+ merge-multiple: true
184
+ path: build/digests/
185
+ - uses: docker/login-action@783ae9cac559640a24c9212dfa6cd9e998252f9a
186
+ with:
187
+ registry: ghcr.io
188
+ username: ${{ github.actor }}
189
+ password: ${{ secrets.GITHUB_TOKEN }}
190
+ - name: Publish both architectures after all four checks pass
191
+ env:
192
+ IMAGE: ${{ needs.build.outputs.image }}
193
+ VERSION: ${{ needs.build.outputs.version }}
194
+ GH_TOKEN: ${{ github.token }}
195
+ GH_REPO: ${{ github.repository }}
196
+ run: |
197
+ latest=$(gh release view --repo "$GH_REPO" --json tagName --jq .tagName)
198
+ for variant in standard distroless; do
199
+ suffix=""
200
+ alias=latest
201
+ if [ "$variant" = distroless ]; then
202
+ suffix=-distroless
203
+ alias=distroless
204
+ fi
205
+ mapfile -t refs < <(cat build/digests/"$variant"-*.txt)
206
+ test "${#refs[@]}" -eq 2
207
+ tags=(--tag "$IMAGE:$VERSION$suffix")
208
+ if [ "$latest" = "v$VERSION" ] && [[ "$VERSION" != *-* ]]; then
209
+ tags+=(--tag "$IMAGE:$alias")
210
+ fi
211
+ docker buildx imagetools create "${tags[@]}" "${refs[@]}"
212
+ docker buildx imagetools inspect "$IMAGE:$VERSION$suffix"
213
+ done
@@ -126,3 +126,14 @@ jobs:
126
126
  EOF
127
127
  gh release create "$RELEASE_TAG" --verify-tag --title "Toolframe $RELEASE_TAG" \
128
128
  --notes-file release-notes.txt releases/*.tar.gz releases/SHA256SUMS releases/manifest-*.json releases/*.whl
129
+
130
+ publish-containers:
131
+ needs: release-binaries
132
+ if: inputs.index == 'pypi'
133
+ permissions:
134
+ contents: read
135
+ packages: write
136
+ uses: ./.github/workflows/container.yml
137
+ with:
138
+ publish: true
139
+ release_artifacts: true
@@ -0,0 +1,63 @@
1
+ name: Code quality
2
+
3
+ on:
4
+ push:
5
+ branches: ['**']
6
+ pull_request:
7
+ types: [opened, synchronize, reopened, edited]
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ branch-policy:
15
+ runs-on: ubuntu-24.04
16
+ timeout-minutes: 5
17
+ steps:
18
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ with:
20
+ persist-credentials: false
21
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
22
+ with:
23
+ python-version: '3.14.7'
24
+ - name: Check the actual source branch
25
+ env:
26
+ BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
27
+ run: python scripts/check_branch_name.py "$BRANCH_NAME"
28
+
29
+ code-quality:
30
+ runs-on: ubuntu-24.04
31
+ timeout-minutes: 10
32
+ steps:
33
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
34
+ with:
35
+ persist-credentials: false
36
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
37
+ with:
38
+ version: '0.12.8'
39
+ enable-cache: false
40
+ - name: Check Python lint and formatting
41
+ run: uvx prek==0.5.5 run ruff-check ruff-format --all-files --show-diff-on-failure
42
+
43
+ commit-policy:
44
+ if: github.event_name == 'pull_request'
45
+ runs-on: ubuntu-24.04
46
+ timeout-minutes: 10
47
+ permissions:
48
+ contents: read
49
+ pull-requests: read
50
+ steps:
51
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
52
+ with:
53
+ fetch-depth: 0
54
+ persist-credentials: false
55
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
56
+ with:
57
+ python-version: '3.14.7'
58
+ - uses: commit-check/commit-check-action@a61779784a953fd9047c001c102cfeac5cf26964 # commit-check 2.18.3
59
+ with:
60
+ message: true
61
+ branch: false
62
+ pr-title: true
63
+ pr-comments: false
@@ -0,0 +1,29 @@
1
+ default_install_hook_types: [pre-commit, commit-msg]
2
+ default_stages: [pre-commit]
3
+ default_language_version:
4
+ python: python3.11
5
+
6
+ repos:
7
+ - repo: local
8
+ hooks:
9
+ - id: check-branch-name
10
+ name: Check branch naming policy
11
+ entry: python scripts/check_branch_name.py
12
+ language: python
13
+ always_run: true
14
+ pass_filenames: false
15
+ - repo: https://github.com/astral-sh/ruff-pre-commit
16
+ rev: v0.16.8
17
+ hooks:
18
+ - id: ruff-check
19
+ types_or: [python, pyi]
20
+ files: '^(src/|tests/|scripts/(build-launcher\.py$|verify-release\.py$|verify-skill\.py$|check_branch_name\.py$|ci/))'
21
+ - id: ruff-format
22
+ types_or: [python, pyi]
23
+ args: [--check]
24
+ files: '^(src/|tests/|scripts/(build-launcher\.py$|verify-release\.py$|verify-skill\.py$|check_branch_name\.py$|ci/))'
25
+ - repo: https://github.com/commit-check/commit-check
26
+ rev: v2.18.3
27
+ hooks:
28
+ - id: check-message
29
+ stages: [commit-msg]
@@ -5,6 +5,16 @@ imply that its distributions have been published or every target is qualified.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.6.3
9
+
10
+ - Apply container security updates, remove unused pip bootstrap dependencies and
11
+ privileged executable bits, and gate publication on vulnerability scans with
12
+ daily image checks and weekly dependency update proposals.
13
+
14
+ ## 0.6.2
15
+
16
+ - Add Debian and Distroless container images with prepared Core runtimes, Tini,
17
+ persistent application volumes and verified multi-platform GHCR publication.
8
18
  - Run multiple SQL storage providers using service defaults and per-plugin bindings.
9
19
  - Retain store provider ownership across removal and restart; reject silent rebinding.
10
20
  - Bind migration plans and forward repair to their provider, and preserve admitted
@@ -0,0 +1,47 @@
1
+ # Contributing
2
+
3
+ ## Commit hooks
4
+
5
+ Install [prek](https://prek.j178.dev/) and enable the repository's hooks:
6
+
7
+ ```sh
8
+ uv tool install prek==0.5.5
9
+ prek install --allow-missing-config
10
+ prek run --all-files
11
+ ```
12
+
13
+ The missing-config option allows older worktrees without this configuration to
14
+ keep working. Worktrees containing `.pre-commit-config.yaml` still run the checks.
15
+ The `pre-commit` hook checks the branch name, Python lint, and formatting. The
16
+ `commit-msg` hook checks Conventional Commits. Both Ruff hooks only check files;
17
+ they do not rewrite a commit's contents. Fix lint and formatting explicitly:
18
+
19
+ ```sh
20
+ uvx ruff==0.16.8 check --fix src tests scripts/build-launcher.py scripts/verify-release.py scripts/verify-skill.py scripts/ci scripts/check_branch_name.py
21
+ uvx ruff==0.16.8 format src tests scripts/build-launcher.py scripts/verify-release.py scripts/verify-skill.py scripts/ci scripts/check_branch_name.py
22
+ prek run --all-files
23
+ ```
24
+
25
+ The Ruff version and checked paths are pinned in
26
+ [the hook configuration](.pre-commit-config.yaml); the Python rules remain in
27
+ [pyproject.toml](pyproject.toml). CI uses these same hooks on all tracked files
28
+ within their configured paths. Existing package, integration, and release checks
29
+ run independently.
30
+
31
+ ## Branches and commits
32
+
33
+ [cchk.toml](cchk.toml) defines the allowed branch and commit types. Work branches
34
+ use `feat/`, `fix/`, `release/`, or `chore/`, followed by lowercase ASCII letters
35
+ and digits separated by single hyphens. Release versions may also contain dots,
36
+ as in `release/v1.2.0-rc.1`; every separator must have an alphanumeric character on
37
+ both sides. The default branch `main` is the standalone exception.
38
+
39
+ Commit types are independent of branch types: for example,
40
+ `docs: clarify setup` is valid on `chore/update-docs`. Commit-check uses Conventional
41
+ Commits with its default subject length of 5–80 characters. Pull requests check
42
+ their commits and title so the title can be used for a Conventional Commit when
43
+ squash merging.
44
+
45
+ Local hooks can be bypassed. CI reports `branch-policy`, `code-quality`, and
46
+ `commit-policy`; maintainers must configure required status checks separately to
47
+ make passing checks a merge requirement.
@@ -1186,7 +1186,7 @@ dependencies = [
1186
1186
 
1187
1187
  [[package]]
1188
1188
  name = "toolframe"
1189
- version = "0.6.2"
1189
+ version = "0.6.3"
1190
1190
  dependencies = [
1191
1191
  "anyhow",
1192
1192
  "flate2",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "toolframe"
3
- version = "0.6.2"
3
+ version = "0.6.3"
4
4
  edition = "2024"
5
5
  rust-version = "1.88"
6
6
  description = "A local-first runtime for agent-built internal apps"
@@ -0,0 +1,107 @@
1
+ # syntax=docker/dockerfile:1
2
+ FROM ghcr.io/astral-sh/uv:0.12.24@sha256:3af4716e991d6956a41e573eab705d0ee08500cd829ed30293eb8472f372c65a AS uv
3
+
4
+ FROM debian:trixie-slim@sha256:a29215f6a35e51e22adffa17f89e9d2ef06214e64a2bad10d765c46aea49f11f AS runtime
5
+ ARG TARGETARCH
6
+ ARG TOOLFRAME_VERSION=0.6.3
7
+ ARG TOOLFRAME_REVISION
8
+ LABEL org.opencontainers.image.source="https://github.com/toolframe/toolframe-core" \
9
+ org.opencontainers.image.title="Toolframe" \
10
+ org.opencontainers.image.description="Persistent control host for Python plugins" \
11
+ org.opencontainers.image.licenses="Apache-2.0 AND MIT" \
12
+ org.opencontainers.image.version="${TOOLFRAME_VERSION}" \
13
+ org.opencontainers.image.revision="${TOOLFRAME_REVISION}"
14
+ RUN apt-get update \
15
+ && apt-get upgrade -y --no-install-recommends \
16
+ && apt-get install -y --no-install-recommends ca-certificates git libstdc++6 procps tini \
17
+ && rm -rf /var/lib/apt/lists/* \
18
+ && find /usr -xdev -type f -perm /6000 -exec chmod a-s '{}' + \
19
+ && groupadd --gid 65532 nonroot \
20
+ && useradd --uid 65532 --gid 65532 --create-home --home-dir /home/nonroot nonroot \
21
+ && install -d -o 65532 -g 65532 /data /opt/toolframe
22
+ COPY --from=uv /uv /usr/local/bin/uv
23
+ # prepare-container.py verifies the release checksums before staging these files.
24
+ COPY build/container/${TARGETARCH}/toolframe /usr/local/bin/toolframe
25
+ COPY build/container/${TARGETARCH}/LICENSE build/container/${TARGETARCH}/NOTICE /usr/share/doc/toolframe/
26
+ COPY scripts/container-healthcheck.py /usr/local/lib/toolframe/healthcheck.py
27
+ COPY scripts/harden-container.py /usr/local/lib/toolframe/harden.py
28
+ ENV HOME=/home/nonroot \
29
+ TOOLFRAME_UV=/usr/local/bin/uv \
30
+ TOOLFRAME_RUNTIME_ROOT=/opt/toolframe/runtime \
31
+ UV_PYTHON_INSTALL_DIR=/opt/toolframe/python \
32
+ UV_CACHE_DIR=/data/.toolframe/uv-cache \
33
+ UV_LINK_MODE=copy \
34
+ PYTHONUNBUFFERED=1 \
35
+ PYTHONDONTWRITEBYTECODE=1
36
+ USER 65532:65532
37
+ WORKDIR /data
38
+ # --help prepares Core without creating an application, keys or management tokens.
39
+ # Keep the same absolute Python and venv paths in both final images.
40
+ RUN test "$(toolframe --version)" = "toolframe ${TOOLFRAME_VERSION}" \
41
+ && UV_CACHE_DIR=/tmp/uv-build-cache toolframe --help > /dev/null \
42
+ && ln -s "$(uv python find --managed-python 3.14.7)" /opt/toolframe/python3 \
43
+ && UV_CACHE_DIR=/tmp/uv-build-cache uv cache clean \
44
+ && /opt/toolframe/python3 /usr/local/lib/toolframe/harden.py
45
+ VOLUME ["/data"]
46
+ EXPOSE 9876
47
+ STOPSIGNAL SIGTERM
48
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=60s \
49
+ CMD ["/opt/toolframe/python3", "/usr/local/lib/toolframe/healthcheck.py"]
50
+ ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/toolframe", "--home", "/data"]
51
+ CMD ["serve", "--host", "0.0.0.0"]
52
+
53
+ FROM runtime AS ps-dependencies
54
+ USER root
55
+ # Core uses ps to reconcile worker ownership. Retain the final base's libc and
56
+ # loader, and copy only ps's additional shared libraries under their SONAMEs.
57
+ # Include dpkg status fragments so scanners can identify these copied components.
58
+ RUN mkdir -p /opt/ps-libs/var/lib/dpkg/status.d \
59
+ && ldd /usr/bin/ps | awk '/=> \// { print $3 }' | while read -r library; do \
60
+ case "$library" in */libc.so.*|*/libm.so.*) continue ;; esac; \
61
+ directory=$(dirname "$(readlink -f "$library")"); \
62
+ install -D "$library" "/opt/ps-libs$directory/$(basename "$library")"; \
63
+ package=$(dpkg-query -S "$(readlink -f "$library")") || exit 1; \
64
+ package=${package%%:*}; \
65
+ dpkg-query -s "$package" > "/opt/ps-libs/var/lib/dpkg/status.d/$package" || exit 1; \
66
+ done \
67
+ && dpkg-query -s procps > /opt/ps-libs/var/lib/dpkg/status.d/procps \
68
+ && dpkg-query -s tini > /opt/ps-libs/var/lib/dpkg/status.d/tini
69
+
70
+ FROM gcr.io/distroless/cc-debian13:nonroot@sha256:e792ab3d241a468a4fd7519ddbbebe66b49b5f365771716ea688ad40b6c6f1c2 AS distroless
71
+ ARG TOOLFRAME_VERSION=0.6.3
72
+ ARG TOOLFRAME_REVISION
73
+ LABEL org.opencontainers.image.source="https://github.com/toolframe/toolframe-core" \
74
+ org.opencontainers.image.title="Toolframe (Distroless)" \
75
+ org.opencontainers.image.description="Persistent control host for Python plugins, without a system shell" \
76
+ org.opencontainers.image.licenses="Apache-2.0 AND MIT" \
77
+ org.opencontainers.image.version="${TOOLFRAME_VERSION}" \
78
+ org.opencontainers.image.revision="${TOOLFRAME_REVISION}"
79
+ COPY --from=runtime /usr/local/bin/toolframe /usr/local/bin/uv /usr/local/bin/
80
+ COPY --from=runtime /usr/bin/tini /usr/bin/tini
81
+ COPY --from=runtime /usr/bin/ps /usr/bin/ps
82
+ COPY --from=ps-dependencies /opt/ps-libs/ /
83
+ COPY --from=runtime /usr/share/doc/ /usr/share/doc/
84
+ COPY --from=runtime /usr/local/lib/toolframe/ /usr/local/lib/toolframe/
85
+ COPY --from=runtime --chown=65532:65532 /opt/toolframe/ /opt/toolframe/
86
+ COPY --from=runtime --chown=65532:65532 /home/nonroot/ /home/nonroot/
87
+ COPY --from=runtime --chown=65532:65532 /data/ /data/
88
+ ENV HOME=/home/nonroot \
89
+ TOOLFRAME_UV=/usr/local/bin/uv \
90
+ TOOLFRAME_RUNTIME_ROOT=/opt/toolframe/runtime \
91
+ UV_PYTHON_INSTALL_DIR=/opt/toolframe/python \
92
+ UV_CACHE_DIR=/data/.toolframe/uv-cache \
93
+ UV_LINK_MODE=copy \
94
+ PYTHONUNBUFFERED=1 \
95
+ PYTHONDONTWRITEBYTECODE=1
96
+ USER 65532:65532
97
+ WORKDIR /data
98
+ VOLUME ["/data"]
99
+ EXPOSE 9876
100
+ STOPSIGNAL SIGTERM
101
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=60s \
102
+ CMD ["/opt/toolframe/python3", "/usr/local/lib/toolframe/healthcheck.py"]
103
+ ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/toolframe", "--home", "/data"]
104
+ CMD ["serve", "--host", "0.0.0.0"]
105
+
106
+ # The default build target is the Debian image.
107
+ FROM runtime AS standard