From cffcf06621a66298361038b9a5b711dccfd73b10 Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Sat, 1 Aug 2026 02:30:16 +0800 Subject: [PATCH] Add PodFetch app --- apps/podfetch/README.md | 34 +++++ apps/podfetch/README_en.md | 3 + apps/podfetch/data.yml | 31 ++++ apps/podfetch/latest/.env.sample | 11 ++ apps/podfetch/latest/data.yml | 170 ++++++++++++++++++++++ apps/podfetch/latest/docker-compose.yml | 27 ++++ apps/podfetch/latest/scripts/init.sh | 64 ++++++++ apps/podfetch/latest/scripts/uninstall.sh | 5 + apps/podfetch/latest/scripts/upgrade.sh | 5 + apps/podfetch/logo-LICENSE.txt | 9 ++ apps/podfetch/logo.png | Bin 0 -> 1564 bytes apps/podfetch/v5.2.2/.env.sample | 11 ++ apps/podfetch/v5.2.2/data.yml | 170 ++++++++++++++++++++++ apps/podfetch/v5.2.2/docker-compose.yml | 27 ++++ apps/podfetch/v5.2.2/scripts/init.sh | 64 ++++++++ apps/podfetch/v5.2.2/scripts/uninstall.sh | 5 + apps/podfetch/v5.2.2/scripts/upgrade.sh | 5 + 17 files changed, 641 insertions(+) create mode 100644 apps/podfetch/README.md create mode 100644 apps/podfetch/README_en.md create mode 100644 apps/podfetch/data.yml create mode 100644 apps/podfetch/latest/.env.sample create mode 100644 apps/podfetch/latest/data.yml create mode 100644 apps/podfetch/latest/docker-compose.yml create mode 100755 apps/podfetch/latest/scripts/init.sh create mode 100755 apps/podfetch/latest/scripts/uninstall.sh create mode 100755 apps/podfetch/latest/scripts/upgrade.sh create mode 100644 apps/podfetch/logo-LICENSE.txt create mode 100644 apps/podfetch/logo.png create mode 100644 apps/podfetch/v5.2.2/.env.sample create mode 100644 apps/podfetch/v5.2.2/data.yml create mode 100644 apps/podfetch/v5.2.2/docker-compose.yml create mode 100755 apps/podfetch/v5.2.2/scripts/init.sh create mode 100755 apps/podfetch/v5.2.2/scripts/uninstall.sh create mode 100755 apps/podfetch/v5.2.2/scripts/upgrade.sh diff --git a/apps/podfetch/README.md b/apps/podfetch/README.md new file mode 100644 index 000000000..37c6eb912 --- /dev/null +++ b/apps/podfetch/README.md @@ -0,0 +1,34 @@ +# PodFetch + +## 产品介绍 + +PodFetch 是一个自托管的播客管理器和下载器,提供 Web 界面、全文检索以及多种移动端和订阅阅读器集成。 + +## 主要功能 + +- 订阅、刷新和下载公开播客源。 +- 使用 SQLite 保存订阅、用户和播放状态。 +- 将播客媒体保存到独立目录,便于备份或迁移。 +- 支持基本认证、OIDC、GPodder 和 Audiobookshelf 兼容接口。 + +## 访问说明 + +- 默认启用基本认证,请使用安装表单中的管理员用户名和密码登录。 +- `播客目录` 和 `数据库目录` 都是持久化边界,卸载应用时不会自动删除。 +- 如使用反向代理,请启用 WebSocket 并转发标准的 `X-Forwarded-*` 头。 + +## Introduction + +PodFetch is a self-hosted podcast manager and downloader with a web interface, full-text search, and integrations for mobile apps and feed readers. + +## Features + +- Subscribe to, refresh, and download public podcast feeds. +- Store subscriptions, users, and playback state in SQLite. +- Keep downloaded media in a separate directory for backup and migration. +- Supports Basic authentication, OIDC, GPodder, and Audiobookshelf-compatible APIs. + +## 参考资料 + +- 文档: +- 源码: diff --git a/apps/podfetch/README_en.md b/apps/podfetch/README_en.md new file mode 100644 index 000000000..957d7eeae --- /dev/null +++ b/apps/podfetch/README_en.md @@ -0,0 +1,3 @@ +# PodFetch + +See `README.md` for the bilingual product description, configuration, and persistence notes. diff --git a/apps/podfetch/data.yml b/apps/podfetch/data.yml new file mode 100644 index 000000000..2082fd363 --- /dev/null +++ b/apps/podfetch/data.yml @@ -0,0 +1,31 @@ +name: PodFetch +tags: + - 多媒体 +title: 自托管的播客管理器和下载器 +description: 自托管的播客管理器和下载器 +additionalProperties: + key: podfetch + name: PodFetch + tags: + - Media + shortDescZh: 自托管的播客管理器和下载器 + shortDescEn: Self-hosted podcast manager and downloader + description: + en: Self-hosted podcast manager and downloader with web, mobile, and feed-reader integrations. + zh: 支持 Web、移动端和订阅阅读器集成的自托管播客管理器与下载器。 + zh-Hant: 支援 Web、行動端和訂閱閱讀器整合的自託管播客管理器與下載器。 + ja: Web、モバイル、フィードリーダー連携に対応したセルフホスト型ポッドキャスト管理・ダウンロードツールです。 + ko: 웹, 모바일, 피드 리더 연동을 지원하는 자체 호스팅 팟캐스트 관리자 및 다운로더입니다. + ru: Самостоятельно размещаемый менеджер и загрузчик подкастов с интеграциями. + ms: Pengurus dan pemuat turun podcast dihos sendiri dengan integrasi web dan mudah alih. + pt-br: Gerenciador e baixador de podcasts auto-hospedado com integracoes web e moveis. + type: tool + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://samtv12345.github.io/PodFetch/ + github: https://github.com/SamTV12345/PodFetch + document: https://samtv12345.github.io/PodFetch/ + architectures: + - amd64 + - arm64 diff --git a/apps/podfetch/latest/.env.sample b/apps/podfetch/latest/.env.sample new file mode 100644 index 000000000..882d05454 --- /dev/null +++ b/apps/podfetch/latest/.env.sample @@ -0,0 +1,11 @@ +CONTAINER_NAME=podfetch +PANEL_APP_BIND_ADDRESS=0.0.0.0 +PANEL_APP_PORT_HTTP=40100 +PUID=1000 +PGID=1000 +POLLING_INTERVAL=60 +BASIC_AUTH=true +PODFETCH_USERNAME=admin +PODFETCH_PASSWORD=replace-with-a-strong-password +PODCASTS_DIR=./data/podcasts +DATABASE_DIR=./data/db diff --git a/apps/podfetch/latest/data.yml b/apps/podfetch/latest/data.yml new file mode 100644 index 000000000..ca119e9f9 --- /dev/null +++ b/apps/podfetch/latest/data.yml @@ -0,0 +1,170 @@ +additionalProperties: + formFields: + - default: 0.0.0.0 + edit: true + envKey: PANEL_APP_BIND_ADDRESS + labelEn: Bind Address + labelZh: 绑定地址 + label: + en: Bind Address + zh: 绑定地址 + zh-Hant: 綁定位址 + ja: バインドアドレス + ko: 바인드 주소 + ru: Адрес привязки + ms: Alamat ikatan + pt-br: Endereco de vinculacao + required: true + type: text + - default: 40100 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP Port + labelZh: HTTP 端口 + label: + en: HTTP Port + zh: HTTP 端口 + zh-Hant: HTTP 連接埠 + ja: HTTP ポート + ko: HTTP 포트 + ru: HTTP-порт + ms: Port HTTP + pt-br: Porta HTTP + required: true + rule: paramPort + type: number + - default: 1000 + edit: true + envKey: PUID + labelEn: User ID + labelZh: 用户 ID + label: + en: User ID + zh: 用户 ID + zh-Hant: 使用者 ID + ja: ユーザー ID + ko: 사용자 ID + ru: Идентификатор пользователя + ms: ID pengguna + pt-br: ID do usuario + required: true + type: number + - default: 1000 + edit: true + envKey: PGID + labelEn: Group ID + labelZh: 用户组 ID + label: + en: Group ID + zh: 用户组 ID + zh-Hant: 使用者群組 ID + ja: グループ ID + ko: 그룹 ID + ru: Идентификатор группы + ms: ID kumpulan + pt-br: ID do grupo + required: true + type: number + - default: 60 + edit: true + envKey: POLLING_INTERVAL + labelEn: Polling Interval + labelZh: 检查间隔 + label: + en: Polling Interval + zh: 检查间隔 + zh-Hant: 檢查間隔 + ja: 確認間隔 + ko: 확인 간격 + ru: Интервал проверки + ms: Selang semakan + pt-br: Intervalo de verificacao + required: true + type: number + - default: "true" + edit: true + envKey: BASIC_AUTH + labelEn: Enable Basic Authentication + labelZh: 启用基本认证 + label: + en: Enable Basic Authentication + zh: 启用基本认证 + zh-Hant: 啟用基本驗證 + ja: Basic 認証を有効化 + ko: 기본 인증 사용 + ru: Включить базовую аутентификацию + ms: Dayakan pengesahan asas + pt-br: Ativar autenticacao basica + required: true + type: select + values: + - label: "true" + value: "true" + - label: "false" + value: "false" + - default: admin + edit: true + envKey: PODFETCH_USERNAME + labelEn: Administrator Username + labelZh: 管理员用户名 + label: + en: Administrator Username + zh: 管理员用户名 + zh-Hant: 管理員使用者名稱 + ja: 管理者ユーザー名 + ko: 관리자 사용자 이름 + ru: Имя администратора + ms: Nama pengguna pentadbir + pt-br: Nome do administrador + required: true + type: text + - default: "" + edit: true + envKey: PODFETCH_PASSWORD + labelEn: Administrator Password + labelZh: 管理员密码 + label: + en: Administrator Password + zh: 管理员密码 + zh-Hant: 管理員密碼 + ja: 管理者パスワード + ko: 관리자 비밀번호 + ru: Пароль администратора + ms: Kata laluan pentadbir + pt-br: Senha do administrador + random: true + required: true + rule: paramComplexity + type: password + - default: ./data/podcasts + edit: true + envKey: PODCASTS_DIR + labelEn: Podcast Directory + labelZh: 播客目录 + label: + en: Podcast Directory + zh: 播客目录 + zh-Hant: Podcast 目錄 + ja: ポッドキャストディレクトリ + ko: 팟캐스트 디렉터리 + ru: Каталог подкастов + ms: Direktori podcast + pt-br: Diretorio de podcasts + required: true + type: text + - default: ./data/db + edit: true + envKey: DATABASE_DIR + labelEn: Database Directory + labelZh: 数据库目录 + label: + en: Database Directory + zh: 数据库目录 + zh-Hant: 資料庫目錄 + ja: データベースディレクトリ + ko: 데이터베이스 디렉터리 + ru: Каталог базы данных + ms: Direktori pangkalan data + pt-br: Diretorio do banco de dados + required: true + type: text diff --git a/apps/podfetch/latest/docker-compose.yml b/apps/podfetch/latest/docker-compose.yml new file mode 100644 index 000000000..0c48389b7 --- /dev/null +++ b/apps/podfetch/latest/docker-compose.yml @@ -0,0 +1,27 @@ +services: + podfetch: + image: "docker.io/samuel19982/podfetch:latest" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + user: "${PUID}:${PGID}" + networks: + - 1panel-network + ports: + - "${PANEL_APP_BIND_ADDRESS}:${PANEL_APP_PORT_HTTP}:8000" + environment: + - PORT=8000 + - POLLING_INTERVAL=${POLLING_INTERVAL} + - DATABASE_URL=sqlite:///app/db/podcast.db + - PODFETCH_FOLDER=/app/podcasts + - BASIC_AUTH=${BASIC_AUTH} + - USERNAME=${PODFETCH_USERNAME} + - PASSWORD=${PODFETCH_PASSWORD} + volumes: + - "${PODCASTS_DIR}:/app/podcasts" + - "${DATABASE_DIR}:/app/db" + labels: + createdBy: Apps + +networks: + 1panel-network: + external: true diff --git a/apps/podfetch/latest/scripts/init.sh b/apps/podfetch/latest/scripts/init.sh new file mode 100755 index 000000000..fb1055e5e --- /dev/null +++ b/apps/podfetch/latest/scripts/init.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +read_env_value() { + local key="$1" + local value="" + + value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2-)" + case "$value" in + \"*\" | \'*\') value="${value:1:${#value}-2}" ;; + esac + printf '%s\n' "$value" +} + +prepare_directory() { + local key="$1" + local raw="$2" + local absolute="" + + [[ -n "$raw" ]] || fail "${key} must not be empty" + case "$raw" in + *$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'"*) fail "${key} contains unsupported dotenv characters" ;; + esac + if [[ "$raw" = /* ]]; then + [[ ! -L "$raw" ]] || fail "${key} must not be a symbolic link" + absolute="$(realpath -m -- "$raw")" + [[ "$absolute" != "/" ]] || fail "${key} must not be the filesystem root" + if [[ -e "$absolute" ]]; then + [[ -d "$absolute" ]] || fail "${key} must be a directory" + [[ "$(stat -c '%u:%g' "$absolute")" == "${PUID}:${PGID}" ]] || fail "Existing ${key} must be owned by ${PUID}:${PGID}" + else + install -d -m 0750 -- "$absolute" + chown "${PUID}:${PGID}" -- "$absolute" + fi + else + absolute="$(realpath -m -- "${ROOT_DIR}/${raw#./}")" + case "$absolute" in + "${ROOT_DIR}"/*) ;; + *) fail "Relative ${key} must remain inside the application version directory" ;; + esac + [[ ! -L "$absolute" ]] || fail "${key} must not be a symbolic link" + install -d -m 0750 -- "$absolute" + chown "${PUID}:${PGID}" -- "$absolute" + fi +} + +[[ "$(id -u)" -eq 0 ]] || fail "PodFetch init must run as root" +[[ -f "$ENV_FILE" ]] || fail "Environment file not found: ${ENV_FILE}" +[[ ! -L "$ENV_FILE" ]] || fail "Environment file must not be a symbolic link" + +PUID="${PUID:-$(read_env_value PUID)}" +PGID="${PGID:-$(read_env_value PGID)}" +[[ "$PUID" =~ ^[0-9]+$ && "$PGID" =~ ^[0-9]+$ ]] || fail "PUID and PGID must be numeric" + +prepare_directory PODCASTS_DIR "${PODCASTS_DIR:-$(read_env_value PODCASTS_DIR)}" +prepare_directory DATABASE_DIR "${DATABASE_DIR:-$(read_env_value DATABASE_DIR)}" diff --git a/apps/podfetch/latest/scripts/uninstall.sh b/apps/podfetch/latest/scripts/uninstall.sh new file mode 100755 index 000000000..552de26c5 --- /dev/null +++ b/apps/podfetch/latest/scripts/uninstall.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Podcast media and the SQLite database are retained for recovery. +exit 0 diff --git a/apps/podfetch/latest/scripts/upgrade.sh b/apps/podfetch/latest/scripts/upgrade.sh new file mode 100755 index 000000000..4b02f0ad7 --- /dev/null +++ b/apps/podfetch/latest/scripts/upgrade.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +# PodFetch applies its database migrations when the new container starts. +exit 0 diff --git a/apps/podfetch/logo-LICENSE.txt b/apps/podfetch/logo-LICENSE.txt new file mode 100644 index 000000000..4126e031a --- /dev/null +++ b/apps/podfetch/logo-LICENSE.txt @@ -0,0 +1,9 @@ +PodFetch logo + +Source: https://github.com/SamTV12345/PodFetch/blob/b28002a56bd0291df3a4b184cf5673dcd99aceec/ui/public/logo.png +Source commit: b28002a56bd0291df3a4b184cf5673dcd99aceec +Source SHA-256: 3ad03a34777eff296167d19f38f0084c54096f7b78e725958953d05d7e8d58c4 +License: Apache License 2.0 (Apache-2.0) +License text: https://github.com/SamTV12345/PodFetch/blob/b28002a56bd0291df3a4b184cf5673dcd99aceec/LICENSE + +The store asset is a proportional rendering of the official project logo. diff --git a/apps/podfetch/logo.png b/apps/podfetch/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8fdc790053e6b64a549421362c8bdaf37a04e4b6 GIT binary patch literal 1564 zcmZ`(Ydq5n82*owiB=<|QKC3ZqcO#(+=sd3*4(SH5XxmPu^p61hm^U5Wg=$$Tyn`J zM^2ktsL3I>#mQwmVj`E0nw_1m{m%LDzR&M{-uM0Re0ZMp3yv6w6if;L0En%PrHcrg z{w+yS0~9Mf`64*xZ{y+s0Pz|Ckdy)dJ0dEH2LREh0AT4d0H7ZMz@czzyR(UCA%@3d zECFF!*5FIgv^U(wGYSC2Mc3jyP;-Jyh=>FeZ5^z@OJEfl9m$DcGZp~sNw&2#bBmi? zp$f4@80B7Fr&8NLf|Q$jYKLKS7xUPE<*91>tK}$sXM|!gPmY9!`q!(Z#3XjH8xZF` z#&r?<(BGd-mJ-+scJ$g;8ge*K7dq3$fB1A5o%pN9wT#6!!R2C;?Wg@C%f;A^F0KHp z6Z@8Xu5LOi_HF(*AEKf>E#N{Fl@mCw=hqhRXX#C!@F^GhTX5_gi2XAXnNaG|9`k3oZyJ#OF zv(z`5;A6~SohNj+Ua!z>Oc1it@2H$niet*?0CU+|%{WLIr<>LIf_C!t&xig~m^cay zS4LSl-MOtpXfXtHEZq0K-<1M~Y4-4c4-Zq^5lO#WMNdyon<}#l;#e(22a~=*jGv!z zZH5hfoLk&`Lh{xz$qE@G{a@@k!x-=NXUvMQ1R|c&bKV{M2|9V5!AhkvTB?*h(_6E6 zd78LQp;dIuhD)W{$HUGe154HrOY})3&e^J-4PkNmog$L^i1}3oIh2TFs>qsBbJNv_ zZUQUXwEMep6^Wz4X#a_}Fx1gp|@#Y{E3+Ks6OJ!oz4Sn-pMUlF4-z>u} zG&PQ^4;>$Kw&OOjA0C&bkj*_KSDPMNKBslkPtt7URnp}YVcX>ux3}|i)xDGX&kJQ! zpm0X;r~RQ?SfLSoB6n+er(*vDNCM^rH_)-1vo+gi9)1OqJ8CfI`R@9h?94BSZn840 zNzHC}*Nenab9W{i#a0iDX6G)Tn?vGx%@l@;U#vc^tSDNmj1poDWYYZ{Kqv?obu*pm z(9rK2|JDodkg%ZL--$IcITPkLnXh-smp$tty|Hs>on60^cZv8_@8+L>quMRlhD47F z&CMP67k7EvvfK7)TDr6QO4jB<_@j|jGF8ym8`%F%AWkWqzLr7TU6B{-o;6Z=JwA!& zb)=D&W51UURgaad%4EY1d$nK3yld@VnDRy3J0pLeW`G{;AOtL93DDq8C9ro0p z1^VGG;)9BC^SVa_Yd3184M%gH+-|cc8{}L}i@?>?JQq`yBd>4tzq6iPTiuF_tU*_! zinTH#c>1nW8nYrMEycg|aTTdR@5m%hSB zK3`mwQo1pCnko>u)+M@@cd!J{T2hC-WM242#zj;Lw-X!P0>d_A#vdVWAUTsQJMTZ& zoTrszo=T6_Zdo95>ImxxGNFEuT_jRp`z5G0ik~QO2vnaF&_Yus7?#WUB|=d+vL~SP z0{p;~`<^nZT|BZO_5u|f`D6BI>Lvf!_*{z{KM=u0i@};J9 qdE|!|k*mdmM1J4@)Bok05af<}pCOY}?I+4-z}Cvqvex|at$zT@<=e3U literal 0 HcmV?d00001 diff --git a/apps/podfetch/v5.2.2/.env.sample b/apps/podfetch/v5.2.2/.env.sample new file mode 100644 index 000000000..882d05454 --- /dev/null +++ b/apps/podfetch/v5.2.2/.env.sample @@ -0,0 +1,11 @@ +CONTAINER_NAME=podfetch +PANEL_APP_BIND_ADDRESS=0.0.0.0 +PANEL_APP_PORT_HTTP=40100 +PUID=1000 +PGID=1000 +POLLING_INTERVAL=60 +BASIC_AUTH=true +PODFETCH_USERNAME=admin +PODFETCH_PASSWORD=replace-with-a-strong-password +PODCASTS_DIR=./data/podcasts +DATABASE_DIR=./data/db diff --git a/apps/podfetch/v5.2.2/data.yml b/apps/podfetch/v5.2.2/data.yml new file mode 100644 index 000000000..ca119e9f9 --- /dev/null +++ b/apps/podfetch/v5.2.2/data.yml @@ -0,0 +1,170 @@ +additionalProperties: + formFields: + - default: 0.0.0.0 + edit: true + envKey: PANEL_APP_BIND_ADDRESS + labelEn: Bind Address + labelZh: 绑定地址 + label: + en: Bind Address + zh: 绑定地址 + zh-Hant: 綁定位址 + ja: バインドアドレス + ko: 바인드 주소 + ru: Адрес привязки + ms: Alamat ikatan + pt-br: Endereco de vinculacao + required: true + type: text + - default: 40100 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP Port + labelZh: HTTP 端口 + label: + en: HTTP Port + zh: HTTP 端口 + zh-Hant: HTTP 連接埠 + ja: HTTP ポート + ko: HTTP 포트 + ru: HTTP-порт + ms: Port HTTP + pt-br: Porta HTTP + required: true + rule: paramPort + type: number + - default: 1000 + edit: true + envKey: PUID + labelEn: User ID + labelZh: 用户 ID + label: + en: User ID + zh: 用户 ID + zh-Hant: 使用者 ID + ja: ユーザー ID + ko: 사용자 ID + ru: Идентификатор пользователя + ms: ID pengguna + pt-br: ID do usuario + required: true + type: number + - default: 1000 + edit: true + envKey: PGID + labelEn: Group ID + labelZh: 用户组 ID + label: + en: Group ID + zh: 用户组 ID + zh-Hant: 使用者群組 ID + ja: グループ ID + ko: 그룹 ID + ru: Идентификатор группы + ms: ID kumpulan + pt-br: ID do grupo + required: true + type: number + - default: 60 + edit: true + envKey: POLLING_INTERVAL + labelEn: Polling Interval + labelZh: 检查间隔 + label: + en: Polling Interval + zh: 检查间隔 + zh-Hant: 檢查間隔 + ja: 確認間隔 + ko: 확인 간격 + ru: Интервал проверки + ms: Selang semakan + pt-br: Intervalo de verificacao + required: true + type: number + - default: "true" + edit: true + envKey: BASIC_AUTH + labelEn: Enable Basic Authentication + labelZh: 启用基本认证 + label: + en: Enable Basic Authentication + zh: 启用基本认证 + zh-Hant: 啟用基本驗證 + ja: Basic 認証を有効化 + ko: 기본 인증 사용 + ru: Включить базовую аутентификацию + ms: Dayakan pengesahan asas + pt-br: Ativar autenticacao basica + required: true + type: select + values: + - label: "true" + value: "true" + - label: "false" + value: "false" + - default: admin + edit: true + envKey: PODFETCH_USERNAME + labelEn: Administrator Username + labelZh: 管理员用户名 + label: + en: Administrator Username + zh: 管理员用户名 + zh-Hant: 管理員使用者名稱 + ja: 管理者ユーザー名 + ko: 관리자 사용자 이름 + ru: Имя администратора + ms: Nama pengguna pentadbir + pt-br: Nome do administrador + required: true + type: text + - default: "" + edit: true + envKey: PODFETCH_PASSWORD + labelEn: Administrator Password + labelZh: 管理员密码 + label: + en: Administrator Password + zh: 管理员密码 + zh-Hant: 管理員密碼 + ja: 管理者パスワード + ko: 관리자 비밀번호 + ru: Пароль администратора + ms: Kata laluan pentadbir + pt-br: Senha do administrador + random: true + required: true + rule: paramComplexity + type: password + - default: ./data/podcasts + edit: true + envKey: PODCASTS_DIR + labelEn: Podcast Directory + labelZh: 播客目录 + label: + en: Podcast Directory + zh: 播客目录 + zh-Hant: Podcast 目錄 + ja: ポッドキャストディレクトリ + ko: 팟캐스트 디렉터리 + ru: Каталог подкастов + ms: Direktori podcast + pt-br: Diretorio de podcasts + required: true + type: text + - default: ./data/db + edit: true + envKey: DATABASE_DIR + labelEn: Database Directory + labelZh: 数据库目录 + label: + en: Database Directory + zh: 数据库目录 + zh-Hant: 資料庫目錄 + ja: データベースディレクトリ + ko: 데이터베이스 디렉터리 + ru: Каталог базы данных + ms: Direktori pangkalan data + pt-br: Diretorio do banco de dados + required: true + type: text diff --git a/apps/podfetch/v5.2.2/docker-compose.yml b/apps/podfetch/v5.2.2/docker-compose.yml new file mode 100644 index 000000000..d6964385b --- /dev/null +++ b/apps/podfetch/v5.2.2/docker-compose.yml @@ -0,0 +1,27 @@ +services: + podfetch: + image: "docker.io/samuel19982/podfetch:v5.2.2@sha256:d8b8e546d3ae40c6e7821343c3d0f0a238b47cb772295e95c7f8683ca419663f" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + user: "${PUID}:${PGID}" + networks: + - 1panel-network + ports: + - "${PANEL_APP_BIND_ADDRESS}:${PANEL_APP_PORT_HTTP}:8000" + environment: + - PORT=8000 + - POLLING_INTERVAL=${POLLING_INTERVAL} + - DATABASE_URL=sqlite:///app/db/podcast.db + - PODFETCH_FOLDER=/app/podcasts + - BASIC_AUTH=${BASIC_AUTH} + - USERNAME=${PODFETCH_USERNAME} + - PASSWORD=${PODFETCH_PASSWORD} + volumes: + - "${PODCASTS_DIR}:/app/podcasts" + - "${DATABASE_DIR}:/app/db" + labels: + createdBy: Apps + +networks: + 1panel-network: + external: true diff --git a/apps/podfetch/v5.2.2/scripts/init.sh b/apps/podfetch/v5.2.2/scripts/init.sh new file mode 100755 index 000000000..fb1055e5e --- /dev/null +++ b/apps/podfetch/v5.2.2/scripts/init.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +read_env_value() { + local key="$1" + local value="" + + value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2-)" + case "$value" in + \"*\" | \'*\') value="${value:1:${#value}-2}" ;; + esac + printf '%s\n' "$value" +} + +prepare_directory() { + local key="$1" + local raw="$2" + local absolute="" + + [[ -n "$raw" ]] || fail "${key} must not be empty" + case "$raw" in + *$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'"*) fail "${key} contains unsupported dotenv characters" ;; + esac + if [[ "$raw" = /* ]]; then + [[ ! -L "$raw" ]] || fail "${key} must not be a symbolic link" + absolute="$(realpath -m -- "$raw")" + [[ "$absolute" != "/" ]] || fail "${key} must not be the filesystem root" + if [[ -e "$absolute" ]]; then + [[ -d "$absolute" ]] || fail "${key} must be a directory" + [[ "$(stat -c '%u:%g' "$absolute")" == "${PUID}:${PGID}" ]] || fail "Existing ${key} must be owned by ${PUID}:${PGID}" + else + install -d -m 0750 -- "$absolute" + chown "${PUID}:${PGID}" -- "$absolute" + fi + else + absolute="$(realpath -m -- "${ROOT_DIR}/${raw#./}")" + case "$absolute" in + "${ROOT_DIR}"/*) ;; + *) fail "Relative ${key} must remain inside the application version directory" ;; + esac + [[ ! -L "$absolute" ]] || fail "${key} must not be a symbolic link" + install -d -m 0750 -- "$absolute" + chown "${PUID}:${PGID}" -- "$absolute" + fi +} + +[[ "$(id -u)" -eq 0 ]] || fail "PodFetch init must run as root" +[[ -f "$ENV_FILE" ]] || fail "Environment file not found: ${ENV_FILE}" +[[ ! -L "$ENV_FILE" ]] || fail "Environment file must not be a symbolic link" + +PUID="${PUID:-$(read_env_value PUID)}" +PGID="${PGID:-$(read_env_value PGID)}" +[[ "$PUID" =~ ^[0-9]+$ && "$PGID" =~ ^[0-9]+$ ]] || fail "PUID and PGID must be numeric" + +prepare_directory PODCASTS_DIR "${PODCASTS_DIR:-$(read_env_value PODCASTS_DIR)}" +prepare_directory DATABASE_DIR "${DATABASE_DIR:-$(read_env_value DATABASE_DIR)}" diff --git a/apps/podfetch/v5.2.2/scripts/uninstall.sh b/apps/podfetch/v5.2.2/scripts/uninstall.sh new file mode 100755 index 000000000..552de26c5 --- /dev/null +++ b/apps/podfetch/v5.2.2/scripts/uninstall.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Podcast media and the SQLite database are retained for recovery. +exit 0 diff --git a/apps/podfetch/v5.2.2/scripts/upgrade.sh b/apps/podfetch/v5.2.2/scripts/upgrade.sh new file mode 100755 index 000000000..4b02f0ad7 --- /dev/null +++ b/apps/podfetch/v5.2.2/scripts/upgrade.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +# PodFetch applies its database migrations when the new container starts. +exit 0