collapsarr 0.1.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 (165) hide show
  1. collapsarr-0.1.3/.dockerignore +56 -0
  2. collapsarr-0.1.3/.env.example +16 -0
  3. collapsarr-0.1.3/.gitattributes +2 -0
  4. collapsarr-0.1.3/.github/brand/icon-512.png +0 -0
  5. collapsarr-0.1.3/.github/brand/social-preview.png +0 -0
  6. collapsarr-0.1.3/.github/brand/social-preview.svg +21 -0
  7. collapsarr-0.1.3/.github/workflows/release.yml +219 -0
  8. collapsarr-0.1.3/.gitignore +61 -0
  9. collapsarr-0.1.3/Dockerfile +85 -0
  10. collapsarr-0.1.3/LICENSE +674 -0
  11. collapsarr-0.1.3/PKG-INFO +218 -0
  12. collapsarr-0.1.3/README.md +192 -0
  13. collapsarr-0.1.3/collapsarr/__init__.py +15 -0
  14. collapsarr-0.1.3/collapsarr/__main__.py +25 -0
  15. collapsarr-0.1.3/collapsarr/arr/__init__.py +76 -0
  16. collapsarr-0.1.3/collapsarr/arr/client.py +71 -0
  17. collapsarr-0.1.3/collapsarr/arr/files.py +211 -0
  18. collapsarr-0.1.3/collapsarr/arr/models.py +147 -0
  19. collapsarr-0.1.3/collapsarr/arr/routes.py +290 -0
  20. collapsarr-0.1.3/collapsarr/arr/service.py +222 -0
  21. collapsarr-0.1.3/collapsarr/arr/webhooks.py +200 -0
  22. collapsarr-0.1.3/collapsarr/auth.py +83 -0
  23. collapsarr-0.1.3/collapsarr/config.py +95 -0
  24. collapsarr-0.1.3/collapsarr/database.py +82 -0
  25. collapsarr-0.1.3/collapsarr/downmix/__init__.py +54 -0
  26. collapsarr-0.1.3/collapsarr/downmix/apply.py +241 -0
  27. collapsarr-0.1.3/collapsarr/downmix/pipeline.py +235 -0
  28. collapsarr-0.1.3/collapsarr/downmix/probe.py +310 -0
  29. collapsarr-0.1.3/collapsarr/downmix/remux.py +284 -0
  30. collapsarr-0.1.3/collapsarr/downmix/targets.py +145 -0
  31. collapsarr-0.1.3/collapsarr/frontend.py +61 -0
  32. collapsarr-0.1.3/collapsarr/health.py +111 -0
  33. collapsarr-0.1.3/collapsarr/jobs/__init__.py +49 -0
  34. collapsarr-0.1.3/collapsarr/jobs/failure_notify.py +145 -0
  35. collapsarr-0.1.3/collapsarr/jobs/history.py +153 -0
  36. collapsarr-0.1.3/collapsarr/jobs/models.py +90 -0
  37. collapsarr-0.1.3/collapsarr/jobs/queue.py +382 -0
  38. collapsarr-0.1.3/collapsarr/jobs/routes.py +193 -0
  39. collapsarr-0.1.3/collapsarr/jobs/scheduler.py +402 -0
  40. collapsarr-0.1.3/collapsarr/main.py +212 -0
  41. collapsarr-0.1.3/collapsarr/media/__init__.py +38 -0
  42. collapsarr-0.1.3/collapsarr/media/models.py +132 -0
  43. collapsarr-0.1.3/collapsarr/media/routes.py +88 -0
  44. collapsarr-0.1.3/collapsarr/media/service.py +241 -0
  45. collapsarr-0.1.3/collapsarr/notify/__init__.py +37 -0
  46. collapsarr-0.1.3/collapsarr/notify/dispatch.py +162 -0
  47. collapsarr-0.1.3/collapsarr/notify/models.py +63 -0
  48. collapsarr-0.1.3/collapsarr/notify/routes.py +105 -0
  49. collapsarr-0.1.3/collapsarr/notify/service.py +80 -0
  50. collapsarr-0.1.3/collapsarr/settings/__init__.py +29 -0
  51. collapsarr-0.1.3/collapsarr/settings/models.py +107 -0
  52. collapsarr-0.1.3/collapsarr/settings/routes.py +157 -0
  53. collapsarr-0.1.3/collapsarr/settings/service.py +158 -0
  54. collapsarr-0.1.3/docker-entrypoint.sh +24 -0
  55. collapsarr-0.1.3/frontend/README.md +48 -0
  56. collapsarr-0.1.3/frontend/dist/apple-touch-icon.png +0 -0
  57. collapsarr-0.1.3/frontend/dist/assets/index-Dfozi1oM.js +68 -0
  58. collapsarr-0.1.3/frontend/dist/assets/index-J2qCuzXj.css +1 -0
  59. collapsarr-0.1.3/frontend/dist/favicon-32.png +0 -0
  60. collapsarr-0.1.3/frontend/dist/favicon.svg +10 -0
  61. collapsarr-0.1.3/frontend/dist/index.html +18 -0
  62. collapsarr-0.1.3/frontend/eslint.config.js +28 -0
  63. collapsarr-0.1.3/frontend/index.html +17 -0
  64. collapsarr-0.1.3/frontend/package-lock.json +4700 -0
  65. collapsarr-0.1.3/frontend/package.json +37 -0
  66. collapsarr-0.1.3/frontend/public/apple-touch-icon.png +0 -0
  67. collapsarr-0.1.3/frontend/public/favicon-32.png +0 -0
  68. collapsarr-0.1.3/frontend/public/favicon.svg +10 -0
  69. collapsarr-0.1.3/frontend/src/api/activity.ts +52 -0
  70. collapsarr-0.1.3/frontend/src/api/client.ts +91 -0
  71. collapsarr-0.1.3/frontend/src/api/health.ts +16 -0
  72. collapsarr-0.1.3/frontend/src/api/instances.ts +106 -0
  73. collapsarr-0.1.3/frontend/src/api/notifiers.ts +30 -0
  74. collapsarr-0.1.3/frontend/src/api/settings.ts +28 -0
  75. collapsarr-0.1.3/frontend/src/api/wanted.ts +19 -0
  76. collapsarr-0.1.3/frontend/src/components/AppShell.tsx +24 -0
  77. collapsarr-0.1.3/frontend/src/components/HealthBanner.tsx +49 -0
  78. collapsarr-0.1.3/frontend/src/components/PlaceholderView.tsx +42 -0
  79. collapsarr-0.1.3/frontend/src/components/Sidebar.tsx +37 -0
  80. collapsarr-0.1.3/frontend/src/components/icons.tsx +80 -0
  81. collapsarr-0.1.3/frontend/src/components/settings/ConnectSection.tsx +162 -0
  82. collapsarr-0.1.3/frontend/src/components/settings/GeneralSection.tsx +275 -0
  83. collapsarr-0.1.3/frontend/src/components/settings/InstancesSection.tsx +369 -0
  84. collapsarr-0.1.3/frontend/src/components/settings/PathMappingsPanel.tsx +294 -0
  85. collapsarr-0.1.3/frontend/src/components/settings/TargetsSection.tsx +148 -0
  86. collapsarr-0.1.3/frontend/src/main.tsx +18 -0
  87. collapsarr-0.1.3/frontend/src/pages/ActivityPage.tsx +195 -0
  88. collapsarr-0.1.3/frontend/src/pages/FileDetailPage.tsx +370 -0
  89. collapsarr-0.1.3/frontend/src/pages/SettingsPage.tsx +30 -0
  90. collapsarr-0.1.3/frontend/src/pages/WantedPage.tsx +127 -0
  91. collapsarr-0.1.3/frontend/src/routes/nav.tsx +25 -0
  92. collapsarr-0.1.3/frontend/src/routes/router.tsx +21 -0
  93. collapsarr-0.1.3/frontend/src/styles/global.css +915 -0
  94. collapsarr-0.1.3/frontend/src/styles/theme.css +63 -0
  95. collapsarr-0.1.3/frontend/src/test/activityPage.test.tsx +147 -0
  96. collapsarr-0.1.3/frontend/src/test/apiClient.test.ts +78 -0
  97. collapsarr-0.1.3/frontend/src/test/appShell.test.tsx +89 -0
  98. collapsarr-0.1.3/frontend/src/test/fileDetailPage.test.tsx +238 -0
  99. collapsarr-0.1.3/frontend/src/test/healthBanner.test.tsx +49 -0
  100. collapsarr-0.1.3/frontend/src/test/settingsConnect.test.tsx +109 -0
  101. collapsarr-0.1.3/frontend/src/test/settingsGeneral.test.tsx +145 -0
  102. collapsarr-0.1.3/frontend/src/test/settingsInstances.test.tsx +223 -0
  103. collapsarr-0.1.3/frontend/src/test/settingsPage.test.tsx +67 -0
  104. collapsarr-0.1.3/frontend/src/test/settingsTargets.test.tsx +124 -0
  105. collapsarr-0.1.3/frontend/src/test/setup.ts +49 -0
  106. collapsarr-0.1.3/frontend/src/test/wantedPage.test.tsx +97 -0
  107. collapsarr-0.1.3/frontend/src/types/activity.ts +58 -0
  108. collapsarr-0.1.3/frontend/src/types/health.ts +17 -0
  109. collapsarr-0.1.3/frontend/src/types/instances.ts +71 -0
  110. collapsarr-0.1.3/frontend/src/types/notifiers.ts +29 -0
  111. collapsarr-0.1.3/frontend/src/types/settings.ts +41 -0
  112. collapsarr-0.1.3/frontend/src/types/wanted.ts +25 -0
  113. collapsarr-0.1.3/frontend/src/vite-env.d.ts +1 -0
  114. collapsarr-0.1.3/frontend/tsconfig.json +25 -0
  115. collapsarr-0.1.3/frontend/vite.config.ts +24 -0
  116. collapsarr-0.1.3/hatch_build.py +33 -0
  117. collapsarr-0.1.3/pyproject.toml +93 -0
  118. collapsarr-0.1.3/tests/__init__.py +0 -0
  119. collapsarr-0.1.3/tests/conftest.py +48 -0
  120. collapsarr-0.1.3/tests/fixtures/arr/radarr_movie_list.json +76 -0
  121. collapsarr-0.1.3/tests/fixtures/arr/radarr_status_ok.json +31 -0
  122. collapsarr-0.1.3/tests/fixtures/arr/radarr_webhook_on_import.json +39 -0
  123. collapsarr-0.1.3/tests/fixtures/arr/radarr_webhook_on_upgrade.json +39 -0
  124. collapsarr-0.1.3/tests/fixtures/arr/sonarr_episodefiles_series1.json +42 -0
  125. collapsarr-0.1.3/tests/fixtures/arr/sonarr_series_list.json +22 -0
  126. collapsarr-0.1.3/tests/fixtures/arr/sonarr_status_ok.json +31 -0
  127. collapsarr-0.1.3/tests/fixtures/arr/sonarr_webhook_on_import.json +43 -0
  128. collapsarr-0.1.3/tests/fixtures/arr/sonarr_webhook_on_upgrade.json +43 -0
  129. collapsarr-0.1.3/tests/fixtures/arr/sonarr_webhook_test.json +13 -0
  130. collapsarr-0.1.3/tests/fixtures/arr/unauthorized.json +3 -0
  131. collapsarr-0.1.3/tests/fixtures/downmix/multi_lang.mkv +0 -0
  132. collapsarr-0.1.3/tests/fixtures/downmix/stereo_eng.mkv +0 -0
  133. collapsarr-0.1.3/tests/fixtures/downmix/surround_51.mkv +0 -0
  134. collapsarr-0.1.3/tests/fixtures/downmix/surround_71.mkv +0 -0
  135. collapsarr-0.1.3/tests/fixtures/downmix/untagged_language.mkv +0 -0
  136. collapsarr-0.1.3/tests/fixtures/downmix/video_audio_subs.mkv +0 -0
  137. collapsarr-0.1.3/tests/test_arr_client.py +135 -0
  138. collapsarr-0.1.3/tests/test_arr_files.py +200 -0
  139. collapsarr-0.1.3/tests/test_arr_models.py +248 -0
  140. collapsarr-0.1.3/tests/test_arr_routes.py +298 -0
  141. collapsarr-0.1.3/tests/test_arr_service.py +302 -0
  142. collapsarr-0.1.3/tests/test_arr_webhooks.py +433 -0
  143. collapsarr-0.1.3/tests/test_auth.py +53 -0
  144. collapsarr-0.1.3/tests/test_config.py +51 -0
  145. collapsarr-0.1.3/tests/test_downmix_apply.py +324 -0
  146. collapsarr-0.1.3/tests/test_downmix_pipeline.py +388 -0
  147. collapsarr-0.1.3/tests/test_downmix_probe.py +364 -0
  148. collapsarr-0.1.3/tests/test_downmix_remux.py +439 -0
  149. collapsarr-0.1.3/tests/test_downmix_targets.py +176 -0
  150. collapsarr-0.1.3/tests/test_frontend.py +74 -0
  151. collapsarr-0.1.3/tests/test_health.py +216 -0
  152. collapsarr-0.1.3/tests/test_health_check.py +138 -0
  153. collapsarr-0.1.3/tests/test_jobs_failure_notify.py +251 -0
  154. collapsarr-0.1.3/tests/test_jobs_history.py +413 -0
  155. collapsarr-0.1.3/tests/test_jobs_queue.py +328 -0
  156. collapsarr-0.1.3/tests/test_jobs_routes.py +302 -0
  157. collapsarr-0.1.3/tests/test_jobs_scheduler.py +608 -0
  158. collapsarr-0.1.3/tests/test_main.py +45 -0
  159. collapsarr-0.1.3/tests/test_media_service.py +411 -0
  160. collapsarr-0.1.3/tests/test_notify_dispatch.py +266 -0
  161. collapsarr-0.1.3/tests/test_notify_routes.py +126 -0
  162. collapsarr-0.1.3/tests/test_notify_service.py +148 -0
  163. collapsarr-0.1.3/tests/test_settings_routes.py +128 -0
  164. collapsarr-0.1.3/tests/test_settings_service.py +236 -0
  165. collapsarr-0.1.3/tests/test_wanted_routes.py +126 -0
@@ -0,0 +1,56 @@
1
+ # Keep the build context small and reproducible. The frontend bundle and Python
2
+ # wheel are rebuilt inside the image, so host-built artifacts are excluded.
3
+
4
+ # VCS / tooling
5
+ .git
6
+ .gitignore
7
+ .gitattributes
8
+ .github
9
+ .claude
10
+ .agents
11
+
12
+ # Python caches & local envs
13
+ **/__pycache__
14
+ *.py[cod]
15
+ *.egg-info
16
+ .venv
17
+ venv
18
+ env
19
+ build
20
+ dist
21
+ .pytest_cache
22
+ .mypy_cache
23
+ .ruff_cache
24
+ .coverage
25
+ htmlcov
26
+
27
+ # Node / frontend (deps + host build output rebuilt in-image)
28
+ node_modules
29
+ frontend/node_modules
30
+ frontend/dist
31
+
32
+ # Runtime data & secrets
33
+ .env
34
+ .env.*
35
+ !.env.example
36
+ /config
37
+ *.db
38
+ *.sqlite
39
+ *.sqlite3
40
+ *.sqlite3-journal
41
+ logs
42
+ *.log
43
+
44
+ # Test media / scratch
45
+ /samples
46
+ /media
47
+
48
+ # Docs & editor/OS junk
49
+ docs
50
+ *.md
51
+ !README.md
52
+ .idea
53
+ .vscode
54
+ *.swp
55
+ .DS_Store
56
+ Thumbs.db
@@ -0,0 +1,16 @@
1
+ # Collapsarr configuration.
2
+ # Copy to `.env` and adjust as needed. All variables are optional — the values
3
+ # below are the built-in defaults. Environment variables take precedence.
4
+
5
+ # Filesystem path to the SQLite database file.
6
+ COLLAPSARR_DATABASE_PATH=/config/collapsarr.db
7
+
8
+ # Full SQLAlchemy database URL. When set, overrides COLLAPSARR_DATABASE_PATH.
9
+ # COLLAPSARR_DATABASE_URL=sqlite:////config/collapsarr.db
10
+
11
+ # API server bind address and port.
12
+ COLLAPSARR_HOST=0.0.0.0
13
+ COLLAPSARR_PORT=8282
14
+
15
+ # Log level (DEBUG, INFO, WARNING, ERROR).
16
+ COLLAPSARR_LOG_LEVEL=INFO
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -0,0 +1,21 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1280" height="640" viewBox="0 0 1280 640">
2
+ <rect x="0" y="0" width="1280" height="640" fill="#18151f"/>
3
+
4
+ <!-- glyph: Outer Survivors, scaled 1.3x -->
5
+ <g fill="#8B5CF6">
6
+ <rect x="176.0" y="203" width="44.2" height="130" rx="7.8"/>
7
+ <rect x="257.9" y="138" width="44.2" height="195" rx="7.8"/>
8
+ <rect x="339.8" y="203" width="44.2" height="130" rx="7.8"/>
9
+ <rect x="85.0" y="203" width="62.4" height="299" rx="13"/>
10
+ <rect x="412.6" y="203" width="62.4" height="299" rx="13"/>
11
+ </g>
12
+
13
+ <text x="560" y="240" font-family="Menlo, ui-monospace, monospace" font-size="20"
14
+ letter-spacing="3" fill="#8B5CF6">AUDIO DOWNMIX FOR SONARR &amp; RADARR</text>
15
+
16
+ <text x="557" y="345" font-family="Futura, Avenir Next, sans-serif" font-weight="bold"
17
+ font-size="104" fill="#f3f1fa">Collapsarr</text>
18
+
19
+ <text x="560" y="392" font-family="Helvetica Neue, Helvetica, sans-serif" font-size="30"
20
+ fill="#9791ae">Enjoy your media on any speaker setup.</text>
21
+ </svg>
@@ -0,0 +1,219 @@
1
+ # Tag-triggered release pipeline (COL-41).
2
+ #
3
+ # Fires on a semver tag (v*.*.*) that lives on `main`, then:
4
+ # 1. guard — refuse to release a tag that is not contained in main.
5
+ # 2. test-* — backend (pytest/ruff/mypy) + frontend (vitest/tsc/eslint);
6
+ # any failure fails the pipeline before anything is published.
7
+ # 3. build-wheel — build the frontend FIRST, then `python -m build` so the
8
+ # wheel bundles frontend/dist -> collapsarr/static (COL-40).
9
+ # 4. docker — multi-arch (amd64+arm64) image build & push to Docker Hub.
10
+ # 5. pypi-publish — publish the wheel/sdist to PyPI via OIDC Trusted Publishing.
11
+ # 6. github-release — cut a GitHub Release with the built artifacts attached.
12
+ #
13
+ # Credentials come from GitHub Actions secrets / OIDC that the HITL epic (COL-9)
14
+ # populates; this workflow only wires them up. It is intentionally reviewable and
15
+ # dry-runnable before those secrets exist — nothing here is evaluated until a real
16
+ # tag is pushed.
17
+ #
18
+ # Named secrets / tokens this workflow reads:
19
+ # - Docker Hub push : secrets.DOCKERHUB_USERNAME + secrets.DOCKERHUB_TOKEN
20
+ # - GitHub Release : secrets.GITHUB_TOKEN (auto-provided by Actions)
21
+ # - PyPI push : OIDC Trusted Publishing (no secret; requires a PyPI
22
+ # "pending publisher" configured for this repo/workflow).
23
+
24
+ name: release
25
+
26
+ on:
27
+ push:
28
+ tags:
29
+ - 'v*.*.*'
30
+
31
+ permissions:
32
+ contents: read
33
+
34
+ env:
35
+ IMAGE_NAME: odxnsson/collapsarr
36
+ PYTHON_VERSION: '3.12'
37
+ NODE_VERSION: '20'
38
+
39
+ jobs:
40
+ # ---- Guard: enforce the "tag pushed to main" acceptance criterion -----------
41
+ guard:
42
+ name: Verify tag is on main
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+ with:
47
+ fetch-depth: 0
48
+ - name: Ensure the tagged commit is contained in main
49
+ run: |
50
+ git fetch origin main
51
+ if git merge-base --is-ancestor "${GITHUB_SHA}" FETCH_HEAD; then
52
+ echo "Tag ${GITHUB_REF_NAME} (${GITHUB_SHA}) is on main — proceeding."
53
+ else
54
+ echo "::error::Tag ${GITHUB_REF_NAME} is not on main; refusing to release."
55
+ exit 1
56
+ fi
57
+
58
+ # ---- Test suite (must pass before any build/publish) ------------------------
59
+ test-backend:
60
+ name: Backend tests (pytest / ruff / mypy)
61
+ needs: guard
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - uses: actions/checkout@v4
65
+ - name: Install FFmpeg
66
+ run: sudo apt-get update && sudo apt-get install -y ffmpeg
67
+ - uses: actions/setup-python@v5
68
+ with:
69
+ python-version: ${{ env.PYTHON_VERSION }}
70
+ - name: Install backend with dev extras
71
+ run: pip install -e ".[dev]"
72
+ - name: Ruff
73
+ run: ruff check collapsarr tests
74
+ - name: Mypy
75
+ run: mypy
76
+ - name: Pytest
77
+ run: pytest
78
+
79
+ test-frontend:
80
+ name: Frontend tests (vitest / tsc / eslint)
81
+ needs: guard
82
+ runs-on: ubuntu-latest
83
+ defaults:
84
+ run:
85
+ working-directory: frontend
86
+ steps:
87
+ - uses: actions/checkout@v4
88
+ - uses: actions/setup-node@v4
89
+ with:
90
+ node-version: ${{ env.NODE_VERSION }}
91
+ cache: npm
92
+ cache-dependency-path: frontend/package-lock.json
93
+ - name: Install dependencies
94
+ run: npm ci
95
+ - name: Lint
96
+ run: npm run lint
97
+ - name: Typecheck
98
+ run: npm run typecheck
99
+ - name: Test
100
+ run: npm run test
101
+
102
+ # ---- Build the wheel + sdist ------------------------------------------------
103
+ # Order matters: the frontend is built FIRST so frontend/dist exists when
104
+ # `python -m build` runs, otherwise the wheel ships without the UI (COL-40).
105
+ build-wheel:
106
+ name: Build wheel + sdist
107
+ needs: [test-backend, test-frontend]
108
+ runs-on: ubuntu-latest
109
+ steps:
110
+ - uses: actions/checkout@v4
111
+ - uses: actions/setup-node@v4
112
+ with:
113
+ node-version: ${{ env.NODE_VERSION }}
114
+ cache: npm
115
+ cache-dependency-path: frontend/package-lock.json
116
+ - name: Build frontend (produces frontend/dist)
117
+ working-directory: frontend
118
+ run: |
119
+ npm ci
120
+ npm run build
121
+ - uses: actions/setup-python@v5
122
+ with:
123
+ python-version: ${{ env.PYTHON_VERSION }}
124
+ - name: Stamp package version from the git tag
125
+ run: |
126
+ VERSION="${GITHUB_REF_NAME#v}"
127
+ echo "Releasing version ${VERSION}"
128
+ sed -i "s/^version = \".*\"/version = \"${VERSION}\"/" pyproject.toml
129
+ - name: Build distributions
130
+ run: |
131
+ python -m pip install --upgrade build
132
+ python -m build
133
+ - name: Verify the frontend was bundled into the wheel
134
+ run: |
135
+ python - <<'PY'
136
+ import glob, zipfile, sys
137
+ wheels = glob.glob("dist/*.whl")
138
+ if not wheels:
139
+ sys.exit("no wheel produced")
140
+ names = zipfile.ZipFile(wheels[0]).namelist()
141
+ if not any(n.startswith("collapsarr/static/") for n in names):
142
+ sys.exit("frontend assets missing from wheel (collapsarr/static/ not found)")
143
+ print("OK: frontend bundled into", wheels[0])
144
+ PY
145
+ - name: Upload distributions
146
+ uses: actions/upload-artifact@v4
147
+ with:
148
+ name: dist
149
+ path: dist/
150
+ if-no-files-found: error
151
+
152
+ # ---- Multi-arch Docker image build & push -----------------------------------
153
+ docker:
154
+ name: Build & push multi-arch image
155
+ needs: [test-backend, test-frontend]
156
+ runs-on: ubuntu-latest
157
+ steps:
158
+ - uses: actions/checkout@v4
159
+ - name: Derive image version from tag
160
+ id: meta
161
+ run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
162
+ - name: Set up QEMU
163
+ uses: docker/setup-qemu-action@v3
164
+ - name: Set up Buildx
165
+ uses: docker/setup-buildx-action@v3
166
+ - name: Log in to Docker Hub
167
+ uses: docker/login-action@v3
168
+ with:
169
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
170
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
171
+ - name: Build and push
172
+ uses: docker/build-push-action@v6
173
+ with:
174
+ context: .
175
+ platforms: linux/amd64,linux/arm64
176
+ push: true
177
+ tags: |
178
+ ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
179
+ ${{ env.IMAGE_NAME }}:latest
180
+ provenance: false
181
+
182
+ # ---- Publish to PyPI (OIDC Trusted Publishing — no API token needed) --------
183
+ pypi-publish:
184
+ name: Publish to PyPI
185
+ needs: build-wheel
186
+ runs-on: ubuntu-latest
187
+ permissions:
188
+ id-token: write
189
+ steps:
190
+ - name: Download distributions
191
+ uses: actions/download-artifact@v4
192
+ with:
193
+ name: dist
194
+ path: dist/
195
+ - name: Publish to PyPI
196
+ uses: pypa/gh-action-pypi-publish@release/v1
197
+ with:
198
+ packages-dir: dist/
199
+
200
+ # ---- Cut the GitHub Release -------------------------------------------------
201
+ github-release:
202
+ name: Create GitHub Release
203
+ needs: [build-wheel, docker, pypi-publish]
204
+ runs-on: ubuntu-latest
205
+ permissions:
206
+ contents: write
207
+ steps:
208
+ - name: Download distributions
209
+ uses: actions/download-artifact@v4
210
+ with:
211
+ name: dist
212
+ path: dist/
213
+ - name: Create GitHub Release
214
+ uses: softprops/action-gh-release@v2
215
+ with:
216
+ generate_release_notes: true
217
+ files: dist/*
218
+ env:
219
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,61 @@
1
+ # ---- Secrets & local config (NEVER commit these) ----
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+ *.local
6
+ docker-compose.override.yml
7
+
8
+ # ---- App runtime data (arr apps store a SQLite DB + config) ----
9
+ /config/
10
+ *.db
11
+ *.sqlite
12
+ *.sqlite3
13
+ *.sqlite3-journal
14
+ logs/
15
+ *.log
16
+
17
+ # ---- Test media / FFmpeg scratch output ----
18
+ # keep small committed fixtures under tests/fixtures/, ignore the rest
19
+ /samples/
20
+ /media/
21
+ *.mkv
22
+ *.mp4
23
+ *.srt
24
+ !tests/fixtures/**
25
+
26
+ # ---- Python ----
27
+ __pycache__/
28
+ *.py[cod]
29
+ *.egg-info/
30
+ .eggs/
31
+ .venv/
32
+ venv/
33
+ env/
34
+ build/
35
+ dist/
36
+ .pytest_cache/
37
+ .mypy_cache/
38
+ .ruff_cache/
39
+ .coverage
40
+ htmlcov/
41
+
42
+ # ---- Node / frontend ----
43
+ node_modules/
44
+ .npm
45
+ npm-debug.log*
46
+ yarn-error.log*
47
+ .pnp.*
48
+ .vite/
49
+ coverage/
50
+
51
+ # ---- Editors / OS junk ----
52
+ .DS_Store
53
+ Thumbs.db
54
+ .idea/
55
+ .vscode/
56
+ *.swp
57
+
58
+ # ---- Agents ----
59
+ .agents/
60
+ .claude/
61
+ docs/
@@ -0,0 +1,85 @@
1
+ # Collapsarr container image (COL-39).
2
+ #
3
+ # Multi-stage, multi-arch (linux/amd64 + linux/arm64) build:
4
+ # 1. `frontend` — Node stage that builds the Vite/React UI into /frontend/dist.
5
+ # 2. `builder` — Python stage that builds the collapsarr wheel via hatchling,
6
+ # bundling the frontend stage's output into it (COL-40).
7
+ # 3. final — python:3.12-slim + FFmpeg that installs the wheel (UI
8
+ # included) and runs as a PUID/PGID-adjustable user
9
+ # (linuxserver.io convention).
10
+ #
11
+ # Build for one arch locally:
12
+ # docker build -t collapsarr:dev .
13
+ # Build/push multi-arch via buildx:
14
+ # docker buildx build --platform linux/amd64,linux/arm64 -t <repo>/collapsarr:tag --push .
15
+
16
+ # ---- Stage 1: build the frontend ------------------------------------------------
17
+ FROM node:20-slim AS frontend
18
+ WORKDIR /frontend
19
+
20
+ # Install deps against the lockfile first so this layer caches across source edits.
21
+ COPY frontend/package.json frontend/package-lock.json ./
22
+ RUN npm ci
23
+
24
+ COPY frontend/ ./
25
+ RUN npm run build
26
+
27
+ # ---- Stage 2: build the Python wheel -------------------------------------------
28
+ FROM python:3.12-slim AS builder
29
+ WORKDIR /build
30
+
31
+ RUN pip install --no-cache-dir build
32
+
33
+ # pyproject reads README.md (readme) and LICENSE (license-files) at build time.
34
+ # hatch_build.py is the custom build hook that bundles frontend/dist into the
35
+ # wheel as collapsarr/static (COL-40) — it requires frontend/dist to exist,
36
+ # hence copying the frontend stage's output in before building.
37
+ COPY pyproject.toml README.md LICENSE hatch_build.py ./
38
+ COPY collapsarr/ ./collapsarr/
39
+ COPY --from=frontend /frontend/dist ./frontend/dist
40
+
41
+ RUN python -m build --wheel --outdir /dist
42
+
43
+ # ---- Stage 3: runtime image ----------------------------------------------------
44
+ FROM python:3.12-slim
45
+
46
+ # FFmpeg is the core dependency (downmix pipeline); gosu drops privileges to the
47
+ # PUID/PGID user at start-up. --no-install-recommends + apt-list cleanup keep the
48
+ # layer small.
49
+ RUN apt-get update \
50
+ && apt-get install -y --no-install-recommends ffmpeg gosu \
51
+ && rm -rf /var/lib/apt/lists/*
52
+
53
+ # Non-root run user (linuxserver.io "abc"); the entrypoint remaps it to the
54
+ # requested PUID/PGID at container start.
55
+ RUN groupadd -g 1000 abc \
56
+ && useradd -o -m -u 1000 -g abc -d /config -s /usr/sbin/nologin abc
57
+
58
+ # Install the app from the wheel built in the previous stage. The wheel already
59
+ # bundles the built UI as collapsarr/static (COL-40) and FastAPI serves it
60
+ # directly from there — no separate copy of the frontend build needed here.
61
+ COPY --from=builder /dist/*.whl /tmp/
62
+ RUN pip install --no-cache-dir /tmp/*.whl && rm -rf /tmp/*.whl
63
+
64
+ COPY docker-entrypoint.sh /docker-entrypoint.sh
65
+ RUN chmod +x /docker-entrypoint.sh
66
+
67
+ # Runtime configuration. COLLAPSARR_* mirror the app defaults (collapsarr/config.py);
68
+ # PUID/PGID are consumed by the entrypoint.
69
+ ENV COLLAPSARR_HOST=0.0.0.0 \
70
+ COLLAPSARR_PORT=8282 \
71
+ COLLAPSARR_DATABASE_PATH=/config/collapsarr.db \
72
+ PUID=1000 \
73
+ PGID=1000
74
+
75
+ # Persistent data (SQLite DB + config) lives here.
76
+ VOLUME /config
77
+
78
+ EXPOSE 8282
79
+
80
+ # Liveness probe using the stdlib so no extra packages are needed.
81
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
82
+ CMD python -c "import sys,urllib.request; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8282/health').status==200 else 1)"
83
+
84
+ ENTRYPOINT ["/docker-entrypoint.sh"]
85
+ CMD ["collapsarr"]