ownfoil 2.4.0__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 (183) hide show
  1. ownfoil-2.4.0/.devcontainer/devcontainer.json +29 -0
  2. ownfoil-2.4.0/.devcontainer/post-create.sh +58 -0
  3. ownfoil-2.4.0/.github/dependabot.yml +31 -0
  4. ownfoil-2.4.0/.github/release.yml +21 -0
  5. ownfoil-2.4.0/.github/workflows/build_check.yml +20 -0
  6. ownfoil-2.4.0/.github/workflows/docker.yml +68 -0
  7. ownfoil-2.4.0/.github/workflows/docker_pr.yml +32 -0
  8. ownfoil-2.4.0/.github/workflows/pypi.yml +27 -0
  9. ownfoil-2.4.0/.github/workflows/region_titles.yml +136 -0
  10. ownfoil-2.4.0/.github/workflows/stale.yml +21 -0
  11. ownfoil-2.4.0/.github/workflows/test.yml +37 -0
  12. ownfoil-2.4.0/.gitignore +27 -0
  13. ownfoil-2.4.0/Dockerfile +25 -0
  14. ownfoil-2.4.0/Install.md +208 -0
  15. ownfoil-2.4.0/LICENSE.md +661 -0
  16. ownfoil-2.4.0/PKG-INFO +79 -0
  17. ownfoil-2.4.0/README.md +45 -0
  18. ownfoil-2.4.0/Usage.md +311 -0
  19. ownfoil-2.4.0/app/app.py +545 -0
  20. ownfoil-2.4.0/app/auth.py +370 -0
  21. ownfoil-2.4.0/app/clients/__init__.py +9 -0
  22. ownfoil-2.4.0/app/clients/client.py +171 -0
  23. ownfoil-2.4.0/app/clients/cyberfoil.py +137 -0
  24. ownfoil-2.4.0/app/clients/sphaira.py +154 -0
  25. ownfoil-2.4.0/app/clients/tinfoil.py +189 -0
  26. ownfoil-2.4.0/app/constants.py +158 -0
  27. ownfoil-2.4.0/app/containers/__init__.py +8 -0
  28. ownfoil-2.4.0/app/containers/cnmt.py +67 -0
  29. ownfoil-2.4.0/app/containers/compression.py +182 -0
  30. ownfoil-2.4.0/app/containers/container.py +28 -0
  31. ownfoil-2.4.0/app/containers/verification.py +156 -0
  32. ownfoil-2.4.0/app/db.py +691 -0
  33. ownfoil-2.4.0/app/file_watcher.py +323 -0
  34. ownfoil-2.4.0/app/gql/README.md +550 -0
  35. ownfoil-2.4.0/app/gql/__init__.py +5 -0
  36. ownfoil-2.4.0/app/gql/cache.py +77 -0
  37. ownfoil-2.4.0/app/gql/context.py +40 -0
  38. ownfoil-2.4.0/app/gql/docs.py +44 -0
  39. ownfoil-2.4.0/app/gql/filters.py +665 -0
  40. ownfoil-2.4.0/app/gql/mutations.py +238 -0
  41. ownfoil-2.4.0/app/gql/resolvers.py +1174 -0
  42. ownfoil-2.4.0/app/gql/scalars.py +15 -0
  43. ownfoil-2.4.0/app/gql/schema.py +227 -0
  44. ownfoil-2.4.0/app/gql/selection.py +43 -0
  45. ownfoil-2.4.0/app/gql/types.py +557 -0
  46. ownfoil-2.4.0/app/gql/view.py +111 -0
  47. ownfoil-2.4.0/app/library.py +381 -0
  48. ownfoil-2.4.0/app/local.py +76 -0
  49. ownfoil-2.4.0/app/migrations/README +1 -0
  50. ownfoil-2.4.0/app/migrations/alembic.ini +51 -0
  51. ownfoil-2.4.0/app/migrations/env.py +111 -0
  52. ownfoil-2.4.0/app/migrations/script.py.mako +23 -0
  53. ownfoil-2.4.0/app/migrations/versions/78c33e9bffce_drop_old_files_table_and_create_new_.py +109 -0
  54. ownfoil-2.4.0/app/migrations/versions/a1b2c3d4e5f6_add_tasks_and_ignored_events.py +63 -0
  55. ownfoil-2.4.0/app/migrations/versions/a7b8c9d0e1f2_add_title_overrides.py +61 -0
  56. ownfoil-2.4.0/app/migrations/versions/b2c3d4e5f6a7_add_organized_column_to_files.py +22 -0
  57. ownfoil-2.4.0/app/migrations/versions/b8c9d0e1f2a3_add_added_at_to_files.py +29 -0
  58. ownfoil-2.4.0/app/migrations/versions/c3d4e5f6a7b8_add_worker_id_to_tasks.py +22 -0
  59. ownfoil-2.4.0/app/migrations/versions/c9d0e1f2a3b4_add_verification_to_files.py +37 -0
  60. ownfoil-2.4.0/app/migrations/versions/d4e5f6a7b8c9_add_temp_files.py +28 -0
  61. ownfoil-2.4.0/app/migrations/versions/e5f6a7b8c9d0_add_release_date_to_apps.py +25 -0
  62. ownfoil-2.4.0/app/migrations/versions/f6a7b8c9d0e1_add_app_files_file_id_index.py +25 -0
  63. ownfoil-2.4.0/app/realtime.py +233 -0
  64. ownfoil-2.4.0/app/run.py +101 -0
  65. ownfoil-2.4.0/app/settings.py +334 -0
  66. ownfoil-2.4.0/app/static/favicon/apple-touch-icon.png +0 -0
  67. ownfoil-2.4.0/app/static/favicon/favicon-96x96.png +0 -0
  68. ownfoil-2.4.0/app/static/favicon/favicon.ico +0 -0
  69. ownfoil-2.4.0/app/static/favicon/favicon.svg +5 -0
  70. ownfoil-2.4.0/app/static/favicon/site.webmanifest +21 -0
  71. ownfoil-2.4.0/app/static/favicon/web-app-manifest-192x192.png +0 -0
  72. ownfoil-2.4.0/app/static/favicon/web-app-manifest-512x512.png +0 -0
  73. ownfoil-2.4.0/app/static/js/format.js +9 -0
  74. ownfoil-2.4.0/app/static/js/realtime.js +87 -0
  75. ownfoil-2.4.0/app/static/logo/logo-1024.png +0 -0
  76. ownfoil-2.4.0/app/static/logo/logo-128.png +0 -0
  77. ownfoil-2.4.0/app/static/logo/logo-256.png +0 -0
  78. ownfoil-2.4.0/app/static/logo/logo-512.png +0 -0
  79. ownfoil-2.4.0/app/static/logo/logo-64.png +0 -0
  80. ownfoil-2.4.0/app/static/logo/logo.svg +3 -0
  81. ownfoil-2.4.0/app/static/style.css +411 -0
  82. ownfoil-2.4.0/app/task_events.py +150 -0
  83. ownfoil-2.4.0/app/tasks.py +1089 -0
  84. ownfoil-2.4.0/app/templates/base.html +36 -0
  85. ownfoil-2.4.0/app/templates/index.html +707 -0
  86. ownfoil-2.4.0/app/templates/login.html +34 -0
  87. ownfoil-2.4.0/app/templates/nav.html +43 -0
  88. ownfoil-2.4.0/app/templates/profile.html +7 -0
  89. ownfoil-2.4.0/app/templates/settings.html +1469 -0
  90. ownfoil-2.4.0/app/templates/settings_layout.html +33 -0
  91. ownfoil-2.4.0/app/templates/setup.html +542 -0
  92. ownfoil-2.4.0/app/templates/stats.html +169 -0
  93. ownfoil-2.4.0/app/templates/tasks.html +467 -0
  94. ownfoil-2.4.0/app/titledb/__init__.py +9 -0
  95. ownfoil-2.4.0/app/titledb/schema.py +142 -0
  96. ownfoil-2.4.0/app/titledb/store.py +611 -0
  97. ownfoil-2.4.0/app/titledb/update.py +106 -0
  98. ownfoil-2.4.0/app/titles.py +177 -0
  99. ownfoil-2.4.0/app/utils.py +397 -0
  100. ownfoil-2.4.0/app/worker.py +153 -0
  101. ownfoil-2.4.0/app/worker_pool.py +89 -0
  102. ownfoil-2.4.0/chart/Chart.yaml +8 -0
  103. ownfoil-2.4.0/chart/readme.md +2 -0
  104. ownfoil-2.4.0/chart/templates/deploy.yaml +51 -0
  105. ownfoil-2.4.0/chart/templates/ingress.yaml +34 -0
  106. ownfoil-2.4.0/chart/templates/pvc.yaml +24 -0
  107. ownfoil-2.4.0/chart/templates/services.yaml +19 -0
  108. ownfoil-2.4.0/chart/values.yaml +46 -0
  109. ownfoil-2.4.0/docker/run.sh +16 -0
  110. ownfoil-2.4.0/docker-compose.yml +22 -0
  111. ownfoil-2.4.0/ownfoil/__init__.py +0 -0
  112. ownfoil-2.4.0/ownfoil/cli.py +77 -0
  113. ownfoil-2.4.0/pyproject.toml +53 -0
  114. ownfoil-2.4.0/requirements.txt +15 -0
  115. ownfoil-2.4.0/tests/capture/README.md +47 -0
  116. ownfoil-2.4.0/tests/capture/fixture.py +105 -0
  117. ownfoil-2.4.0/tests/capture/run_capture.py +128 -0
  118. ownfoil-2.4.0/tests/capture/sanitize.py +137 -0
  119. ownfoil-2.4.0/tests/capture/scenarios.py +150 -0
  120. ownfoil-2.4.0/tests/capture/server.py +217 -0
  121. ownfoil-2.4.0/tests/captures/cyberfoil/admin-browse.json +537 -0
  122. ownfoil-2.4.0/tests/captures/cyberfoil/authenticated-browse.json +537 -0
  123. ownfoil-2.4.0/tests/captures/cyberfoil/client-disabled.json +275 -0
  124. ownfoil-2.4.0/tests/captures/cyberfoil/content-filter.json +375 -0
  125. ownfoil-2.4.0/tests/captures/cyberfoil/download-repeat.json +243 -0
  126. ownfoil-2.4.0/tests/captures/cyberfoil/download.json +833 -0
  127. ownfoil-2.4.0/tests/captures/cyberfoil/free-browsing.json +46 -0
  128. ownfoil-2.4.0/tests/captures/cyberfoil/no-shop-access.json +275 -0
  129. ownfoil-2.4.0/tests/captures/cyberfoil/private-no-credentials.json +263 -0
  130. ownfoil-2.4.0/tests/captures/cyberfoil/public-browse.json +517 -0
  131. ownfoil-2.4.0/tests/captures/cyberfoil/unknown-user.json +275 -0
  132. ownfoil-2.4.0/tests/captures/cyberfoil/wrong-password.json +275 -0
  133. ownfoil-2.4.0/tests/captures/sphaira/admin-browse.json +97 -0
  134. ownfoil-2.4.0/tests/captures/sphaira/authenticated-browse.json +97 -0
  135. ownfoil-2.4.0/tests/captures/sphaira/client-disabled.json +97 -0
  136. ownfoil-2.4.0/tests/captures/sphaira/content-filter.json +97 -0
  137. ownfoil-2.4.0/tests/captures/sphaira/download-repeat.json +195 -0
  138. ownfoil-2.4.0/tests/captures/sphaira/download.json +297 -0
  139. ownfoil-2.4.0/tests/captures/sphaira/free-browsing.json +673 -0
  140. ownfoil-2.4.0/tests/captures/sphaira/no-shop-access.json +97 -0
  141. ownfoil-2.4.0/tests/captures/sphaira/private-no-credentials.json +93 -0
  142. ownfoil-2.4.0/tests/captures/sphaira/public-browse.json +93 -0
  143. ownfoil-2.4.0/tests/captures/sphaira/unknown-user.json +97 -0
  144. ownfoil-2.4.0/tests/captures/sphaira/wrong-password.json +97 -0
  145. ownfoil-2.4.0/tests/captures/tinfoil/admin-browse.json +127 -0
  146. ownfoil-2.4.0/tests/captures/tinfoil/authenticated-browse.json +127 -0
  147. ownfoil-2.4.0/tests/captures/tinfoil/client-disabled.json +127 -0
  148. ownfoil-2.4.0/tests/captures/tinfoil/content-filter.json +127 -0
  149. ownfoil-2.4.0/tests/captures/tinfoil/download-repeat.json +125 -0
  150. ownfoil-2.4.0/tests/captures/tinfoil/download.json +127 -0
  151. ownfoil-2.4.0/tests/captures/tinfoil/free-browsing.json +437 -0
  152. ownfoil-2.4.0/tests/captures/tinfoil/no-shop-access.json +127 -0
  153. ownfoil-2.4.0/tests/captures/tinfoil/private-no-credentials.json +123 -0
  154. ownfoil-2.4.0/tests/captures/tinfoil/public-browse.json +123 -0
  155. ownfoil-2.4.0/tests/captures/tinfoil/tinfoil-plaintext.json +133 -0
  156. ownfoil-2.4.0/tests/captures/tinfoil/unknown-user.json +127 -0
  157. ownfoil-2.4.0/tests/captures/tinfoil/wrong-password.json +127 -0
  158. ownfoil-2.4.0/tests/conftest.py +69 -0
  159. ownfoil-2.4.0/tests/test_clients.py +592 -0
  160. ownfoil-2.4.0/tests/test_compression.py +794 -0
  161. ownfoil-2.4.0/tests/test_containers.py +66 -0
  162. ownfoil-2.4.0/tests/test_db_paths.py +31 -0
  163. ownfoil-2.4.0/tests/test_download_count.py +109 -0
  164. ownfoil-2.4.0/tests/test_file_watcher.py +439 -0
  165. ownfoil-2.4.0/tests/test_first_run.py +137 -0
  166. ownfoil-2.4.0/tests/test_gql_app_cards.py +212 -0
  167. ownfoil-2.4.0/tests/test_gql_catalogue.py +160 -0
  168. ownfoil-2.4.0/tests/test_gql_etag.py +178 -0
  169. ownfoil-2.4.0/tests/test_gql_graph.py +755 -0
  170. ownfoil-2.4.0/tests/test_gql_mutations.py +257 -0
  171. ownfoil-2.4.0/tests/test_gql_schema_docs.py +58 -0
  172. ownfoil-2.4.0/tests/test_gql_verification_status.py +174 -0
  173. ownfoil-2.4.0/tests/test_lan_ip.py +99 -0
  174. ownfoil-2.4.0/tests/test_network_path.py +67 -0
  175. ownfoil-2.4.0/tests/test_open_browser.py +77 -0
  176. ownfoil-2.4.0/tests/test_organizer_paths.py +189 -0
  177. ownfoil-2.4.0/tests/test_realtime.py +230 -0
  178. ownfoil-2.4.0/tests/test_task_display_names.py +60 -0
  179. ownfoil-2.4.0/tests/test_task_events.py +321 -0
  180. ownfoil-2.4.0/tests/test_titledb_lifecycle.py +348 -0
  181. ownfoil-2.4.0/tests/test_titledb_schedule.py +75 -0
  182. ownfoil-2.4.0/tests/test_titledb_update.py +188 -0
  183. ownfoil-2.4.0/tests/test_verification.py +408 -0
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "Ownfoil",
3
+ "image": "mcr.microsoft.com/devcontainers/base:bookworm",
4
+
5
+ // Install uv, Python and the virtualenv once, when the container is created.
6
+ "postCreateCommand": "bash .devcontainer/post-create.sh",
7
+
8
+ // Make the virtualenv the default interpreter for every process in the container.
9
+ "remoteEnv": {
10
+ "PATH": "${containerWorkspaceFolder}/.venv/bin:${containerEnv:PATH}"
11
+ },
12
+
13
+ // Ownfoil web UI
14
+ "forwardPorts": [8465],
15
+ "portsAttributes": {
16
+ "8465": {
17
+ "label": "Ownfoil",
18
+ "onAutoForward": "notify"
19
+ }
20
+ },
21
+
22
+ "customizations": {
23
+ "vscode": {
24
+ "extensions": [
25
+ "eamodio.gitlens"
26
+ ]
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env bash
2
+ # Prepare the development environment: uv, Python, virtualenv and dependencies.
3
+ # Runs once when the dev container / Codespace is created.
4
+ set -euo pipefail
5
+
6
+ PYTHON_VERSION="3.14"
7
+ REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8
+ VENV_DIR="${REPO_DIR}/.venv"
9
+
10
+ # uv installs itself in ~/.local/bin
11
+ export PATH="${HOME}/.local/bin:${PATH}"
12
+
13
+ # 1. Install uv (the base image may already provide it)
14
+ if ! command -v uv >/dev/null 2>&1; then
15
+ echo "Installing uv..."
16
+ curl -LsSf https://astral.sh/uv/install.sh | sh
17
+ fi
18
+ uv --version
19
+
20
+ # 2. Install the Python version used by the Ownfoil image (see Dockerfile)
21
+ echo "Installing Python ${PYTHON_VERSION}..."
22
+ uv python install "${PYTHON_VERSION}"
23
+
24
+ # 3. Create the virtualenv
25
+ echo "Creating virtualenv in ${VENV_DIR}..."
26
+ uv venv --python "${PYTHON_VERSION}" --allow-existing "${VENV_DIR}"
27
+
28
+ # 4. Install the application requirements, plus pytest to run the test suite
29
+ echo "Installing requirements..."
30
+ uv pip install --python "${VENV_DIR}/bin/python" --requirement "${REPO_DIR}/requirements.txt" pytest
31
+
32
+ # 5. Activate the virtualenv in interactive shells
33
+ for rc in "${HOME}/.bashrc" "${HOME}/.zshrc"; do
34
+ [ -f "${rc}" ] || continue
35
+ grep -qxF "source ${VENV_DIR}/bin/activate" "${rc}" || {
36
+ printf '\n# Activate the Ownfoil virtualenv\nsource %s/bin/activate\n' "${VENV_DIR}" >> "${rc}"
37
+ }
38
+ done
39
+
40
+ echo "Environment ready: $(${VENV_DIR}/bin/python --version)"
41
+
42
+ # 6. Install the Claude Code CLI.
43
+ if ! command -v claude >/dev/null 2>&1; then
44
+ echo "Installing Claude Code CLI..."
45
+ curl -fsSL https://claude.ai/install.sh | bash
46
+ fi
47
+ # Skip onboarding
48
+ cat > ~/.claude.json << EOF
49
+ {
50
+ "hasCompletedOnboarding": true,
51
+ "theme": "dark",
52
+ "projects": {
53
+ "${REPO_DIR}": {
54
+ "hasTrustDialogAccepted": true
55
+ }
56
+ }
57
+ }
58
+ EOF
@@ -0,0 +1,31 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ # Maintain dependencies for GitHub Actions
9
+ - package-ecosystem: "github-actions"
10
+ directory: "/"
11
+ target-branch: "develop"
12
+ schedule:
13
+ interval: "weekly"
14
+ labels:
15
+ - "type/build"
16
+
17
+ - package-ecosystem: "docker"
18
+ directory: "/"
19
+ target-branch: "develop"
20
+ schedule:
21
+ interval: "weekly"
22
+ labels:
23
+ - "type/build"
24
+
25
+ - package-ecosystem: "pip"
26
+ directory: "/"
27
+ target-branch: "develop"
28
+ schedule:
29
+ interval: "weekly"
30
+ labels:
31
+ - "type/build"
@@ -0,0 +1,21 @@
1
+ changelog:
2
+ categories:
3
+ - title: New Features
4
+ labels:
5
+ - 'type/feat'
6
+ - title: Fixes and Improvements
7
+ labels:
8
+ - 'type/fix'
9
+ - 'type/improv'
10
+ - title: Other Changes
11
+ labels:
12
+ - '*'
13
+ exclude:
14
+ labels:
15
+ - 'type/feat'
16
+ - 'type/fix'
17
+ - 'type/improv'
18
+ - 'type/build'
19
+ - title: Dependencies
20
+ labels:
21
+ - 'type/build'
@@ -0,0 +1,20 @@
1
+ name: Validate Python package build
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - '**'
7
+
8
+ jobs:
9
+ build:
10
+ name: uv build
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v7
15
+
16
+ - name: Install uv
17
+ uses: astral-sh/setup-uv@v7
18
+
19
+ - name: Build package
20
+ run: uv build
@@ -0,0 +1,68 @@
1
+ name: Publish multi arch Docker image
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - '**'
8
+ tags:
9
+ - '*.*.*'
10
+
11
+ jobs:
12
+ push_to_registry:
13
+ name: Push Docker image to Docker Hub
14
+ if: startsWith(github.ref, 'refs/tags/') || (!startsWith(github.ref, 'refs/heads/master') && !startsWith(github.ref, 'refs/heads/dependabot') && !startsWith(github.event.head_commit.message, 'ci') && !startsWith(github.event.head_commit.message, 'doc'))
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ # Get the repository's code
18
+ - name: Checkout
19
+ uses: actions/checkout@v7
20
+ # with:
21
+ # submodules: recursive
22
+
23
+ # https://github.com/docker/setup-qemu-action
24
+ - name: Set up QEMU
25
+ uses: docker/setup-qemu-action@v4
26
+
27
+ # https://github.com/docker/setup-buildx-action
28
+ - name: Set up Docker Buildx
29
+ id: buildx
30
+ uses: docker/setup-buildx-action@v4
31
+
32
+ - name: Log in to Docker Hub
33
+ uses: docker/login-action@v4
34
+ with:
35
+ username: ${{ secrets.DOCKER_USERNAME }}
36
+ password: ${{ secrets.DOCKER_PASSWORD }}
37
+
38
+ - name: Extract metadata (tags, labels) for Docker
39
+ id: meta
40
+ uses: docker/metadata-action@v6
41
+ with:
42
+ images: a1ex4/ownfoil
43
+ flavor: |
44
+ latest=auto
45
+ tags: |
46
+ type=ref,event=tag
47
+ type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
48
+ type=ref,event=branch,enable=${{ github.ref != 'refs/heads/master' }}
49
+ type=semver,pattern={{version}}
50
+ type=semver,pattern={{major}}.{{minor}}
51
+ type=semver,pattern={{major}}
52
+
53
+ - name: Set platforms based on branch or tag
54
+ run: |
55
+ if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
56
+ echo "PLATFORMS=linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6" >> $GITHUB_ENV
57
+ else
58
+ echo "PLATFORMS=linux/amd64,linux/arm64" >> $GITHUB_ENV
59
+ fi
60
+
61
+ - name: Build and push Docker image
62
+ uses: docker/build-push-action@v7
63
+ with:
64
+ context: .
65
+ platforms: ${{ env.PLATFORMS }}
66
+ push: true
67
+ tags: ${{ steps.meta.outputs.tags }}
68
+ labels: ${{ steps.meta.outputs.labels }}
@@ -0,0 +1,32 @@
1
+ name: Build multi arch Docker image on PR
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ jobs:
7
+ push_to_registry:
8
+ name: Build multi arch Docker image on PR
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ # Get the repository's code
12
+ - name: Checkout
13
+ uses: actions/checkout@v7
14
+ # with:
15
+ # submodules: recursive
16
+
17
+ # https://github.com/docker/setup-qemu-action
18
+ - name: Set up QEMU
19
+ uses: docker/setup-qemu-action@v4
20
+
21
+ # https://github.com/docker/setup-buildx-action
22
+ - name: Set up Docker Buildx
23
+ id: buildx
24
+ uses: docker/setup-buildx-action@v4
25
+
26
+ - name: Build Docker images
27
+ uses: docker/build-push-action@v7
28
+ with:
29
+ context: .
30
+ # platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
31
+ platforms: linux/amd64,linux/arm64
32
+ push: false
@@ -0,0 +1,27 @@
1
+ name: Publish Python package to PyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ tags:
7
+ - '*.*.*'
8
+
9
+ jobs:
10
+ build_and_publish:
11
+ name: Build and publish to PyPI
12
+ runs-on: ubuntu-latest
13
+ environment: pypi
14
+ permissions:
15
+ id-token: write
16
+ steps:
17
+ - name: Checkout
18
+ uses: actions/checkout@v7
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v7
22
+
23
+ - name: Build package
24
+ run: uv build
25
+
26
+ - name: Publish to PyPI
27
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,136 @@
1
+ # This is a basic workflow to help you get started with Actions
2
+
3
+ name: Build titledb
4
+
5
+ # Controls when the workflow will run
6
+ on:
7
+ schedule:
8
+ - cron: '0 */4 * * *'
9
+ # Allows you to run this workflow manually from the Actions tab
10
+ workflow_dispatch:
11
+ inputs:
12
+ force:
13
+ description: 'Rebuild even if blawar/titledb has not moved'
14
+ type: boolean
15
+ default: false
16
+
17
+ # A workflow run is made up of one or more jobs that can run sequentially or in parallel
18
+ jobs:
19
+ # This workflow contains a single job called "build"
20
+ build:
21
+ # The type of runner that the job will run on
22
+ runs-on: ubuntu-latest
23
+
24
+ permissions:
25
+ contents: write
26
+ actions: write
27
+
28
+ # Steps represent a sequence of tasks that will be executed as part of the job
29
+ steps:
30
+ # A no-op run must not conclude as a success: nightly.link serves the latest *successful*
31
+ # run's artifact to pre-release Ownfoil versions, so it has to keep pointing at a run that
32
+ # actually uploaded one. Cancelling leaves that resolution untouched.
33
+ - name: Skip when blawar/titledb has not moved
34
+ env:
35
+ GH_TOKEN: ${{ github.token }}
36
+ # This step runs before the checkout, so gh has no local repo to infer from
37
+ GH_REPO: ${{ github.repository }}
38
+ run: |
39
+ set -euo pipefail
40
+ remote=$(git ls-remote https://github.com/blawar/titledb.git HEAD | cut -f1)
41
+ published=$(curl -sfL "https://github.com/${{ github.repository }}/releases/download/titledb/latest" || true)
42
+ echo "upstream=${remote} published=${published:-<none>}"
43
+ echo "titledb_sha=${remote}" >> "$GITHUB_ENV"
44
+ if [ "$remote" = "$published" ] && [ "${{ inputs.force }}" != "true" ]; then
45
+ echo "titledb unchanged, cancelling this run"
46
+ gh run cancel "${{ github.run_id }}"
47
+ sleep 60
48
+ fi
49
+
50
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
51
+ - uses: actions/checkout@v7
52
+ - uses: actions/setup-python@v7
53
+ with:
54
+ python-version: '3.10'
55
+
56
+ # Runs a set of commands using the runners shell
57
+ - name: Setup folders
58
+ run: |
59
+ set -euo pipefail
60
+ sudo apt update && sudo apt install -y libcurl4-openssl-dev libssl-dev
61
+ git clone https://github.com/blawar/nut
62
+ cd nut
63
+ pip install -r requirements.txt
64
+ mkdir build_artefacts titledb
65
+ curl -fsSL --retry 3 "https://github.com/blawar/titledb/archive/${titledb_sha}.tar.gz" \
66
+ | tar -xz --strip-components=1 -C titledb \
67
+ --exclude=ncas.json --exclude=cheats.json
68
+ echo "formatted_date=$(date +"%Y-%m-%d_%H%M%S")" >> $GITHUB_ENV
69
+ # Short hash inside the artefact: consumed by Ownfoil < 2.4 through nightly.link.
70
+ # Explicitly 7 chars, matching what --short gave on the old shallow clone.
71
+ touch "build_artefacts/latest_${titledb_sha:0:7}"
72
+ # Full hash outside the artefact: the release marker, and what the skip check compares.
73
+ # Both are now the sha the tarball was pinned to, so they agree by construction.
74
+ echo "$titledb_sha" > latest
75
+
76
+ - uses: jannekem/run-python-script-action@v1
77
+ name: Generate region titles
78
+ with:
79
+ script: |
80
+ import os, sys
81
+ import json
82
+
83
+ os.chdir('nut')
84
+ sys.path.append(os.getcwd())
85
+ import nut
86
+
87
+ with open('titledb/languages.json') as f:
88
+ languages = json.load(f)
89
+ languages = dict(sorted(languages.items()))
90
+ for region in languages.keys():
91
+ for language in languages[region]:
92
+ print(region, language)
93
+ nut.importRegion(region, language)
94
+ os.rename('titledb/titles.json', f'build_artefacts/titles.{region}.{language}.json')
95
+
96
+ for f in [
97
+ 'cnmts.json',
98
+ 'languages.json',
99
+ 'versions.json',
100
+ 'versions.txt',
101
+ ]:
102
+ os.rename(f'titledb/{f}', f'build_artefacts/{f}')
103
+
104
+
105
+ # Must stay ahead of the compression step: pre-release versions read the uncompressed
106
+ # JSON straight out of this zip.
107
+ - uses: actions/upload-artifact@v7
108
+ with:
109
+ name: titledb
110
+ path: nut/build_artefacts/*
111
+ compression-level: 9
112
+
113
+ - name: Compress artefacts
114
+ run: zstd -12 -T0 --rm -q nut/build_artefacts/*.json nut/build_artefacts/versions.txt
115
+
116
+ - name: Publish to the titledb release
117
+ env:
118
+ GH_TOKEN: ${{ github.token }}
119
+ run: |
120
+ set -euo pipefail
121
+ gh release view titledb >/dev/null 2>&1 || \
122
+ gh release create titledb --prerelease --latest=false --title 'titledb data' \
123
+ --notes 'Rolling titledb dataset consumed by Ownfoil. Assets are replaced in place; `latest` holds the blawar/titledb commit they were built from and is always written last.'
124
+ uploaded=false
125
+ for attempt in 1 2 3; do
126
+ if gh release upload titledb nut/build_artefacts/*.zst --clobber; then
127
+ uploaded=true
128
+ break
129
+ fi
130
+ echo "upload attempt ${attempt} failed, retrying"
131
+ sleep 30
132
+ done
133
+ # Guards the invariant below: never publish the marker over a half-uploaded dataset
134
+ [ "$uploaded" = true ]
135
+ # Written last, alone: a client that sees a new hash knows every asset is already in place
136
+ gh release upload titledb nut/latest --clobber
@@ -0,0 +1,21 @@
1
+ name: 'Close stale issues'
2
+ on:
3
+ workflow_dispatch:
4
+ schedule:
5
+ - cron: '30 1 * * *'
6
+
7
+ jobs:
8
+ stale:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: Close stale issues
12
+ uses: actions/stale@v11
13
+ with:
14
+ days-before-stale: 30
15
+ days-before-close: 7
16
+ stale-issue-label: 'status/stale'
17
+ stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.'
18
+ close-issue-message: 'Closing this issue due to inactivity. Feel free to reopen if necessary.'
19
+ any-of-labels: 'status/waiting-for-info'
20
+ operations-per-run: 200
21
+ debug-only: false
@@ -0,0 +1,37 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ test:
10
+ name: Tests on ${{ matrix.os }}
11
+ if: ${{ !startsWith(github.event.head_commit.message, 'ci') && !startsWith(github.event.head_commit.message, 'doc') }}
12
+ runs-on: ${{ matrix.os }}
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ os: [ubuntu-latest, windows-latest]
17
+
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@v7
21
+
22
+ - name: Set up uv
23
+ uses: astral-sh/setup-uv@v7
24
+ with:
25
+ python-version: '3.14'
26
+ enable-cache: true
27
+ cache-dependency-glob: requirements.txt
28
+
29
+ - name: Create virtualenv
30
+ run: uv venv
31
+
32
+ - name: Install dependencies
33
+ run: uv pip install --requirement requirements.txt pytest
34
+
35
+ - name: Run tests
36
+ run: uv run pytest tests -v
37
+ timeout-minutes: 20
@@ -0,0 +1,27 @@
1
+ __pycache__
2
+ config
3
+ data
4
+
5
+ # capture harness workdir: fixture db, settings and the pseudonym map
6
+ tests/capture/.workdir
7
+
8
+ # venv
9
+ /bin
10
+ /venv
11
+ /include
12
+ /lib
13
+ /lib64
14
+ /pyvenv.cfg
15
+ /share
16
+ /Scripts
17
+
18
+ # pyenv
19
+ .python-version
20
+
21
+ # uv build/tool artifacts
22
+ /dist
23
+ ownfoil.bat
24
+ uv.lock
25
+
26
+ .claude
27
+ CLAUDE.local.md
@@ -0,0 +1,25 @@
1
+ FROM python:3.14-alpine
2
+
3
+ # Install platform-specific build dependencies
4
+ ARG TARGETPLATFORM
5
+ RUN apk update && apk add --no-cache bash sudo \
6
+ && if [ "$TARGETPLATFORM" = "linux/arm/v6" ] || [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
7
+ apk add --no-cache build-base gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev; \
8
+ fi
9
+
10
+ COPY requirements.txt /tmp/
11
+ RUN pip install --no-cache-dir --requirement /tmp/requirements.txt && rm /tmp/requirements.txt
12
+ RUN if [ "$TARGETPLATFORM" = "linux/arm/v6" ] || [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
13
+ apk del build-base gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev; \
14
+ fi
15
+
16
+ RUN mkdir /app
17
+ COPY ./app /app
18
+ COPY ./docker/run.sh /app/run.sh
19
+
20
+ RUN mkdir -p /app/data
21
+
22
+ WORKDIR /app
23
+
24
+ ENTRYPOINT [ "/app/run.sh" ]
25
+