diff --git a/apps/actual-budget/26.7.0/.env.sample b/apps/actual-budget/26.7.0/.env.sample new file mode 100644 index 000000000..211261027 --- /dev/null +++ b/apps/actual-budget/26.7.0/.env.sample @@ -0,0 +1,3 @@ +PANEL_APP_PORT_HTTP=5006 +APP_DATA_DIR=./data +CONTAINER_NAME= diff --git a/apps/actual-budget/26.7.0/data.yml b/apps/actual-budget/26.7.0/data.yml new file mode 100644 index 000000000..5fa1df05a --- /dev/null +++ b/apps/actual-budget/26.7.0/data.yml @@ -0,0 +1,35 @@ +additionalProperties: + formFields: + - default: 5006 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port + labelZh: 端口 + label: + en: Port + zh: 端口 + zh-Hant: 埠 + ja: ポート + ko: 포트 + ru: Порт + ms: Port + pt-br: Porta + required: true + rule: paramPort + type: number + - default: ./data + edit: true + envKey: APP_DATA_DIR + labelEn: Data Directory + labelZh: 数据目录 + label: + en: Data Directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori Data + pt-br: Diretório de dados + required: true + type: text diff --git a/apps/actual-budget/26.7.0/data/.gitkeep b/apps/actual-budget/26.7.0/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/actual-budget/26.7.0/docker-compose.yml b/apps/actual-budget/26.7.0/docker-compose.yml new file mode 100644 index 000000000..2429279cc --- /dev/null +++ b/apps/actual-budget/26.7.0/docker-compose.yml @@ -0,0 +1,31 @@ +services: + actual-budget: + image: "actualbudget/actual-server:26.7.0@sha256:e18b7fbfec6157a368fad4146563f397502e9da70a120aeaeac63b4977405d1c" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + user: "1001:1001" + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:5006" + read_only: true + tmpfs: + - /tmp:size=64m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + volumes: + - "${APP_DATA_DIR}:/data" + healthcheck: + test: ["CMD-SHELL", "node scripts/health-check.js"] + interval: 60s + timeout: 10s + retries: 3 + start_period: 20s + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/actual-budget/26.7.0/scripts/init.sh b/apps/actual-budget/26.7.0/scripts/init.sh new file mode 100755 index 000000000..f53fbbc72 --- /dev/null +++ b/apps/actual-budget/26.7.0/scripts/init.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" + +read_env_value() { + local key="$1" + local value + + [[ -f "$ENV_FILE" ]] || return 0 + 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" +} + +raw="${APP_DATA_DIR:-}" +if [[ -z "$raw" ]]; then + raw="$(read_env_value APP_DATA_DIR)" +fi +raw="${raw:-./data}" + +[[ -n "$raw" ]] || { + printf 'APP_DATA_DIR must not be empty\n' >&2 + exit 1 +} +if [[ "$raw" = /* ]]; then + printf 'APP_DATA_DIR must be relative to the application version directory\n' >&2 + exit 1 +fi + +path="$(realpath -m -- "$ROOT_DIR/${raw#./}")" +case "$path" in + "$ROOT_DIR"/*) ;; + *) + printf 'APP_DATA_DIR must remain inside the application version directory\n' >&2 + exit 1 + ;; +esac + +install -d -m 0700 "$path" +path="$(realpath -e -- "$path")" +case "$path" in + "$ROOT_DIR"/*) ;; + *) + printf 'APP_DATA_DIR resolves outside the application version directory\n' >&2 + exit 1 + ;; +esac + +chmod 0700 "$path" +chown -R --no-dereference 1001:1001 "$path" diff --git a/apps/actual-budget/26.7.0/scripts/uninstall.sh b/apps/actual-budget/26.7.0/scripts/uninstall.sh new file mode 100755 index 000000000..6e877466e --- /dev/null +++ b/apps/actual-budget/26.7.0/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +set -euo pipefail diff --git a/apps/actual-budget/26.7.0/scripts/upgrade.sh b/apps/actual-budget/26.7.0/scripts/upgrade.sh new file mode 100755 index 000000000..692e341e2 --- /dev/null +++ b/apps/actual-budget/26.7.0/scripts/upgrade.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +"$(dirname "$0")/init.sh" diff --git a/apps/actual-budget/README.md b/apps/actual-budget/README.md new file mode 100644 index 000000000..77927b1d9 --- /dev/null +++ b/apps/actual-budget/README.md @@ -0,0 +1,53 @@ +# Actual Budget + +## 产品介绍 + +Actual Budget 是隐私优先的开源个人财务应用,提供信封预算、账户与交易管理、规则、报表和多设备同步。 + +## 主要功能 + +- 信封预算与灵活预算管理 +- 账户、交易、分类、规则和报表 +- 预算导入导出、本地备份与多设备同步 + +## 访问说明 + +安装后通过 `http://<服务器 IP>:<端口>` 访问,实际端口以 `PANEL_APP_PORT_HTTP` 为准。首次打开时应立即设置服务器密码;在完成密码设置和反向代理 HTTPS 配置前,不要将端口暴露到不可信网络。 + +## 数据持久化 + +`APP_DATA_DIR` 保存服务器账户数据库、预算文件和同步数据。该路径必须位于应用版本目录内;初始化脚本会拒绝绝对路径、路径穿越和符号链接逃逸,并为上游容器用户 `1001:1001` 准备权限。卸载不会删除该目录,请定期独立备份。 + +## 安全与部署风险 + +- 容器以 UID/GID `1001:1001` 运行,丢弃全部 Linux capabilities,并启用只读根文件系统和 `no-new-privileges`。 +- 当前官方 `26.7.0` 镜像的新鲜 Trivy 扫描仍包含上游未修复漏洞。其中两份受 `CVE-2026-59873` 影响的 `node-tar` 分别属于 npm CLI 和仅由 `node-gyp` 导入的构建链;Actual 的运行时导入、备份和同步上传路径使用 `AdmZip` 或限长后原样存储,未调用 `node-tar`。该结论是默认运行路径例外,不代表镜像中不存在漏洞。 +- 如果在容器内手动运行 npm/node-gyp、增加执行外部命令的插件或改变官方入口,上述可达性结论将不再成立。镜像摘要变更时必须重新扫描。 + +## Introduction + +Actual Budget is a privacy-focused open-source personal finance app with envelope budgeting, account and transaction management, rules, reports, and multi-device synchronization. + +## Features + +- Envelope and flexible budgeting +- Accounts, transactions, categories, rules, and reports +- Budget import/export, local backups, and multi-device synchronization + +## Usage Notes + +- Access the service at `http://:` and set the server password immediately on first use. Do not expose it to an untrusted network before password setup and HTTPS reverse-proxy configuration are complete. +- `APP_DATA_DIR` stores the account database, budget files, and synchronization data. It must remain inside the application version directory and should be backed up independently. +- The container runs as UID/GID `1001:1001`, drops all Linux capabilities, and uses a read-only root filesystem plus `no-new-privileges`. + +## Security Note + +The current official image contains upstream vulnerabilities. Two `node-tar` copies affected by `CVE-2026-59873` belong to the npm CLI and the `node-gyp` build chain; Actual's runtime import, backup, and sync-upload paths use `AdmZip` or bounded opaque storage and do not invoke them. This is a default-path reachability exception, not a claim that the packages are absent. Running npm/node-gyp inside the container or replacing the official entrypoint invalidates the exception. + +## References + +- Project: +- Docker installation: +- Official Compose: +- License: (MIT) +- Security advisory: diff --git a/apps/actual-budget/data.yml b/apps/actual-budget/data.yml new file mode 100644 index 000000000..9109f6d6d --- /dev/null +++ b/apps/actual-budget/data.yml @@ -0,0 +1,31 @@ +name: Actual Budget +tags: + - 财务 +title: 隐私优先的开源个人财务应用 +description: 隐私优先的开源个人财务应用 +additionalProperties: + key: actual-budget + name: Actual Budget + tags: + - Tool + shortDescZh: 隐私优先的开源个人财务应用 + shortDescEn: Privacy-focused open-source personal finance app + description: + en: Privacy-focused open-source personal finance app + zh: 隐私优先的开源个人财务应用 + zh-Hant: 隱私優先的開源個人理財應用程式 + ja: プライバシー重視のオープンソース個人資産管理アプリ + ko: 개인정보 보호를 중시하는 오픈소스 개인 재무 앱 + ru: Конфиденциальное приложение с открытым кодом для личных финансов + ms: Aplikasi kewangan peribadi sumber terbuka yang mengutamakan privasi + pt-br: Aplicativo de finanças pessoais de código aberto com foco em privacidade + type: website + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://actualbudget.org/ + github: https://github.com/actualbudget/actual + document: https://actualbudget.org/docs/install/docker/ + architectures: + - amd64 + - arm64 diff --git a/apps/actual-budget/latest/.env.sample b/apps/actual-budget/latest/.env.sample new file mode 100644 index 000000000..211261027 --- /dev/null +++ b/apps/actual-budget/latest/.env.sample @@ -0,0 +1,3 @@ +PANEL_APP_PORT_HTTP=5006 +APP_DATA_DIR=./data +CONTAINER_NAME= diff --git a/apps/actual-budget/latest/data.yml b/apps/actual-budget/latest/data.yml new file mode 100644 index 000000000..5fa1df05a --- /dev/null +++ b/apps/actual-budget/latest/data.yml @@ -0,0 +1,35 @@ +additionalProperties: + formFields: + - default: 5006 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port + labelZh: 端口 + label: + en: Port + zh: 端口 + zh-Hant: 埠 + ja: ポート + ko: 포트 + ru: Порт + ms: Port + pt-br: Porta + required: true + rule: paramPort + type: number + - default: ./data + edit: true + envKey: APP_DATA_DIR + labelEn: Data Directory + labelZh: 数据目录 + label: + en: Data Directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori Data + pt-br: Diretório de dados + required: true + type: text diff --git a/apps/actual-budget/latest/data/.gitkeep b/apps/actual-budget/latest/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/actual-budget/latest/docker-compose.yml b/apps/actual-budget/latest/docker-compose.yml new file mode 100644 index 000000000..cc2cb8343 --- /dev/null +++ b/apps/actual-budget/latest/docker-compose.yml @@ -0,0 +1,31 @@ +services: + actual-budget: + image: "actualbudget/actual-server:latest@sha256:e18b7fbfec6157a368fad4146563f397502e9da70a120aeaeac63b4977405d1c" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + user: "1001:1001" + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:5006" + read_only: true + tmpfs: + - /tmp:size=64m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + volumes: + - "${APP_DATA_DIR}:/data" + healthcheck: + test: ["CMD-SHELL", "node scripts/health-check.js"] + interval: 60s + timeout: 10s + retries: 3 + start_period: 20s + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/actual-budget/latest/scripts/init.sh b/apps/actual-budget/latest/scripts/init.sh new file mode 100755 index 000000000..f53fbbc72 --- /dev/null +++ b/apps/actual-budget/latest/scripts/init.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" + +read_env_value() { + local key="$1" + local value + + [[ -f "$ENV_FILE" ]] || return 0 + 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" +} + +raw="${APP_DATA_DIR:-}" +if [[ -z "$raw" ]]; then + raw="$(read_env_value APP_DATA_DIR)" +fi +raw="${raw:-./data}" + +[[ -n "$raw" ]] || { + printf 'APP_DATA_DIR must not be empty\n' >&2 + exit 1 +} +if [[ "$raw" = /* ]]; then + printf 'APP_DATA_DIR must be relative to the application version directory\n' >&2 + exit 1 +fi + +path="$(realpath -m -- "$ROOT_DIR/${raw#./}")" +case "$path" in + "$ROOT_DIR"/*) ;; + *) + printf 'APP_DATA_DIR must remain inside the application version directory\n' >&2 + exit 1 + ;; +esac + +install -d -m 0700 "$path" +path="$(realpath -e -- "$path")" +case "$path" in + "$ROOT_DIR"/*) ;; + *) + printf 'APP_DATA_DIR resolves outside the application version directory\n' >&2 + exit 1 + ;; +esac + +chmod 0700 "$path" +chown -R --no-dereference 1001:1001 "$path" diff --git a/apps/actual-budget/latest/scripts/uninstall.sh b/apps/actual-budget/latest/scripts/uninstall.sh new file mode 100755 index 000000000..6e877466e --- /dev/null +++ b/apps/actual-budget/latest/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +set -euo pipefail diff --git a/apps/actual-budget/latest/scripts/upgrade.sh b/apps/actual-budget/latest/scripts/upgrade.sh new file mode 100755 index 000000000..692e341e2 --- /dev/null +++ b/apps/actual-budget/latest/scripts/upgrade.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +"$(dirname "$0")/init.sh" diff --git a/apps/actual-budget/logo.png b/apps/actual-budget/logo.png new file mode 100644 index 000000000..dd6e1c54b Binary files /dev/null and b/apps/actual-budget/logo.png differ