diff --git a/apps/tinyauth/5.1.3/.env.sample b/apps/tinyauth/5.1.3/.env.sample
new file mode 100644
index 000000000..48d328944
--- /dev/null
+++ b/apps/tinyauth/5.1.3/.env.sample
@@ -0,0 +1,6 @@
+CONTAINER_NAME=tinyauth
+PANEL_APP_PORT_HTTP=3000
+TINYAUTH_APP_URL=http://tinyauth.localhost:3000
+TINYAUTH_ADMIN_USERNAME=admin
+TINYAUTH_ADMIN_PASSWORD=Tinyauth_Admin_Change_Me_2026!
+TINYAUTH_SECURE_COOKIE=false
diff --git a/apps/tinyauth/5.1.3/data.yml b/apps/tinyauth/5.1.3/data.yml
new file mode 100644
index 000000000..96949bffa
--- /dev/null
+++ b/apps/tinyauth/5.1.3/data.yml
@@ -0,0 +1,92 @@
+additionalProperties:
+ formFields:
+ - default: 3000
+ 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: http://tinyauth.localhost:3000
+ edit: true
+ envKey: TINYAUTH_APP_URL
+ labelEn: External URL
+ labelZh: 外部访问地址
+ label:
+ en: External URL
+ zh: 外部访问地址
+ zh-Hant: 外部存取網址
+ ja: 外部 URL
+ ko: 외부 URL
+ ru: Внешний URL
+ ms: URL Luaran
+ pt-br: URL externo
+ required: true
+ rule: paramExtUrl
+ type: text
+ - default: admin
+ edit: true
+ envKey: TINYAUTH_ADMIN_USERNAME
+ labelEn: Admin Username
+ labelZh: 管理员用户名
+ label:
+ en: Admin Username
+ zh: 管理员用户名
+ zh-Hant: 管理員使用者名稱
+ ja: 管理者ユーザー名
+ ko: 관리자 사용자 이름
+ ru: Имя администратора
+ ms: Nama Pengguna Pentadbir
+ pt-br: Usuario administrador
+ required: true
+ rule: paramCommon
+ type: text
+ - default: ""
+ edit: true
+ envKey: TINYAUTH_ADMIN_PASSWORD
+ labelEn: Admin Password
+ labelZh: 管理员密码
+ label:
+ en: Admin 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: "false"
+ edit: true
+ envKey: TINYAUTH_SECURE_COOKIE
+ labelEn: Secure Cookies
+ labelZh: 安全 Cookie
+ label:
+ en: Secure Cookies
+ zh: 安全 Cookie
+ zh-Hant: 安全 Cookie
+ ja: Secure Cookie
+ ko: 보안 Cookie
+ ru: Secure Cookie
+ ms: Cookie Selamat
+ pt-br: Cookie seguro
+ required: true
+ type: select
+ values:
+ - label: "false"
+ value: "false"
+ - label: "true"
+ value: "true"
diff --git a/apps/tinyauth/5.1.3/docker-compose.yml b/apps/tinyauth/5.1.3/docker-compose.yml
new file mode 100644
index 000000000..74224fc12
--- /dev/null
+++ b/apps/tinyauth/5.1.3/docker-compose.yml
@@ -0,0 +1,42 @@
+services:
+ tinyauth:
+ image: "ghcr.io/tinyauthapp/tinyauth:v5.1.3@sha256:a1bde189951900f84a60f133c26a1ec7a3264073ec7875b548f4fbadb7f5f735"
+ container_name: ${CONTAINER_NAME}
+ restart: unless-stopped
+ user: "1000:1000"
+ networks:
+ - 1panel-network
+ ports:
+ - "${PANEL_APP_PORT_HTTP}:3000"
+ environment:
+ - TINYAUTH_APPURL=${TINYAUTH_APP_URL}
+ - TINYAUTH_LABELPROVIDER=none
+ - TINYAUTH_AUTH_USERSFILE=/data/users
+ - TINYAUTH_AUTH_SECURECOOKIE=${TINYAUTH_SECURE_COOKIE}
+ - TINYAUTH_ANALYTICS_ENABLED=false
+ - TINYAUTH_SERVER_ADDRESS=0.0.0.0
+ - TINYAUTH_SERVER_PORT=3000
+ - TINYAUTH_DATABASE_DRIVER=sqlite
+ - TINYAUTH_DATABASE_PATH=/data/tinyauth.db
+ - TINYAUTH_RESOURCES_PATH=/data/resources
+ volumes:
+ - "./data:/data"
+ read_only: true
+ tmpfs:
+ - /tmp:nodev,noexec,nosuid,size=32M
+ cap_drop:
+ - ALL
+ security_opt:
+ - no-new-privileges:true
+ healthcheck:
+ test: ["CMD", "tinyauth", "healthcheck"]
+ interval: 30s
+ timeout: 5s
+ start_period: 10s
+ retries: 5
+ labels:
+ createdBy: "Apps"
+
+networks:
+ 1panel-network:
+ external: true
diff --git a/apps/tinyauth/5.1.3/scripts/init.sh b/apps/tinyauth/5.1.3/scripts/init.sh
new file mode 100755
index 000000000..95c45bbff
--- /dev/null
+++ b/apps/tinyauth/5.1.3/scripts/init.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
+ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}"
+DOCKER_BIN="${DOCKER_BIN:-docker}"
+IMAGE='ghcr.io/tinyauthapp/tinyauth:v5.1.3@sha256:a1bde189951900f84a60f133c26a1ec7a3264073ec7875b548f4fbadb7f5f735'
+
+fail() {
+ printf '%s\n' "$1" >&2
+ exit 1
+}
+
+read_env_value() {
+ local key="$1"
+ local value
+ value="$(sed -n "s/^${key}=//p" "$ENV_FILE" | tail -n 1)"
+ case "$value" in
+ \"*\") value="${value#\"}"; value="${value%\"}" ;;
+ \'*\') value="${value#\'}"; value="${value%\'}" ;;
+ esac
+ printf '%s\n' "$value"
+}
+
+[[ -f "$ENV_FILE" && ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must be a regular file"
+command -v "$DOCKER_BIN" >/dev/null 2>&1 || fail 'Docker is required to generate the Tinyauth password hash'
+
+username="${TINYAUTH_ADMIN_USERNAME:-$(read_env_value TINYAUTH_ADMIN_USERNAME)}"
+password="${TINYAUTH_ADMIN_PASSWORD:-$(read_env_value TINYAUTH_ADMIN_PASSWORD)}"
+app_url="${TINYAUTH_APP_URL:-$(read_env_value TINYAUTH_APP_URL)}"
+secure_cookie="${TINYAUTH_SECURE_COOKIE:-$(read_env_value TINYAUTH_SECURE_COOKIE)}"
+
+data_dir="$(realpath -m -- "$ROOT_DIR/data")"
+case "$data_dir" in
+ "$ROOT_DIR"/*) ;;
+ *) fail 'Tinyauth data directory must remain inside the application version directory' ;;
+esac
+for path in "$data_dir" "$data_dir/resources" "$data_dir/oidc"; do
+ [[ ! -L "$path" ]] || fail "$path must not be a symbolic link"
+ [[ ! -e "$path" || -d "$path" ]] || fail "$path must be a directory"
+done
+users_file="$data_dir/users"
+[[ ! -L "$users_file" ]] || fail 'Tinyauth users file must not be a symbolic link'
+[[ ! -e "$users_file" || -f "$users_file" ]] || fail 'Tinyauth users path must be a regular file'
+
+[[ "$username" =~ ^[A-Za-z0-9._-]{1,64}$ ]] || fail 'TINYAUTH_ADMIN_USERNAME must contain only letters, digits, dot, underscore, or hyphen'
+[[ ${#password} -ge 12 && ${#password} -le 256 ]] || fail 'TINYAUTH_ADMIN_PASSWORD must contain 12 to 256 characters'
+[[ "$password" != *$'\n'* && "$password" != *$'\r'* ]] || fail 'TINYAUTH_ADMIN_PASSWORD must not contain line breaks'
+[[ "$app_url" =~ ^https?://[^[:space:]]+$ ]] || fail 'TINYAUTH_APP_URL must be an absolute HTTP or HTTPS URL without whitespace'
+app_host="${app_url#*://}"
+app_host="${app_host%%/*}"
+app_host="${app_host%%:*}"
+[[ "$app_host" == *.* && "$app_host" =~ ^[A-Za-z0-9][A-Za-z0-9.-]*[A-Za-z0-9]$ ]] || fail 'TINYAUTH_APP_URL must use a domain name with at least two labels'
+[[ ! "$app_host" =~ ^[0-9.]+$ ]] || fail 'TINYAUTH_APP_URL must not use an IP address'
+[[ "$secure_cookie" == false || "$secure_cookie" == true ]] || fail 'TINYAUTH_SECURE_COOKIE must be true or false'
+
+install -d -m 0750 -o 1000 -g 1000 -- "$data_dir" "$data_dir/resources" "$data_dir/oidc"
+
+# Variables in this command are expanded by the shell inside the generator container.
+# shellcheck disable=SC2016
+generator_command='IFS= read -r password; exec tinyauth user create --username "$1" --password "$password"'
+generator_output="$(
+ printf '%s\n' "$password" |
+ "$DOCKER_BIN" run --rm -i --network none --read-only --user 1000:1000 \
+ --cap-drop ALL --security-opt no-new-privileges --env NO_COLOR=1 \
+ --entrypoint /bin/sh "$IMAGE" -ec \
+ "$generator_command" \
+ sh "$username"
+)"
+user_entry="$(printf '%s\n' "$generator_output" | sed -n 's/^TINYAUTH_AUTH_USERS=//p' | head -n 1)"
+[[ "$user_entry" =~ ^[A-Za-z0-9._-]{1,64}:\$2[aby]\$[0-9]{2}\$[./A-Za-z0-9]{53}$ ]] || fail 'Failed to generate a valid bcrypt user entry'
+
+umask 077
+temporary="$(mktemp "$data_dir/.users.tmp.XXXXXX")"
+trap 'rm -f -- "${temporary:-}"' EXIT
+printf '%s\n' "$user_entry" >"$temporary"
+chmod 0600 "$temporary"
+chown 1000:1000 "$temporary"
+mv -f -- "$temporary" "$users_file"
+trap - EXIT
+
+chown 1000:1000 "$data_dir" "$data_dir/resources" "$data_dir/oidc" "$users_file"
+chmod 0750 "$data_dir" "$data_dir/resources" "$data_dir/oidc"
+chmod 0600 "$users_file"
diff --git a/apps/tinyauth/5.1.3/scripts/uninstall.sh b/apps/tinyauth/5.1.3/scripts/uninstall.sh
new file mode 100755
index 000000000..5a3f6bd6b
--- /dev/null
+++ b/apps/tinyauth/5.1.3/scripts/uninstall.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if command -v docker-compose >/dev/null 2>&1; then
+ docker-compose down --volumes --remove-orphans
+else
+ docker compose down --volumes --remove-orphans
+fi
diff --git a/apps/tinyauth/5.1.3/scripts/upgrade.sh b/apps/tinyauth/5.1.3/scripts/upgrade.sh
new file mode 100755
index 000000000..9879df4c3
--- /dev/null
+++ b/apps/tinyauth/5.1.3/scripts/upgrade.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+set -euo pipefail
+exit 0
diff --git a/apps/tinyauth/README.md b/apps/tinyauth/README.md
new file mode 100644
index 000000000..cc750f262
--- /dev/null
+++ b/apps/tinyauth/README.md
@@ -0,0 +1,68 @@
+# Tinyauth
+
+## 产品介绍
+
+Tinyauth 是一个轻量级身份认证与授权服务,可作为 Traefik、Nginx、Caddy 等反向代理的 Forward Auth 后端,也可独立提供本地用户登录、OAuth、LDAP 和 OpenID Connect 服务。
+
+本应用使用 Tinyauth 官方镜像和本地 SQLite 数据库。安装时输入的管理员密码会通过上游自带命令生成 bcrypt 哈希,运行容器只读取持久化的哈希用户文件,不接收管理员明文密码。
+
+## 主要功能
+
+- 本地用户登录与基于 Cookie 的认证会话
+- 适配 Traefik、Nginx 和 Caddy 的 Forward Auth 端点
+- OAuth、LDAP 和 Tailscale 身份源集成
+- 作为 OpenID Connect 身份提供方服务其他应用
+- 按用户、组、IP、域名和路径配置访问控制
+
+## 访问说明
+
+- Web 界面:安装表单中填写的外部访问地址
+- 初始管理员:安装表单中的管理员用户名和随机密码
+- 使用 HTTP 时保持“安全 Cookie”为 `false`;通过 HTTPS 域名访问时应将其设置为 `true`
+- 外部访问地址必须使用至少两级的域名,并与浏览器实际使用的协议、域名和端口一致;Tinyauth 不接受 IP 地址或裸 `localhost`
+
+登录后可直接使用 Tinyauth 的独立认证界面。若要保护其他应用,需要在反向代理中把认证请求转发到 Tinyauth,并按上游文档配置对应的 Forward Auth 端点和请求头。本包不会自动发现或修改其他 1Panel 应用。
+
+## 安全边界
+
+本包明确设置 `TINYAUTH_LABELPROVIDER=none`,不挂载 `/var/run/docker.sock`,也不启用 Docker 或 Kubernetes label provider。容器以 UID/GID `1000:1000` 运行,根文件系统只读,删除全部 Linux capabilities,并启用 `no-new-privileges`。
+
+镜像依赖扫描会报告 Docker client 库中的三个 High 漏洞,但这些代码只由已禁用的 Docker label provider 调用;默认包没有 Docker socket、Docker API 地址或 provider 自动探测路径。请勿自行把 label provider 改为 `auto` 或 `docker`,也不要向容器添加 Docker socket。
+
+固定版本从 `5.1.3` 开始,因为 `5.1.2` 及更早版本受 `GHSA-r27r-rr9v-vv37` 的 Forward Auth ACL 认证绕过影响。不要降级到 `5.1.2` 或更早版本。
+
+## 数据与升级
+
+`./data` 挂载到容器 `/data`,保存 bcrypt 用户文件、SQLite 数据库、OIDC 密钥和资源文件。卸载只移除容器和 Compose 资源,不删除该目录。升级前应备份整个 `./data` 目录,并确认外部访问地址和反向代理配置保持一致。
+
+`latest` 使用上游 `v5` 移动标签;需要可重复部署时请选择固定版本 `5.1.3`。
+
+## Introduction
+
+Tinyauth is a lightweight authentication and authorization server for reverse proxies and standalone applications. It supports local users, OAuth, LDAP, OpenID Connect, and Forward Auth integrations for Traefik, Nginx, and Caddy.
+
+This package uses the official image and a local SQLite database. The install script passes the administrator password to Tinyauth's own bcrypt generator over standard input and persists only the resulting user hash for the runtime container.
+
+## Features
+
+- Local users and cookie-based authentication sessions
+- Forward Auth endpoints for Traefik, Nginx, and Caddy
+- OAuth, LDAP, and Tailscale identity integrations
+- OpenID Connect provider support for downstream applications
+- User, group, IP, domain, and path-based access controls
+
+The package explicitly disables label discovery with `TINYAUTH_LABELPROVIDER=none` and does not mount the Docker socket. The container runs as UID/GID `1000:1000` with a read-only root filesystem, all Linux capabilities dropped, and `no-new-privileges` enabled. Do not enable the Docker provider or add a Docker socket mount.
+
+Set the external URL to the exact browser-facing protocol, domain, and port. Tinyauth rejects IP addresses and bare `localhost`; use a domain with at least two labels. Use secure cookies only with HTTPS. To protect another application, configure its reverse proxy to call the appropriate Tinyauth Forward Auth endpoint; this package intentionally does not discover or modify other 1Panel applications.
+
+Version `5.1.3` is the oldest version packaged here because it fixes the Forward Auth ACL authentication bypass in `GHSA-r27r-rr9v-vv37`. Do not downgrade to `5.1.2` or earlier.
+
+Persistent users, SQLite state, OIDC keys, and resources live under `./data`. Uninstall preserves this directory. Back up the entire directory before upgrades.
+
+## References
+
+- Website:
+- Documentation:
+- Source:
+- Security advisory:
+- Official image:
diff --git a/apps/tinyauth/data.yml b/apps/tinyauth/data.yml
new file mode 100644
index 000000000..7f032783d
--- /dev/null
+++ b/apps/tinyauth/data.yml
@@ -0,0 +1,31 @@
+name: Tinyauth
+tags:
+ - Security
+title: 轻量级身份认证与授权服务
+description: 轻量级身份认证与授权服务
+additionalProperties:
+ key: tinyauth
+ name: Tinyauth
+ tags:
+ - Security
+ shortDescZh: 轻量级身份认证与授权服务
+ shortDescEn: Lightweight authentication and authorization server
+ description:
+ en: A lightweight authentication and authorization server for reverse proxies and standalone applications
+ zh: 为反向代理和独立应用提供身份认证、OAuth、LDAP、OIDC 与访问控制
+ zh-Hant: 為反向代理和獨立應用提供身份驗證、OAuth、LDAP、OIDC 與存取控制
+ ja: リバースプロキシとスタンドアロンアプリ向けの軽量な認証・認可サーバー
+ ko: 리버스 프록시와 독립 실행형 앱을 위한 경량 인증 및 권한 부여 서버
+ ru: Легковесный сервер аутентификации и авторизации для обратных прокси и автономных приложений
+ ms: Pelayan pengesahan dan kebenaran ringan untuk proksi songsang dan aplikasi kendiri
+ pt-br: Servidor leve de autenticacao e autorizacao para proxies reversos e aplicativos independentes
+ type: tool
+ crossVersionUpdate: true
+ limit: 0
+ recommend: 0
+ website: https://tinyauth.app/
+ github: https://github.com/tinyauthapp/tinyauth
+ document: https://tinyauth.app/docs/getting-started/
+ architectures:
+ - amd64
+ - arm64
diff --git a/apps/tinyauth/latest/.env.sample b/apps/tinyauth/latest/.env.sample
new file mode 100644
index 000000000..48d328944
--- /dev/null
+++ b/apps/tinyauth/latest/.env.sample
@@ -0,0 +1,6 @@
+CONTAINER_NAME=tinyauth
+PANEL_APP_PORT_HTTP=3000
+TINYAUTH_APP_URL=http://tinyauth.localhost:3000
+TINYAUTH_ADMIN_USERNAME=admin
+TINYAUTH_ADMIN_PASSWORD=Tinyauth_Admin_Change_Me_2026!
+TINYAUTH_SECURE_COOKIE=false
diff --git a/apps/tinyauth/latest/data.yml b/apps/tinyauth/latest/data.yml
new file mode 100644
index 000000000..96949bffa
--- /dev/null
+++ b/apps/tinyauth/latest/data.yml
@@ -0,0 +1,92 @@
+additionalProperties:
+ formFields:
+ - default: 3000
+ 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: http://tinyauth.localhost:3000
+ edit: true
+ envKey: TINYAUTH_APP_URL
+ labelEn: External URL
+ labelZh: 外部访问地址
+ label:
+ en: External URL
+ zh: 外部访问地址
+ zh-Hant: 外部存取網址
+ ja: 外部 URL
+ ko: 외부 URL
+ ru: Внешний URL
+ ms: URL Luaran
+ pt-br: URL externo
+ required: true
+ rule: paramExtUrl
+ type: text
+ - default: admin
+ edit: true
+ envKey: TINYAUTH_ADMIN_USERNAME
+ labelEn: Admin Username
+ labelZh: 管理员用户名
+ label:
+ en: Admin Username
+ zh: 管理员用户名
+ zh-Hant: 管理員使用者名稱
+ ja: 管理者ユーザー名
+ ko: 관리자 사용자 이름
+ ru: Имя администратора
+ ms: Nama Pengguna Pentadbir
+ pt-br: Usuario administrador
+ required: true
+ rule: paramCommon
+ type: text
+ - default: ""
+ edit: true
+ envKey: TINYAUTH_ADMIN_PASSWORD
+ labelEn: Admin Password
+ labelZh: 管理员密码
+ label:
+ en: Admin 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: "false"
+ edit: true
+ envKey: TINYAUTH_SECURE_COOKIE
+ labelEn: Secure Cookies
+ labelZh: 安全 Cookie
+ label:
+ en: Secure Cookies
+ zh: 安全 Cookie
+ zh-Hant: 安全 Cookie
+ ja: Secure Cookie
+ ko: 보안 Cookie
+ ru: Secure Cookie
+ ms: Cookie Selamat
+ pt-br: Cookie seguro
+ required: true
+ type: select
+ values:
+ - label: "false"
+ value: "false"
+ - label: "true"
+ value: "true"
diff --git a/apps/tinyauth/latest/docker-compose.yml b/apps/tinyauth/latest/docker-compose.yml
new file mode 100644
index 000000000..6a9953ca1
--- /dev/null
+++ b/apps/tinyauth/latest/docker-compose.yml
@@ -0,0 +1,42 @@
+services:
+ tinyauth:
+ image: "ghcr.io/tinyauthapp/tinyauth:v5"
+ container_name: ${CONTAINER_NAME}
+ restart: unless-stopped
+ user: "1000:1000"
+ networks:
+ - 1panel-network
+ ports:
+ - "${PANEL_APP_PORT_HTTP}:3000"
+ environment:
+ - TINYAUTH_APPURL=${TINYAUTH_APP_URL}
+ - TINYAUTH_LABELPROVIDER=none
+ - TINYAUTH_AUTH_USERSFILE=/data/users
+ - TINYAUTH_AUTH_SECURECOOKIE=${TINYAUTH_SECURE_COOKIE}
+ - TINYAUTH_ANALYTICS_ENABLED=false
+ - TINYAUTH_SERVER_ADDRESS=0.0.0.0
+ - TINYAUTH_SERVER_PORT=3000
+ - TINYAUTH_DATABASE_DRIVER=sqlite
+ - TINYAUTH_DATABASE_PATH=/data/tinyauth.db
+ - TINYAUTH_RESOURCES_PATH=/data/resources
+ volumes:
+ - "./data:/data"
+ read_only: true
+ tmpfs:
+ - /tmp:nodev,noexec,nosuid,size=32M
+ cap_drop:
+ - ALL
+ security_opt:
+ - no-new-privileges:true
+ healthcheck:
+ test: ["CMD", "tinyauth", "healthcheck"]
+ interval: 30s
+ timeout: 5s
+ start_period: 10s
+ retries: 5
+ labels:
+ createdBy: "Apps"
+
+networks:
+ 1panel-network:
+ external: true
diff --git a/apps/tinyauth/latest/scripts/init.sh b/apps/tinyauth/latest/scripts/init.sh
new file mode 100755
index 000000000..89b2ec7e8
--- /dev/null
+++ b/apps/tinyauth/latest/scripts/init.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
+ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}"
+DOCKER_BIN="${DOCKER_BIN:-docker}"
+IMAGE='ghcr.io/tinyauthapp/tinyauth:v5'
+
+fail() {
+ printf '%s\n' "$1" >&2
+ exit 1
+}
+
+read_env_value() {
+ local key="$1"
+ local value
+ value="$(sed -n "s/^${key}=//p" "$ENV_FILE" | tail -n 1)"
+ case "$value" in
+ \"*\") value="${value#\"}"; value="${value%\"}" ;;
+ \'*\') value="${value#\'}"; value="${value%\'}" ;;
+ esac
+ printf '%s\n' "$value"
+}
+
+[[ -f "$ENV_FILE" && ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must be a regular file"
+command -v "$DOCKER_BIN" >/dev/null 2>&1 || fail 'Docker is required to generate the Tinyauth password hash'
+
+username="${TINYAUTH_ADMIN_USERNAME:-$(read_env_value TINYAUTH_ADMIN_USERNAME)}"
+password="${TINYAUTH_ADMIN_PASSWORD:-$(read_env_value TINYAUTH_ADMIN_PASSWORD)}"
+app_url="${TINYAUTH_APP_URL:-$(read_env_value TINYAUTH_APP_URL)}"
+secure_cookie="${TINYAUTH_SECURE_COOKIE:-$(read_env_value TINYAUTH_SECURE_COOKIE)}"
+
+data_dir="$(realpath -m -- "$ROOT_DIR/data")"
+case "$data_dir" in
+ "$ROOT_DIR"/*) ;;
+ *) fail 'Tinyauth data directory must remain inside the application version directory' ;;
+esac
+for path in "$data_dir" "$data_dir/resources" "$data_dir/oidc"; do
+ [[ ! -L "$path" ]] || fail "$path must not be a symbolic link"
+ [[ ! -e "$path" || -d "$path" ]] || fail "$path must be a directory"
+done
+users_file="$data_dir/users"
+[[ ! -L "$users_file" ]] || fail 'Tinyauth users file must not be a symbolic link'
+[[ ! -e "$users_file" || -f "$users_file" ]] || fail 'Tinyauth users path must be a regular file'
+
+[[ "$username" =~ ^[A-Za-z0-9._-]{1,64}$ ]] || fail 'TINYAUTH_ADMIN_USERNAME must contain only letters, digits, dot, underscore, or hyphen'
+[[ ${#password} -ge 12 && ${#password} -le 256 ]] || fail 'TINYAUTH_ADMIN_PASSWORD must contain 12 to 256 characters'
+[[ "$password" != *$'\n'* && "$password" != *$'\r'* ]] || fail 'TINYAUTH_ADMIN_PASSWORD must not contain line breaks'
+[[ "$app_url" =~ ^https?://[^[:space:]]+$ ]] || fail 'TINYAUTH_APP_URL must be an absolute HTTP or HTTPS URL without whitespace'
+app_host="${app_url#*://}"
+app_host="${app_host%%/*}"
+app_host="${app_host%%:*}"
+[[ "$app_host" == *.* && "$app_host" =~ ^[A-Za-z0-9][A-Za-z0-9.-]*[A-Za-z0-9]$ ]] || fail 'TINYAUTH_APP_URL must use a domain name with at least two labels'
+[[ ! "$app_host" =~ ^[0-9.]+$ ]] || fail 'TINYAUTH_APP_URL must not use an IP address'
+[[ "$secure_cookie" == false || "$secure_cookie" == true ]] || fail 'TINYAUTH_SECURE_COOKIE must be true or false'
+
+install -d -m 0750 -o 1000 -g 1000 -- "$data_dir" "$data_dir/resources" "$data_dir/oidc"
+
+# Variables in this command are expanded by the shell inside the generator container.
+# shellcheck disable=SC2016
+generator_command='IFS= read -r password; exec tinyauth user create --username "$1" --password "$password"'
+generator_output="$(
+ printf '%s\n' "$password" |
+ "$DOCKER_BIN" run --rm -i --network none --read-only --user 1000:1000 \
+ --cap-drop ALL --security-opt no-new-privileges --env NO_COLOR=1 \
+ --entrypoint /bin/sh "$IMAGE" -ec \
+ "$generator_command" \
+ sh "$username"
+)"
+user_entry="$(printf '%s\n' "$generator_output" | sed -n 's/^TINYAUTH_AUTH_USERS=//p' | head -n 1)"
+[[ "$user_entry" =~ ^[A-Za-z0-9._-]{1,64}:\$2[aby]\$[0-9]{2}\$[./A-Za-z0-9]{53}$ ]] || fail 'Failed to generate a valid bcrypt user entry'
+
+umask 077
+temporary="$(mktemp "$data_dir/.users.tmp.XXXXXX")"
+trap 'rm -f -- "${temporary:-}"' EXIT
+printf '%s\n' "$user_entry" >"$temporary"
+chmod 0600 "$temporary"
+chown 1000:1000 "$temporary"
+mv -f -- "$temporary" "$users_file"
+trap - EXIT
+
+chown 1000:1000 "$data_dir" "$data_dir/resources" "$data_dir/oidc" "$users_file"
+chmod 0750 "$data_dir" "$data_dir/resources" "$data_dir/oidc"
+chmod 0600 "$users_file"
diff --git a/apps/tinyauth/latest/scripts/uninstall.sh b/apps/tinyauth/latest/scripts/uninstall.sh
new file mode 100755
index 000000000..5a3f6bd6b
--- /dev/null
+++ b/apps/tinyauth/latest/scripts/uninstall.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if command -v docker-compose >/dev/null 2>&1; then
+ docker-compose down --volumes --remove-orphans
+else
+ docker compose down --volumes --remove-orphans
+fi
diff --git a/apps/tinyauth/latest/scripts/upgrade.sh b/apps/tinyauth/latest/scripts/upgrade.sh
new file mode 100755
index 000000000..9879df4c3
--- /dev/null
+++ b/apps/tinyauth/latest/scripts/upgrade.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+set -euo pipefail
+exit 0
diff --git a/apps/tinyauth/logo.png b/apps/tinyauth/logo.png
new file mode 100644
index 000000000..bb4c431cd
Binary files /dev/null and b/apps/tinyauth/logo.png differ