diff --git a/Apps/Beacon/docker-compose.yml b/Apps/Beacon/docker-compose.yml index 295036e..eb119b0 100644 --- a/Apps/Beacon/docker-compose.yml +++ b/Apps/Beacon/docker-compose.yml @@ -1,10 +1,10 @@ name: beacon services: - beacon: - image: ghcr.io/worph/beacon:1.0.11 - container_name: beacon - hostname: beacon + beacon-backend: + image: ghcr.io/worph/beacon:1.0.16 + container_name: beacon-backend + hostname: beacon-backend restart: unless-stopped environment: DISCOVERY_INTERVAL: "60" @@ -13,7 +13,14 @@ services: WEB_PORT: "9300" LOG_LEVEL: info PUBLIC_URL: "https://beacon-${APP_DOMAIN}/mcp" - AUTH_HASH: $AUTH_HASH + # Persistent auth token sourced from /DATA/AppData/beacon/.env (created by + # pre-install-cmd, symlinked into the compose dir). Survives uninstall, so + # the token shown to the user no longer rotates on reinstall. NOT $AUTH_HASH, + # which CasaOS regenerates on every install. + AUTH_HASH: ${BEACON_AUTH} + # Surfaces a "Manage remote access" link in the Beacon dashboard pointing + # at the AppShield-hosted OAuth admin page. Beacon itself does no OAuth. + OAUTH_ADMIN_URL: "https://beacon-${APP_DOMAIN}/AppShield/oauth" TZ: $TZ expose: - 9300 @@ -27,8 +34,15 @@ services: memory: 512M beacon-proxy: - image: ghcr.io/yundera/nginx-hash-lock:1.0.5 - container_name: beacon-proxy + # 2.0.5+ includes the MCP OAuth 2.1 broker (oidc-provider fronting Dex), + # activated by MCP_OAUTH_RESOURCE below. + image: ghcr.io/yundera/appshield:2.0.5 + container_name: beacon + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: beacon restart: unless-stopped expose: - 80 @@ -43,14 +57,28 @@ services: caddy_2.reverse_proxy: "{{upstreams 80}}" user: 0:0 environment: - AUTH_HASH: $AUTH_HASH - BACKEND_HOST: "beacon" + # env mode: take the persistent token from the env (interpolated from the + # symlinked .env) instead of letting AppShield generate/manage its own, so + # the proxy and the backend share the exact same value. + AUTH_HASH_MODE: "env" + AUTH_HASH: ${BEACON_AUTH} + BACKEND_HOST: "beacon-backend" BACKEND_PORT: "9300" LISTEN_PORT: "80" - USER: "ADMIN" - PASSWORD: $APP_DEFAULT_PASSWORD + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" + # MCP OAuth 2.1 broker (opt-in). When set, AppShield runs an OAuth/OIDC + # authorization server fronting Dex so claude.ai (via DCR) and clients like + # n8n (via a manually-created client) can reach /mcp with Bearer tokens. + # Requires an appshield image with MCP OAuth support (see image tag below). + MCP_OAUTH_RESOURCE: "https://beacon-${APP_DOMAIN}/mcp" + OAUTH_DATA_DIR: "/data/oauth" + volumes: + # Persist DCR/manual clients, signing keys and grants across redeploys. + - /DATA/AppData/beacon/oauth:/data/oauth depends_on: - - beacon + - beacon-backend networks: - pcs deploy: @@ -65,9 +93,22 @@ networks: external: true x-casaos: - main: beacon + main: beacon-backend index: / + # Generate a persistent auth token once and keep it in user space so it does + # not rotate on uninstall/reinstall. The real file lives under the app's + # AppData (which survives uninstall); a symlink exposes it as the compose + # project .env so ${BEACON_AUTH} interpolates for both services. + # Idempotent: only generates if not already present. + pre-install-cmd: | + mkdir -p /DATA/AppData/$AppID /DATA/AppData/$AppID/oauth /DATA/AppData/casaos/apps/$AppID && + ENV_FILE=/DATA/AppData/$AppID/.env && + if ! grep -q '^BEACON_AUTH=' "$ENV_FILE" 2>/dev/null; then + echo "BEACON_AUTH=$(openssl rand -hex 64)" >> "$ENV_FILE"; + fi && + ln -sf "$ENV_FILE" /DATA/AppData/casaos/apps/$AppID/.env + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Beacon/icon.png thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Beacon/icon.png screenshot_link: @@ -120,8 +161,7 @@ x-casaos: Beacon automatically discovers MCP servers on the `pcs` Docker network. **Authentication:** - - **Username:** `ADMIN` - - **Password:** `$APP_DEFAULT_PASSWORD` + The web dashboard is protected by your Yundera single sign-on — no extra login. **How it works:** 1. The aggregator broadcasts UDP discovery packets on port 9099 @@ -134,12 +174,15 @@ x-casaos: Connect your AI assistant to the aggregated endpoint to access all discovered MCP tools at once. **Option 1: Public URL (with hash auth)** + + Replace `YOUR_TOKEN` with the auth token shown in the Beacon dashboard. + The token is persistent — it stays the same across reinstalls. ```json { "mcpServers": { "beacon": { "type": "http", - "url": "https://beacon-${APP_DOMAIN}/mcp?hash=${AUTH_HASH}" + "url": "https://beacon-${APP_DOMAIN}/mcp?hash=YOUR_TOKEN" } } } @@ -153,7 +196,7 @@ x-casaos: "mcpServers": { "beacon": { "type": "http", - "url": "http://beacon:9099/mcp" + "url": "http://beacon-backend:9099/mcp" } } } @@ -173,8 +216,7 @@ x-casaos: Beacon decouvre automatiquement les serveurs MCP sur le reseau Docker `pcs`. **Authentification:** - - **Utilisateur:** `ADMIN` - - **Mot de passe:** `$APP_DEFAULT_PASSWORD` + Le tableau de bord web est protege par votre authentification unique Yundera (SSO) — aucune connexion supplementaire. **Comment ca marche:** 1. L'aggregateur envoie des paquets de decouverte UDP sur le port 9099 @@ -187,12 +229,16 @@ x-casaos: Connectez votre assistant IA a l'endpoint agrege pour acceder a tous les outils MCP decouverts. **Option 1: URL publique (avec hash auth)** + + Remplacez `YOUR_TOKEN` par le token d'authentification affiche dans le + tableau de bord Beacon. Le token est persistant — il reste identique + apres une reinstallation. ```json { "mcpServers": { "beacon": { "type": "http", - "url": "https://beacon-${APP_DOMAIN}/mcp?hash=${AUTH_HASH}" + "url": "https://beacon-${APP_DOMAIN}/mcp?hash=YOUR_TOKEN" } } } @@ -204,7 +250,7 @@ x-casaos: "mcpServers": { "beacon": { "type": "http", - "url": "http://beacon:9099/mcp" + "url": "http://beacon-backend:9099/mcp" } } } diff --git a/Apps/BrowserMCP/docker-compose.yml b/Apps/BrowserMCP/docker-compose.yml new file mode 100644 index 0000000..8b06636 --- /dev/null +++ b/Apps/BrowserMCP/docker-compose.yml @@ -0,0 +1,280 @@ +name: browsermcp +services: + # Main container — Chromium + Playwright + MCP server + noVNC viewer. + # Reachable at browsermcp-backend:9746 on the pcs network. Beacon discovery on UDP 9099. + browsermcp-backend: + image: ghcr.io/worph/browser-mcp:1.1.4 + container_name: browsermcp-backend + hostname: browsermcp-backend + restart: unless-stopped + # Chromium needs a large /dev/shm or it crashes rendering big pages. + shm_size: "2gb" + # supervisord is PID 1 and MUST start as root so it can spawn Xvfb/x11vnc and + # drop privileges for its children. A non-root uid fails to manage the display. + user: "0:0" + expose: + - 9746 + - 9099 + environment: + - TZ=$TZ + - PORT=9746 + - DISCOVERY_PORT=9099 + - CONFIG_PATH=/app/data/config.json + - VNC_RESOLUTION=1280x720x24 + # Kill idle Chrome after this many ms (default 2h); respawns on next use. + - IDLE_TTL_MS=7200000 + volumes: + - type: bind + source: /DATA/AppData/browser-mcp/ + target: /app/data + networks: + - pcs + deploy: + resources: + limits: + cpus: "2" + memory: 2G + + # Nginx-hash-lock provides perimeter authentication for the web UI. + # The app itself trusts the network (auth is delegated to this sidecar), so the + # browsermcp-backend container is never exposed directly — only this proxy gets caddy labels. + browsermcp-proxy: + image: ghcr.io/yundera/appshield:2.0.3 + container_name: browsermcp + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: browsermcp + restart: unless-stopped + user: "0:0" + expose: + - 80 + environment: + # /mcp is reachable with just the hash (AI clients); everything else + # (web UI, noVNC, /api) sits behind the ADMIN basic-auth prompt. + ALLOWED_PATHS: "mcp" + AUTH_HASH: $AUTH_HASH + BACKEND_HOST: browsermcp-backend + BACKEND_PORT: "9746" + LISTEN_PORT: "80" + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" + depends_on: + - browsermcp-backend + networks: + - pcs + labels: + caddy_0: browsermcp-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 80}}" + caddy_1: browsermcp-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 80}}" + caddy_2: browsermcp-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 80}}" + deploy: + resources: + limits: + cpus: "0.5" + memory: 256M + +networks: + pcs: + name: pcs + external: true + +x-casaos: + architectures: + - amd64 + main: browsermcp-backend + index: /?hash=$AUTH_HASH + author: Yundera + developer: Worph + category: Developer + description: + en_us: | + **A headless Chromium browser in the cloud, controlled by AI via MCP** + + Browser MCP bundles a full Chromium browser, Playwright automation, and an MCP (Model Context Protocol) server in a single container. It lets LLMs and AI assistants navigate websites, click elements, fill forms, take screenshots, and extract content programmatically. + + **What you can do:** + - Control a real Chromium browser from any MCP-compatible AI assistant + - Navigate websites, click elements, fill forms, and interact with web pages + - Take screenshots for visual feedback loops (screenshot → decide → act) + - Execute JavaScript in the browser context for advanced automation + - View the browser live via the built-in noVNC web viewer + - Access the Web UI dashboard for status monitoring + + **Think of it like:** A web browser in the cloud that AI can see and control. + + **Perfect if you:** Want to automate web browsing, scrape websites, test web apps, or give your AI assistant the ability to interact with any website. + fr_fr: | + **Un navigateur Chromium headless dans le cloud, controle par l'IA via MCP** + + Browser MCP regroupe un navigateur Chromium complet, l'automatisation Playwright et un serveur MCP (Model Context Protocol) dans un seul conteneur. Il permet aux LLMs et assistants IA de naviguer sur les sites web, cliquer sur des elements, remplir des formulaires, prendre des captures d'ecran et extraire du contenu par programmation. + + **Ce que vous pouvez faire:** + - Controler un vrai navigateur Chromium depuis tout assistant IA compatible MCP + - Naviguer sur les sites, cliquer, remplir des formulaires et interagir avec les pages + - Prendre des captures d'ecran pour des boucles de retour visuel (capture → decision → action) + - Executer du JavaScript dans le contexte du navigateur pour une automatisation avancee + - Visualiser le navigateur en direct via le viewer web noVNC integre + - Acceder au tableau de bord Web UI pour le suivi de l'etat + zh_cn: | + **云端无头 Chromium 浏览器,通过 MCP 由 AI 控制** + + Browser MCP 将完整的 Chromium 浏览器、Playwright 自动化和 MCP(模型上下文协议)服务器整合在一个容器中。它允许 LLM 和 AI 助手浏览网站、点击元素、填写表单、截屏并以编程方式提取内容。 + + **功能:** + - 从任何兼容 MCP 的 AI 助手控制真实的 Chromium 浏览器 + - 浏览网站、点击元素、填写表单、与网页交互 + - 截取屏幕截图用于视觉反馈循环(截图 → 决策 → 操作) + - 在浏览器上下文中执行 JavaScript 进行高级自动化 + - 通过内置 noVNC 网页查看器实时查看浏览器 + - 访问 Web UI 仪表板进行状态监控 + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/BrowserMCP/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/BrowserMCP/screenshot-1.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/BrowserMCP/screenshot-2.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/BrowserMCP/screenshot-3.png + tagline: + en_us: AI-controlled web browser via MCP + fr_fr: Navigateur web controle par l'IA via MCP + zh_cn: 通过 MCP 由 AI 控制的网页浏览器 + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/BrowserMCP/thumbnail.png + title: + en_us: Browser MCP + chips: + - key: mcp + name: + en_us: MCP + tips: + before_install: + en_us: | + **Getting Started** + + After installation, Chromium launches automatically inside a virtual display. + The Web UI is protected by your Yundera single sign-on — no extra login. + + **Web Interface:** + - **Main UI** — Dashboard with live noVNC browser view, status, and tool information + + **MCP Integration (for AI/LLMs):** + + Connect your AI assistant to control the browser. + + **Option 1: Public URL (with hash auth)** + ```json + { + "mcpServers": { + "browser-mcp": { + "type": "http", + "url": "https://browsermcp-${APP_DOMAIN}/mcp?hash=${AUTH_HASH}" + } + } + } + ``` + + **Option 2: Local Docker connection (no auth required)** + + For AI assistants running on the same server, connect directly via the Docker network: + ```json + { + "mcpServers": { + "browser-mcp": { + "type": "http", + "url": "http://browsermcp-backend:9746/mcp" + } + } + } + ``` + *Note: Requires the AI assistant to be on the `pcs` Docker network.* + + **Option 3: Via Beacon (aggregated)** + + If you have **Beacon** installed, Browser MCP auto-registers there and its tools + show up under the `browser-mcp__*` namespace — no extra config. + + **Available MCP Tools:** + | Tool | Description | + |------|-------------| + | `navigate` | Navigate the browser to a URL | + | `click` | Click on an element by CSS/text selector | + | `type` | Type text into an input element | + | `screenshot` | Take a screenshot of the current page | + | `evaluate` | Execute JavaScript in the browser context | + | `get_text` | Get the text content of an element | + | `get_page_content` | Get the full HTML of the current page | + | `wait_for` | Wait for an element to reach a specific state | + | `go_back` | Navigate back in browser history | + | `go_forward` | Navigate forward in browser history | + | `set_viewport` | Set the browser viewport size | + | `get_console_logs` | Get browser console log entries | + | `pdf` | Generate a PDF of the current page | + | `get_cookies` | Get browser cookies | + | `set_cookies` | Set browser cookies | + | `clear_cookies` | Clear all browser cookies | + + **Security:** + - The Web UI, live noVNC viewer, and `/api` endpoints sit behind your Yundera + single sign-on. Only `/mcp` is reachable with the hash token. + zh_cn: | + **快速开始** + + 安装后,Chromium 将在虚拟显示中自动启动。Web 界面受您的 Yundera 单点登录 (SSO) 保护——无需额外登录。 + + **Web 界面:** + - **主 UI** — 带有实时 noVNC 浏览器视图、状态和工具信息的仪表板 + + **MCP 集成(用于 AI/LLM):** + + **选项 1:公共 URL(使用 hash 认证)** + ```json + { + "mcpServers": { + "browser-mcp": { + "type": "http", + "url": "https://browsermcp-${APP_DOMAIN}/mcp?hash=${AUTH_HASH}" + } + } + } + ``` + + **选项 2:本地 Docker 连接(无需认证)** + ```json + { + "mcpServers": { + "browser-mcp": { + "type": "http", + "url": "http://browsermcp-backend:9746/mcp" + } + } + } + ``` + *注意:需要 AI 助手在 `pcs` Docker 网络上。* + + **选项 3:通过 Beacon(聚合)** + + 如果安装了 **Beacon**,Browser MCP 会自动注册,其工具显示在 `browser-mcp__*` 命名空间下,无需额外配置。 + + **可用 MCP 工具:** + | 工具 | 描述 | + |------|------| + | `navigate` | 将浏览器导航到 URL | + | `click` | 通过 CSS/文本选择器点击元素 | + | `type` | 在输入元素中输入文本 | + | `screenshot` | 截取当前页面截图 | + | `evaluate` | 在浏览器上下文中执行 JavaScript | + | `get_text` | 获取元素的文本内容 | + | `get_page_content` | 获取当前页面的完整 HTML | + | `wait_for` | 等待元素达到特定状态 | + | `go_back` | 在浏览器历史中后退 | + | `go_forward` | 在浏览器历史中前进 | + | `set_viewport` | 设置浏览器视口大小 | + | `get_console_logs` | 获取浏览器控制台日志 | + | `pdf` | 生成当前页面的 PDF | + | `get_cookies` | 获取浏览器 cookies | + | `set_cookies` | 设置浏览器 cookies | + | `clear_cookies` | 清除所有浏览器 cookies | diff --git a/Apps/BrowserMCP/icon.png b/Apps/BrowserMCP/icon.png new file mode 100644 index 0000000..e9a1dfc Binary files /dev/null and b/Apps/BrowserMCP/icon.png differ diff --git a/Apps/BrowserMCP/screenshot-1.png b/Apps/BrowserMCP/screenshot-1.png new file mode 100644 index 0000000..71363db Binary files /dev/null and b/Apps/BrowserMCP/screenshot-1.png differ diff --git a/Apps/BrowserMCP/screenshot-2.png b/Apps/BrowserMCP/screenshot-2.png new file mode 100644 index 0000000..a3485de Binary files /dev/null and b/Apps/BrowserMCP/screenshot-2.png differ diff --git a/Apps/BrowserMCP/screenshot-3.png b/Apps/BrowserMCP/screenshot-3.png new file mode 100644 index 0000000..0033819 Binary files /dev/null and b/Apps/BrowserMCP/screenshot-3.png differ diff --git a/Apps/BrowserMCP/thumbnail.png b/Apps/BrowserMCP/thumbnail.png new file mode 100644 index 0000000..e9a1dfc Binary files /dev/null and b/Apps/BrowserMCP/thumbnail.png differ diff --git a/Apps/ChronosMCP/docker-compose.yml b/Apps/ChronosMCP/docker-compose.yml index 246b747..bc3b6c5 100644 --- a/Apps/ChronosMCP/docker-compose.yml +++ b/Apps/ChronosMCP/docker-compose.yml @@ -1,10 +1,10 @@ name: chronosmcp services: - # Main server — reachable at chronosmcp:9054 on the pcs network - chronosmcp: + # Main server — reachable at chronosmcp-backend:9054 on the pcs network + chronosmcp-backend: image: ghcr.io/worph/chronos-mcp:1.0.7 - container_name: chronosmcp - hostname: chronosmcp + container_name: chronosmcp-backend + hostname: chronosmcp-backend user: $PUID:$PGID restart: unless-stopped expose: @@ -21,9 +21,15 @@ services: networks: - pcs - # Nginx-hash-lock provides perimeter authentication for the web UI + # AppShield provides perimeter authentication (SSO + hash) for the web UI chronosmcp-proxy: - image: ghcr.io/yundera/nginx-hash-lock:1.0.5 + image: ghcr.io/yundera/appshield:2.0.3 + container_name: chronosmcp + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: chronosmcp restart: unless-stopped cpu_shares: 50 expose: @@ -31,13 +37,14 @@ services: user: "root" environment: AUTH_HASH: $AUTH_HASH - BACKEND_HOST: chronosmcp + BACKEND_HOST: chronosmcp-backend BACKEND_PORT: "9054" LISTEN_PORT: "80" - USER: "ADMIN" - PASSWORD: $APP_DEFAULT_PASSWORD + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" depends_on: - - chronosmcp + - chronosmcp-backend networks: - pcs labels: @@ -59,7 +66,7 @@ x-casaos: architectures: - amd64 - arm64 - main: chronosmcp + main: chronosmcp-backend index: /?hash=$AUTH_HASH author: Yundera category: Utilities @@ -149,7 +156,7 @@ x-casaos: "mcpServers": { "chronos-mcp": { "type": "http", - "url": "http://chronosmcp:9054/mcp" + "url": "http://chronosmcp-backend:9054/mcp" } } } @@ -211,7 +218,7 @@ x-casaos: "mcpServers": { "chronos-mcp": { "type": "http", - "url": "http://chronosmcp:9054/mcp" + "url": "http://chronosmcp-backend:9054/mcp" } } } diff --git a/Apps/ClaudeCode/docker-compose.yml b/Apps/ClaudeCode/docker-compose.yml index 270f886..909e277 100644 --- a/Apps/ClaudeCode/docker-compose.yml +++ b/Apps/ClaudeCode/docker-compose.yml @@ -2,7 +2,7 @@ name: claude services: claude: - image: ghcr.io/worph/claude-code-container:1.0.23 + image: ghcr.io/worph/claude-code-container:1.0.26 container_name: claude restart: unless-stopped user: 0:0 @@ -61,19 +61,13 @@ services: caddy_2.3_handle.reverse_proxy: "{{upstreams 8080}}" environment: - AUTH_PASSWORD=$APP_DEFAULT_PASSWORD + # MCP automation enabled — this is the n8n/automation-facing app. + - MCP_ENABLED=true + # SANDBOXED variant: NO docker.sock, NO host /home/admin/.ssh mount, and NO + # full /DATA mount. This container has no path to the host VM — it is meant + # to be driven programmatically (e.g. n8n) via the MCP endpoint and is + # confined to its own workspace. volumes: - # Docker socket for container management - - type: bind - source: /var/run/docker.sock - target: /var/run/docker.sock - # SSH keys for root access to host VM (used for system maintenance) - - type: bind - source: /root/.ssh/ - target: /host_ssh/ - # Used for PCS integration - - type: bind - source: /DATA/ - target: /DATA/ # Claude Code workspace persistence - type: bind source: /DATA/AppData/claude/workspace/ @@ -112,23 +106,23 @@ x-casaos: en_us: | Claude Code Container - Web-based terminal interface for Claude Code (Anthropic's CLI for Claude). + This is the standard, sandboxed Claude Code app. It runs confined to its own workspace with NO Docker socket, NO host SSH access, and NO sudo account — it cannot touch the host VM. If you need full PCS administration and debugging with host control, install "Claude Code (root)" instead. + Features: - Web-based terminal access to Claude Code - Styled login page with session management - Persistent workspace volume - Claude starts automatically on connection - - Full administrative power: Docker socket access + SSH root access to host VM - - Can perform any maintenance task on the VM + - No host access — runs fully sandboxed + - Bonus: MCP endpoint for programmatic automation (n8n, other AI agents) Default password is auto-generated. Check the app settings for the AUTH_PASSWORD value. zh_cn: | Claude Code 容器 - Claude Code(Anthropic 的 Claude CLI)的 Web 终端界面。 - 功能: - - 基于 Web 的 Claude Code 终端访问 - - 带会话管理的登录页面 - - 持久化工作区 - - 连接时自动启动 Claude + 这是标准的沙盒版 Claude Code 应用,仅限于自己的工作区运行:没有 Docker 套接字、没有主机 SSH 访问、也没有 sudo 账户,无法触及主机虚拟机。如需对 PCS 进行管理和调试(具备主机控制权),请改装 “Claude Code (root)”。 + + 附加功能:MCP 端点,可用于程序化自动化(n8n 等 AI 代理)。 developer: Anthropic / Yundera icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/ClaudeCode/icon.png screenshot_link: @@ -136,8 +130,8 @@ x-casaos: - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/ClaudeCode/screenshot-2.png - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/ClaudeCode/screenshot-3.png tagline: - en_us: Web terminal for Claude Code - Anthropic's AI coding assistant - zh_cn: Claude Code 的 Web 终端 - Anthropic 的 AI 编程助手 + en_us: Web terminal for Claude Code - Anthropic's AI coding assistant (sandboxed, with bonus MCP) + zh_cn: Claude Code 的 Web 终端 - Anthropic 的 AI 编程助手(沙盒化,附带 MCP) thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/ClaudeCode/thumbnail.png title: en_us: Claude Code @@ -153,9 +147,11 @@ x-casaos: Default login password: `$APP_DEFAULT_PASSWORD` + **Sandboxed variant:** This app has no Docker socket, no host SSH, and no sudo account. It cannot perform host maintenance — use "Claude Code (root)" for that. This variant is for automation via MCP. + **MCP Integration (for AI/LLMs):** - Claude Code includes a Model Context Protocol (MCP) server that allows other AI assistants to send prompts and interact with this instance programmatically. + Claude Code includes a Model Context Protocol (MCP) server that allows other AI assistants (e.g. n8n) to send prompts and interact with this instance programmatically. **Option 1: Public URL (with Bearer auth)** ```json diff --git a/Apps/ClaudeCode/pre-install/CLAUDE.md b/Apps/ClaudeCode/pre-install/CLAUDE.md index 4dc2005..2efa22c 100644 --- a/Apps/ClaudeCode/pre-install/CLAUDE.md +++ b/Apps/ClaudeCode/pre-install/CLAUDE.md @@ -1,26 +1,26 @@ -# CLAUDE.md - CasaOS Server Assistant Context +# CLAUDE.md - Sandboxed Automation Assistant Context -You are running as a server assistant inside a CasaOS virtual machine. Your role is to help users debug, update, and maintain applications on their CasaOS home server. +You are running as a **sandboxed** Claude Code instance inside a CasaOS virtual machine. You are intended to be driven programmatically (e.g. by n8n or other AI agents) through the MCP endpoint, or used interactively via the web terminal. ## Your Environment -You are running in a Docker container on a CasaOS VM with **full administrative power**: -- Full access to `/DATA` - the user's entire data directory -- Access to the Docker socket (`/var/run/docker.sock`) - you can use Docker CLI commands -- SSH root access to the host VM by generating a keypair and adding your public key to `/host_ssh/authorized_keys` (comment: `claude-code-app`) -- A persistent workspace at `/home/claude/workspace` +You run in a Docker container with **no access to the host VM**: +- **No Docker socket** — you cannot manage other containers. +- **No host SSH / sudo** — you cannot run privileged operations on the VM. +- **No `/DATA` mount** — you only see your own persistent workspace. +- A persistent workspace at `/home/claude/workspace`. -This container is designed to handle **any maintenance task** on the VM, including system-level operations that require root access on the host. +This container is deliberately confined. If a task needs host-level maintenance (Docker, systemd, packages, the host filesystem), it is **out of scope here** — that is the job of the separate "Claude Code (root)" app. Do not attempt to escalate or reach the host; tell the user to use the root variant instead. ## MCP Server (Programmatic Access) -This container includes an **MCP (Model Context Protocol) server** that allows other AI agents and services to interact with you programmatically via JSON-RPC 2.0. +This container includes an **MCP (Model Context Protocol) server** so other agents/services (such as n8n) can interact with you via JSON-RPC 2.0. ### How It Works -- **Endpoint:** `/mcp` on port 8080 (same port as the web UI) +- **Endpoint:** `/mcp` on port 9090 (Bearer auth) - **Auth:** `Authorization: Bearer ` header -- **Internal endpoint** (from other containers on the `pcs` network): `http://claude:80/mcp` +- **Internal endpoint** (from other containers on the `pcs` network): `http://claude:9090/mcp` - **External endpoint** (via Caddy): `https://claude-/mcp` ### Available MCP Tools @@ -30,315 +30,23 @@ This container includes an **MCP (Model Context Protocol) server** that allows o | `query_claude` | Send a prompt to this Claude instance. Params: `prompt` (required), `continueSession` (default: true), `workdir`, `timeout` (default: 120s), `chatId`, `permissionCallbackUrl` | | `check_status` | Check availability: `{available, browserConnected, queryInProgress}` | -### Interactive Permission Prompts (Telegram Integration) - -When `query_claude` is called with `chatId` and `permissionCallbackUrl` parameters, this Claude instance will request user permission via Telegram before executing potentially dangerous operations (bash commands, file writes, etc.). - -**Flow:** -1. External service calls `query_claude` with `chatId` and `permissionCallbackUrl` -2. When Claude needs permission, the permission MCP server (`permission-mcp.js`) sends a POST to the callback URL -3. User sees Telegram inline keyboard with Allow/Deny/Always Allow buttons -4. User's decision is returned and Claude continues or aborts accordingly - -### Debugging MCP - -```bash -# Check MCP server status (from inside this container) -curl -s http://localhost:9090/mcp/status - -# Check WebSocket connections (browser sessions) -curl -s http://localhost:8080/internal/ws-status - -# Restart MCP server service -sudo s6-svc -r /run/service/mcp-server - -# Test MCP externally -curl -X POST http://localhost:8080/mcp \ - -H "Authorization: Bearer $AUTH_PASSWORD" \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' -``` - ### MCP Session History MCP queries use a separate working directory (`/home/claude/workspace/mcp`) by default. Conversation history is stored in `~/.claude/projects/` with path-mangled directory names: - Web UI sessions: `-home-claude-workspace/` - MCP sessions: `-home-claude-workspace-mcp/` -To share history between MCP and web UI, callers can set `"workdir": "/home/claude/workspace"` in the `query_claude` call. +To share history between MCP and the web UI, callers can set `"workdir": "/home/claude/workspace"` in the `query_claude` call. ## Your Role -You are a **VM/Server Assistant** whose primary responsibilities are: -- **Debugging**: Help diagnose issues with apps, containers, networking, and system configuration -- **Updating**: Assist with updating apps, Docker images, and system components -- **Maintenance**: Help with routine tasks like log analysis, cleanup, backups, and monitoring -- **Configuration**: Help modify app settings, Docker Compose files, and system configurations - -## /DATA Directory Structure - -All user data and application configurations are stored under `/DATA`: - -``` -/DATA/ -├── AppData/ # Application-specific data and configurations -│ ├── casaos/ # CasaOS system files -│ │ ├── 1/ # CasaOS configuration -│ │ └── apps/ # Installed app compose files -│ └── [AppName]/ # Per-app data directories -│ ├── config/ # App configuration files -│ ├── data/ # App-specific data -│ └── [other-dirs]/ # Additional app directories -├── Documents/ # User documents -├── Downloads/ # Download directory -├── Gallery/ # Photo and image storage -└── Media/ # Media files (movies, music, etc.) -``` - -### Key Directories - -- **`/DATA/AppData/[AppName]/`**: App-specific configs, databases, logs (system-managed) -- **`/DATA/AppData/casaos/apps/`**: Docker Compose files for installed apps -- **`/DATA/Documents/`**: User documents -- **`/DATA/Downloads/`**: Downloads directory -- **`/DATA/Gallery/`**: Photos and images -- **`/DATA/Media/`**: Media files for streaming apps - -## Docker Access - -You have full Docker CLI access via the mounted Docker socket. **All Docker commands require `sudo`.** - -```bash -# List running containers -sudo docker ps - -# View container logs -sudo docker logs - -# Restart a container -sudo docker restart - -# View resource usage -sudo docker stats - -# Inspect a container -sudo docker inspect - -# Execute commands in a container -sudo docker exec -it /bin/sh - -# Pull updated image -sudo docker pull - -# Docker Compose operations (from app directory) -cd /DATA/AppData/casaos/apps/ -sudo docker compose up -d -sudo docker compose down -sudo docker compose logs -f -``` - -## SSH Access to Host VM (Root Operations) - -You have SSH root access to the host VM for system-level maintenance tasks. The host's `~/.ssh/` directory is mounted at `/host_ssh/`. - -### ⚠️ CRITICAL: ALWAYS ASK USER PERMISSION FIRST - -**Before executing ANY SSH command or root-level operation on the host VM, you MUST:** -1. Explain to the user what operation you intend to perform -2. Explain why it requires host-level access -3. Describe any potential risks or side effects -4. Wait for explicit user confirmation before proceeding - -This is mandatory because these operations can affect the entire VM and all running services. - -### Setting Up SSH Access - -To send commands to the host, you must first generate your own SSH keypair and register it with the host. This only needs to be done once per session (the key persists in your workspace). - -```bash -# 1. Generate an SSH keypair (if one doesn't already exist) -if [ ! -f /home/claude/.ssh/id_ed25519 ]; then - mkdir -p /home/claude/.ssh - ssh-keygen -t ed25519 -C "claude-code-app" -f /home/claude/.ssh/id_ed25519 -N "" -fi - -# 2. Add your public key to the host's authorized_keys (with comment "claude-code-app") -# The /host_ssh/ directory is mounted from the host's /root/.ssh/ -if ! grep -q "claude-code-app" /host_ssh/authorized_keys 2>/dev/null; then - cat /home/claude/.ssh/id_ed25519.pub >> /host_ssh/authorized_keys -fi -``` - -### How to SSH into the Host - -Once your key is registered, you can SSH into the host: - -```bash -# SSH into the host VM for root operations -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 - -# Or if 172.17.0.1 doesn't resolve, use the gateway IP -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 -``` - -### Common Host-Level Operations - -These operations require SSH access to the host: - -```bash -# System updates -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 "apt update && apt upgrade -y" - -# Reboot the VM -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 "reboot" - -# Check system resources -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 "htop" # or "top -bn1" -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 "free -h" - -# View system logs -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 "journalctl -xe" - -# Manage systemd services -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 "systemctl status docker" -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 "systemctl restart docker" - -# Network configuration -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 "ip addr" -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 "netstat -tulpn" - -# Disk management -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 "lsblk" -ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@172.17.0.1 "fdisk -l" -``` - -### When to Use SSH vs Docker - -| Task | Method | -|------|--------| -| App management (start/stop/logs) | Docker CLI | -| Container configuration | Docker CLI | -| System package updates | SSH to host | -| Kernel/OS updates | SSH to host | -| Hardware/disk management | SSH to host | -| Network interface config | SSH to host | -| Systemd service management | SSH to host | -| VM reboot/shutdown | SSH to host | - -## CasaOS Overview - -CasaOS is an open-source home server operating system that provides: -- **Web-based app store** for easy Docker app installation -- **Automatic container management** with Docker Compose -- **Volume management** with automatic directory creation and permissions -- **User management** with PUID/PGID for proper file ownership - -### CasaOS System Variables - -Apps use these system variables: -- `$PUID` / `$PGID`: User/Group IDs for file permissions -- `$TZ`: Timezone -- `$default_pwd`: Auto-generated default password -- `$domain`: The instance domain -- `$AppID`: Application name/identifier - -### CasaOS Image - -This CasaOS instance is based on [Yundera/casa-img](https://github.com/Yundera/casa-img), which packages CasaOS as a Docker container with: -- All CasaOS modules (UI, Gateway, AppManagement, LocalStorage, etc.) -- Automatic admin user creation -- Docker socket access for container management -- Proper volume and permission handling - -## Caddy Integration with nsl.sh - -Apps get secure HTTPS access via Caddy reverse proxy with Docker labels. Free subdomains are provided by nsl.sh. - -### How It Works - -Caddy automatically discovers containers and routes traffic based on labels: -- **Docker label discovery**: Containers with `caddy=` labels are automatically routed -- **Automatic HTTPS**: All apps get valid SSL certificates via Let's Encrypt -- **Clean URLs**: Apps accessible at `https://appname-username.nsl.sh/` -- **Free subdomains**: nsl.sh provides free `*.nsl.sh` subdomains for all Yundera users - -### URL Patterns - -Apps are accessible via clean HTTPS URLs: -- **Clean URL**: `https://appname-username.nsl.sh/` - -### Label Format - -```yaml -labels: - - "caddy=appname-${APP_DOMAIN}" - - "caddy.reverse_proxy={{upstreams 80}}" -``` - -## Common Maintenance Tasks - -### Viewing App Logs -```bash -sudo docker logs -f -# Or from compose directory: -cd /DATA/AppData/casaos/apps/ -sudo docker compose logs -f -``` - -### Restarting an App -```bash -sudo docker restart -# Or full restart: -cd /DATA/AppData/casaos/apps/ -sudo docker compose down && sudo docker compose up -d -``` - -### Updating an App -```bash -cd /DATA/AppData/casaos/apps/ -sudo docker compose pull -sudo docker compose up -d -``` - -### Checking Disk Usage -```bash -df -h -du -sh /DATA/* -du -sh /DATA/AppData/* -sudo docker system df -``` - -### Cleaning Up Docker -```bash -# Remove unused images -sudo docker image prune -a - -# Remove unused volumes (careful!) -sudo docker volume prune - -# Full cleanup -sudo docker system prune -a -``` - -### Network Debugging -```bash -# Check container networks -sudo docker network ls -sudo docker network inspect - -# Check container ports -sudo docker port - -# Test connectivity from container -sudo docker exec ping -sudo docker exec curl -``` +You are an **automation worker**. Typical responsibilities: +- **Coding tasks** inside your workspace (write, edit, refactor, run code). +- **Answering prompts** sent by upstream automations (n8n workflows, other agents). +- **Producing structured output** that the caller can consume. ## Important Notes -- **🚨 ALWAYS ASK BEFORE ROOT/SSH OPERATIONS** - You MUST get explicit user permission before any SSH command or host-level operation. No exceptions. -- **Be careful with destructive operations** - always confirm with the user before deleting data -- **Preserve user data** - never overwrite existing configurations without asking -- **Check logs first** - most issues can be diagnosed from container logs -- **Test changes** - verify apps still work after configuration changes -- **Explain before executing** - for any significant operation, explain what you're about to do and why +- **Stay in your sandbox** — you have no host access by design. Don't suggest commands that assume Docker/SSH/sudo; they will fail here. +- **Preserve user data** — never overwrite existing files in the workspace without good reason. +- **Be deterministic for automation** — when called via MCP, prefer concise, parseable responses. diff --git a/Apps/ClaudeCodeRoot/docker-compose.yml b/Apps/ClaudeCodeRoot/docker-compose.yml new file mode 100644 index 0000000..0235e90 --- /dev/null +++ b/Apps/ClaudeCodeRoot/docker-compose.yml @@ -0,0 +1,200 @@ +name: clauderoot + +services: + clauderoot: + image: ghcr.io/worph/claude-code-container:1.0.26 + container_name: clauderoot + restart: unless-stopped + user: 0:0 + expose: + - 8080 + - 9090 + labels: + caddy_0: clauderoot-${APP_DOMAIN} + caddy_0.import: gateway_tls + # /login, /logout — no forward_auth, served by MCP server + caddy_0.0_handle: "/login*" + caddy_0.0_handle.reverse_proxy: "{{upstreams 9090}}" + caddy_0.1_handle: "/logout" + caddy_0.1_handle.reverse_proxy: "{{upstreams 9090}}" + # /mcp — no forward_auth, MCP server has its own Bearer auth. + # MCP automation itself is disabled via MCP_ENABLED=false below; with that + # set, the 9090 server returns 404 for /mcp (auth/login still served). + caddy_0.2_handle_path: "/mcp*" + caddy_0.2_handle_path.reverse_proxy: "{{upstreams 9090}}" + # Everything else — forward_auth via MCP, then proxy to ttyd + caddy_0.3_handle: "" + caddy_0.3_handle.forward_auth: "{{upstreams 9090}}" + caddy_0.3_handle.forward_auth.uri: /auth + caddy_0.3_handle.forward_auth.@unauthorized.status: "401" + caddy_0.3_handle.forward_auth.handle_response: "@unauthorized" + caddy_0.3_handle.forward_auth.handle_response.redir: "* /login" + caddy_0.3_handle.reverse_proxy: "{{upstreams 8080}}" + + caddy_1: clauderoot-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.0_handle: "/login*" + caddy_1.0_handle.reverse_proxy: "{{upstreams 9090}}" + caddy_1.1_handle: "/logout" + caddy_1.1_handle.reverse_proxy: "{{upstreams 9090}}" + caddy_1.2_handle_path: "/mcp*" + caddy_1.2_handle_path.reverse_proxy: "{{upstreams 9090}}" + caddy_1.3_handle: "" + caddy_1.3_handle.forward_auth: "{{upstreams 9090}}" + caddy_1.3_handle.forward_auth.uri: /auth + caddy_1.3_handle.forward_auth.@unauthorized.status: "401" + caddy_1.3_handle.forward_auth.handle_response: "@unauthorized" + caddy_1.3_handle.forward_auth.handle_response.redir: "* /login" + caddy_1.3_handle.reverse_proxy: "{{upstreams 8080}}" + + caddy_2: clauderoot-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.0_handle: "/login*" + caddy_2.0_handle.reverse_proxy: "{{upstreams 9090}}" + caddy_2.1_handle: "/logout" + caddy_2.1_handle.reverse_proxy: "{{upstreams 9090}}" + caddy_2.2_handle_path: "/mcp*" + caddy_2.2_handle_path.reverse_proxy: "{{upstreams 9090}}" + caddy_2.3_handle: "" + caddy_2.3_handle.forward_auth: "{{upstreams 9090}}" + caddy_2.3_handle.forward_auth.uri: /auth + caddy_2.3_handle.forward_auth.@unauthorized.status: "401" + caddy_2.3_handle.forward_auth.handle_response: "@unauthorized" + caddy_2.3_handle.forward_auth.handle_response.redir: "* /login" + caddy_2.3_handle.reverse_proxy: "{{upstreams 8080}}" + environment: + - AUTH_PASSWORD=$APP_DEFAULT_PASSWORD + # MCP automation disabled on the privileged (root) app. The 9090 auth layer + # (login/auth/logout) keeps running; only the /mcp endpoint is turned off. + - MCP_ENABLED=false + volumes: + # Docker socket for container management + - type: bind + source: /var/run/docker.sock + target: /var/run/docker.sock + # SSH keys for claude host user access to host VM (used for system maintenance via sudo) + - type: bind + source: /home/claude/.ssh/ + target: /host_ssh/ + # Used for PCS integration + - type: bind + source: /DATA/ + target: /DATA/ + # Claude Code workspace persistence + - type: bind + source: /DATA/AppData/clauderoot/workspace/ + target: /home/claude/workspace + # Claude config persistence (contains credentials, settings, and other important data) + - type: bind + source: /DATA/AppData/clauderoot/config/ + target: /home/claude/.claude + # Claude session state file (required for auth persistence across restarts) + - type: bind + source: /DATA/AppData/clauderoot/config/.claude.json + target: /home/claude/.claude.json + networks: + pcs: null + cpu_shares: 70 + deploy: + resources: + limits: + memory: 2048M + +networks: + pcs: + name: pcs + external: true + +x-casaos: + index: / + + architectures: + - amd64 + - arm64 + main: clauderoot + author: Yundera + category: Developer + description: + en_us: | + Claude Code Container (root) - Web-based terminal interface for Claude Code (Anthropic's CLI for Claude). + + This is the PRIVILEGED variant, intended for PCS administration and debugging. It has full control of the host VM. For sandboxed automation (e.g. n8n via MCP) without host access, install "Claude Code" instead. + + Features: + - Web-based terminal access to Claude Code + - Styled login page with session management + - Persistent workspace volume + - Claude starts automatically on connection + - Full administrative power: Docker socket access + SSH access (dedicated `claude` host user with NOPASSWD sudo) to host VM + - Can perform any maintenance task on the VM + + Default password is auto-generated. Check the app settings for the AUTH_PASSWORD value. + zh_cn: | + Claude Code 容器 - Claude Code(Anthropic 的 Claude CLI)的 Web 终端界面。 + + 功能: + - 基于 Web 的 Claude Code 终端访问 + - 带会话管理的登录页面 + - 持久化工作区 + - 连接时自动启动 Claude + developer: Anthropic / Yundera + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/ClaudeCodeRoot/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/ClaudeCodeRoot/screenshot-1.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/ClaudeCodeRoot/screenshot-2.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/ClaudeCodeRoot/screenshot-3.png + tagline: + en_us: Web terminal for Claude Code - Anthropic's AI coding assistant + zh_cn: Claude Code 的 Web 终端 - Anthropic 的 AI 编程助手 + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/ClaudeCodeRoot/thumbnail.png + title: + en_us: Claude Code (root) + zh_cn: Claude Code (root) + tips: + before_install: + en_us: | + Authentication options: + - Anthropic API key (https://console.anthropic.com/), OR + - Claude Pro/Max subscription (authenticate via OAuth on first run) + + Leave API key empty to use subscription auth. + + Default login password: `$APP_DEFAULT_PASSWORD` + + **This is the privileged (root) app for PCS administration and debugging.** It has full control of the host VM (Docker socket + SSH admin/sudo). + + **MCP automation is disabled on this app.** The programmatic MCP endpoint is intentionally not exposed here. If you want to drive Claude Code from n8n or other agents via MCP, install the standard "Claude Code" app instead. + # Runs as root INSIDE the casa-img container (only /DATA + docker.sock are in + # scope here), so host-user provisioning must break out to the host namespace + # via the docker socket: `docker run -v /:/host alpine:3.20 chroot /host ...`. + # This block ensures a dedicated `claude` host user + NOPASSWD sudo + a + # writable ~/.ssh exist (the /home/claude/.ssh mount above feeds the optional + # host-SSH feature). Idempotent: a no-op where they already do. + # ENSURE semantics: every step is idempotent and best-effort, and the whole + # script always exits 0 — provisioning the host `claude` user is only needed for + # the optional host-SSH feature, so it must NEVER block the app from installing. + pre-install-cmd: | + mkdir -p /DATA/AppData/clauderoot/workspace /DATA/AppData/clauderoot/config || true + touch /DATA/AppData/clauderoot/config/.claude.json || true + if [ ! -f /DATA/AppData/clauderoot/workspace/CLAUDE.md ]; then + wget -q https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/ClaudeCodeRoot/pre-install/CLAUDE.md -O /DATA/AppData/clauderoot/workspace/CLAUDE.md || true + fi + chown -R 1000:1000 /DATA/AppData/clauderoot || true + docker run --rm -v /:/host alpine:3.20 chroot /host /bin/sh -c ' + id claude >/dev/null 2>&1 || useradd -m -s /bin/bash -p "*" claude || true + # OpenSSH refuses ALL auth (incl. pubkey) for an account whose shadow + # password field is "!" — the default useradd leaves. "*" means "no + # password set" without tripping the locked-account check, so key-based + # SSH works. Run unconditionally to also repair accounts already + # provisioned locked. (passwd -u would NOT fix this: it refuses to + # unlock an account with no real hash behind the "!".) + usermod -p "*" claude 2>/dev/null || true + if [ ! -f /etc/sudoers.d/claude ]; then + echo "claude ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/claude && chmod 440 /etc/sudoers.d/claude + fi + install -d -m 700 -o claude -g claude /home/claude/.ssh 2>/dev/null || mkdir -p /home/claude/.ssh + touch /home/claude/.ssh/authorized_keys 2>/dev/null || true + chmod 600 /home/claude/.ssh/authorized_keys 2>/dev/null || true + chown claude:claude /home/claude/.ssh /home/claude/.ssh/authorized_keys 2>/dev/null || true + exit 0 + ' || echo "[pre-install] host claude provisioning skipped (host-SSH may be unavailable); continuing" + exit 0 diff --git a/Apps/ClaudeCodeRoot/icon.png b/Apps/ClaudeCodeRoot/icon.png new file mode 100644 index 0000000..17c93ac Binary files /dev/null and b/Apps/ClaudeCodeRoot/icon.png differ diff --git a/Apps/ClaudeCodeRoot/pre-install/CLAUDE.md b/Apps/ClaudeCodeRoot/pre-install/CLAUDE.md new file mode 100644 index 0000000..928109e --- /dev/null +++ b/Apps/ClaudeCodeRoot/pre-install/CLAUDE.md @@ -0,0 +1,344 @@ +# CLAUDE.md - CasaOS Server Assistant Context + +You are running as a server assistant inside a CasaOS virtual machine. Your role is to help users debug, update, and maintain applications on their CasaOS home server. + +## Your Environment + +You are running in a Docker container on a CasaOS VM with **full administrative power**: +- Full access to `/DATA` - the user's entire data directory +- Access to the Docker socket (`/var/run/docker.sock`) - you can use Docker CLI commands +- SSH access to the host VM as the `claude` user (use `sudo` for root operations) by generating a keypair and adding your public key to `/host_ssh/authorized_keys` (comment: `claude-code-app`) +- A persistent workspace at `/home/claude/workspace` + +This container is designed to handle **any maintenance task** on the VM, including system-level operations that require root access on the host. + +## MCP Server (Programmatic Access) + +This container includes an **MCP (Model Context Protocol) server** that allows other AI agents and services to interact with you programmatically via JSON-RPC 2.0. + +### How It Works + +- **Endpoint:** `/mcp` on port 8080 (same port as the web UI) +- **Auth:** `Authorization: Bearer ` header +- **Internal endpoint** (from other containers on the `pcs` network): `http://clauderoot:80/mcp` +- **External endpoint** (via Caddy): `https://clauderoot-/mcp` + +### Available MCP Tools + +| Tool | Description | +|------|-------------| +| `query_claude` | Send a prompt to this Claude instance. Params: `prompt` (required), `continueSession` (default: true), `workdir`, `timeout` (default: 120s), `chatId`, `permissionCallbackUrl` | +| `check_status` | Check availability: `{available, browserConnected, queryInProgress}` | + +### Interactive Permission Prompts (Telegram Integration) + +When `query_claude` is called with `chatId` and `permissionCallbackUrl` parameters, this Claude instance will request user permission via Telegram before executing potentially dangerous operations (bash commands, file writes, etc.). + +**Flow:** +1. External service calls `query_claude` with `chatId` and `permissionCallbackUrl` +2. When Claude needs permission, the permission MCP server (`permission-mcp.js`) sends a POST to the callback URL +3. User sees Telegram inline keyboard with Allow/Deny/Always Allow buttons +4. User's decision is returned and Claude continues or aborts accordingly + +### Debugging MCP + +```bash +# Check MCP server status (from inside this container) +curl -s http://localhost:9090/mcp/status + +# Check WebSocket connections (browser sessions) +curl -s http://localhost:8080/internal/ws-status + +# Restart MCP server service +sudo s6-svc -r /run/service/mcp-server + +# Test MCP externally +curl -X POST http://localhost:8080/mcp \ + -H "Authorization: Bearer $AUTH_PASSWORD" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' +``` + +### MCP Session History + +MCP queries use a separate working directory (`/home/claude/workspace/mcp`) by default. Conversation history is stored in `~/.claude/projects/` with path-mangled directory names: +- Web UI sessions: `-home-claude-workspace/` +- MCP sessions: `-home-claude-workspace-mcp/` + +To share history between MCP and web UI, callers can set `"workdir": "/home/claude/workspace"` in the `query_claude` call. + +## Your Role + +You are a **VM/Server Assistant** whose primary responsibilities are: +- **Debugging**: Help diagnose issues with apps, containers, networking, and system configuration +- **Updating**: Assist with updating apps, Docker images, and system components +- **Maintenance**: Help with routine tasks like log analysis, cleanup, backups, and monitoring +- **Configuration**: Help modify app settings, Docker Compose files, and system configurations + +## /DATA Directory Structure + +All user data and application configurations are stored under `/DATA`: + +``` +/DATA/ +├── AppData/ # Application-specific data and configurations +│ ├── casaos/ # CasaOS system files +│ │ ├── 1/ # CasaOS configuration +│ │ └── apps/ # Installed app compose files +│ └── [AppName]/ # Per-app data directories +│ ├── config/ # App configuration files +│ ├── data/ # App-specific data +│ └── [other-dirs]/ # Additional app directories +├── Documents/ # User documents +├── Downloads/ # Download directory +├── Gallery/ # Photo and image storage +└── Media/ # Media files (movies, music, etc.) +``` + +### Key Directories + +- **`/DATA/AppData/[AppName]/`**: App-specific configs, databases, logs (system-managed) +- **`/DATA/AppData/casaos/apps/`**: Docker Compose files for installed apps +- **`/DATA/Documents/`**: User documents +- **`/DATA/Downloads/`**: Downloads directory +- **`/DATA/Gallery/`**: Photos and images +- **`/DATA/Media/`**: Media files for streaming apps + +## Docker Access + +You have full Docker CLI access via the mounted Docker socket. **All Docker commands require `sudo`.** + +```bash +# List running containers +sudo docker ps + +# View container logs +sudo docker logs + +# Restart a container +sudo docker restart + +# View resource usage +sudo docker stats + +# Inspect a container +sudo docker inspect + +# Execute commands in a container +sudo docker exec -it /bin/sh + +# Pull updated image +sudo docker pull + +# Docker Compose operations (from app directory) +cd /DATA/AppData/casaos/apps/ +sudo docker compose up -d +sudo docker compose down +sudo docker compose logs -f +``` + +## SSH Access to Host VM (claude user + sudo) + +You have SSH access to the host VM as the `claude` user for system-level maintenance tasks. Run privileged commands with `sudo`. The `claude` user's `~/.ssh/` directory is mounted at `/host_ssh/`. + +### ⚠️ CRITICAL: ALWAYS ASK USER PERMISSION FIRST + +**Before executing ANY SSH command or root-level operation on the host VM, you MUST:** +1. Explain to the user what operation you intend to perform +2. Explain why it requires host-level access +3. Describe any potential risks or side effects +4. Wait for explicit user confirmation before proceeding + +This is mandatory because these operations can affect the entire VM and all running services. + +### Setting Up SSH Access + +To send commands to the host, you must first generate your own SSH keypair and register it with the host. This only needs to be done once per session (the key persists in your workspace). + +```bash +# 1. Generate an SSH keypair (if one doesn't already exist) +if [ ! -f /home/claude/.ssh/id_ed25519 ]; then + mkdir -p /home/claude/.ssh + ssh-keygen -t ed25519 -C "claude-code-app" -f /home/claude/.ssh/id_ed25519 -N "" +fi + +# 2. Add your public key to the host's authorized_keys (with comment "claude-code-app") +# The /host_ssh/ directory is mounted from the host claude user's /home/claude/.ssh/ +if ! grep -q "claude-code-app" /host_ssh/authorized_keys 2>/dev/null; then + cat /home/claude/.ssh/id_ed25519.pub >> /host_ssh/authorized_keys +fi +``` + +### How to SSH into the Host + +Once your key is registered, you can SSH into the host as `claude` and use `sudo` for privileged operations: + +```bash +# SSH into the host VM as the claude user +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 + +# Run privileged commands with sudo +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "sudo " +``` + +### Common Host-Level Operations + +These operations require SSH access to the host: + +```bash +# System updates +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "sudo apt update && sudo apt upgrade -y" + +# Reboot the VM +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "sudo reboot" + +# Check system resources +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "htop" # or "top -bn1" +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "free -h" + +# View system logs +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "sudo journalctl -xe" + +# Manage systemd services +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "systemctl status docker" +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "sudo systemctl restart docker" + +# Network configuration +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "ip addr" +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "sudo netstat -tulpn" + +# Disk management +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "lsblk" +ssh -i /home/claude/.ssh/id_ed25519 -o StrictHostKeyChecking=no claude@172.17.0.1 "sudo fdisk -l" +``` + +### When to Use SSH vs Docker + +| Task | Method | +|------|--------| +| App management (start/stop/logs) | Docker CLI | +| Container configuration | Docker CLI | +| System package updates | SSH to host | +| Kernel/OS updates | SSH to host | +| Hardware/disk management | SSH to host | +| Network interface config | SSH to host | +| Systemd service management | SSH to host | +| VM reboot/shutdown | SSH to host | + +## CasaOS Overview + +CasaOS is an open-source home server operating system that provides: +- **Web-based app store** for easy Docker app installation +- **Automatic container management** with Docker Compose +- **Volume management** with automatic directory creation and permissions +- **User management** with PUID/PGID for proper file ownership + +### CasaOS System Variables + +Apps use these system variables: +- `$PUID` / `$PGID`: User/Group IDs for file permissions +- `$TZ`: Timezone +- `$default_pwd`: Auto-generated default password +- `$domain`: The instance domain +- `$AppID`: Application name/identifier + +### CasaOS Image + +This CasaOS instance is based on [Yundera/casa-img](https://github.com/Yundera/casa-img), which packages CasaOS as a Docker container with: +- All CasaOS modules (UI, Gateway, AppManagement, LocalStorage, etc.) +- Automatic admin user creation +- Docker socket access for container management +- Proper volume and permission handling + +## Caddy Integration with nsl.sh + +Apps get secure HTTPS access via Caddy reverse proxy with Docker labels. Free subdomains are provided by nsl.sh. + +### How It Works + +Caddy automatically discovers containers and routes traffic based on labels: +- **Docker label discovery**: Containers with `caddy=` labels are automatically routed +- **Automatic HTTPS**: All apps get valid SSL certificates via Let's Encrypt +- **Clean URLs**: Apps accessible at `https://appname-username.nsl.sh/` +- **Free subdomains**: nsl.sh provides free `*.nsl.sh` subdomains for all Yundera users + +### URL Patterns + +Apps are accessible via clean HTTPS URLs: +- **Clean URL**: `https://appname-username.nsl.sh/` + +### Label Format + +```yaml +labels: + - "caddy=appname-${APP_DOMAIN}" + - "caddy.reverse_proxy={{upstreams 80}}" +``` + +## Common Maintenance Tasks + +### Viewing App Logs +```bash +sudo docker logs -f +# Or from compose directory: +cd /DATA/AppData/casaos/apps/ +sudo docker compose logs -f +``` + +### Restarting an App +```bash +sudo docker restart +# Or full restart: +cd /DATA/AppData/casaos/apps/ +sudo docker compose down && sudo docker compose up -d +``` + +### Updating an App +```bash +cd /DATA/AppData/casaos/apps/ +sudo docker compose pull +sudo docker compose up -d +``` + +### Checking Disk Usage +```bash +df -h +du -sh /DATA/* +du -sh /DATA/AppData/* +sudo docker system df +``` + +### Cleaning Up Docker +```bash +# Remove unused images +sudo docker image prune -a + +# Remove unused volumes (careful!) +sudo docker volume prune + +# Full cleanup +sudo docker system prune -a +``` + +### Network Debugging +```bash +# Check container networks +sudo docker network ls +sudo docker network inspect + +# Check container ports +sudo docker port + +# Test connectivity from container +sudo docker exec ping +sudo docker exec curl +``` + +## Important Notes + +- **🚨 ALWAYS ASK BEFORE ROOT/SSH OPERATIONS** - You MUST get explicit user permission before any SSH command or host-level operation. No exceptions. +- **Be careful with destructive operations** - always confirm with the user before deleting data +- **Preserve user data** - never overwrite existing configurations without asking +- **Check logs first** - most issues can be diagnosed from container logs +- **Test changes** - verify apps still work after configuration changes +- **Explain before executing** - for any significant operation, explain what you're about to do and why diff --git a/Apps/ClaudeCode/rationale.md b/Apps/ClaudeCodeRoot/rationale.md similarity index 100% rename from Apps/ClaudeCode/rationale.md rename to Apps/ClaudeCodeRoot/rationale.md diff --git a/Apps/ClaudeCodeRoot/screenshot-1.png b/Apps/ClaudeCodeRoot/screenshot-1.png new file mode 100644 index 0000000..786b4d0 Binary files /dev/null and b/Apps/ClaudeCodeRoot/screenshot-1.png differ diff --git a/Apps/ClaudeCodeRoot/screenshot-2.png b/Apps/ClaudeCodeRoot/screenshot-2.png new file mode 100644 index 0000000..3e56cda Binary files /dev/null and b/Apps/ClaudeCodeRoot/screenshot-2.png differ diff --git a/Apps/ClaudeCodeRoot/screenshot-3.png b/Apps/ClaudeCodeRoot/screenshot-3.png new file mode 100644 index 0000000..67613ec Binary files /dev/null and b/Apps/ClaudeCodeRoot/screenshot-3.png differ diff --git a/Apps/ClaudeCodeRoot/thumbnail.png b/Apps/ClaudeCodeRoot/thumbnail.png new file mode 100644 index 0000000..5a5ac3c Binary files /dev/null and b/Apps/ClaudeCodeRoot/thumbnail.png differ diff --git a/Apps/ConvertX/docker-compose.yml b/Apps/ConvertX/docker-compose.yml index ea5caeb..05940b7 100644 --- a/Apps/ConvertX/docker-compose.yml +++ b/Apps/ConvertX/docker-compose.yml @@ -2,8 +2,13 @@ name: convertx services: convertx: - image: ghcr.io/yundera/nginx-hash-lock:latest + image: ghcr.io/yundera/appshield:2.0.3 container_name: convertx + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: convertx restart: unless-stopped expose: - 80 @@ -22,6 +27,9 @@ services: BACKEND_HOST: "convertx-backend" BACKEND_PORT: "3000" LISTEN_PORT: "80" + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" depends_on: - convertx-backend networks: diff --git a/Apps/Docmost/docker-compose.yml b/Apps/Docmost/docker-compose.yml index 11650a0..3b168dc 100644 --- a/Apps/Docmost/docker-compose.yml +++ b/Apps/Docmost/docker-compose.yml @@ -77,6 +77,13 @@ services: PORT: 80 DATABASE_URL: postgresql://docmost:$APP_DEFAULT_PASSWORD@docmost-db:5432/docmost?schema=public REDIS_URL: redis://docmost-redis:6379 + # Email — delivered via the PCS-bundled SMTP relay (no auth, port 587) + MAIL_DRIVER: smtp + MAIL_FROM_ADDRESS: no-reply@${APP_DOMAIN} + MAIL_FROM_NAME: Docmost + SMTP_HOST: smtp + SMTP_PORT: "587" + SMTP_SECURE: "false" deploy: resources: limits: diff --git a/Apps/DocmostMCP/docker-compose.yml b/Apps/DocmostMCP/docker-compose.yml new file mode 100644 index 0000000..8e6ec4a --- /dev/null +++ b/Apps/DocmostMCP/docker-compose.yml @@ -0,0 +1,215 @@ +name: docmostmcp +services: + # Main MCP server — reachable at docmostmcp-backend:8000 on the pcs network + docmostmcp-backend: + image: ghcr.io/yundera/docmost-mcp:1.1.0 + container_name: docmostmcp-backend + hostname: docmostmcp-backend + user: "0:0" + restart: unless-stopped + expose: + - 8000 + environment: + - PUID=$PUID + - PGID=$PGID + - TZ=$TZ + - PORT=8000 + # Docmost connection — the image's entrypoint writes these into + # /data/config.json on first start. CasaOS prompts the user for any + # field with an empty default at install time. + - DOCMOST_BASE_URL=http://docmost + - DOCMOST_EMAIL= + - DOCMOST_PASSWORD= + - DOCMOST_TIMEOUT=30 + volumes: + # config.json (generated from env vars on first start) and token.json + # (cached JWT) live here. Once /data/config.json exists, the entrypoint + # leaves it alone — manual edits persist. + - type: bind + source: /DATA/AppData/docmostmcp/ + target: /data + networks: + - pcs + + # Beaconify sidecar — makes this MCP discoverable by the Beacon aggregator. + # Proxies the upstream MCP and replies to Beacon's UDP discovery on port 9099. + docmostmcp-beacon: + image: ghcr.io/worph/beaconify@sha256:63ce59fbe15365540cb3d45849a46b68b8132a2c1d605ea8b7b310d010540107 + container_name: docmostmcp-beacon + hostname: docmostmcp-beacon + restart: unless-stopped + cpu_shares: 20 + expose: + - 9099 + environment: + BEACONIFY_NAME: docmost-mcp + BEACONIFY_DESCRIPTION: "Docmost MCP — read, write, search, and move pages in a self-hosted Docmost instance" + BEACONIFY_UPSTREAM_URL: http://docmostmcp-backend:8000/mcp + depends_on: + - docmostmcp-backend + networks: + - pcs + + # AppShield provides perimeter authentication for the MCP endpoint: + # human SSO via OIDC for the admin panel, plus machine/API hash auth. + docmostmcp-proxy: + image: ghcr.io/yundera/appshield:2.0.3 + container_name: docmostmcp + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: docmostmcp + restart: unless-stopped + cpu_shares: 50 + expose: + - 80 + user: "root" + environment: + ALLOWED_PATHS: "mcp,sse,messages" + AUTH_HASH: $AUTH_HASH + BACKEND_HOST: docmostmcp-backend + BACKEND_PORT: "8000" + LISTEN_PORT: "80" + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" + depends_on: + - docmostmcp-backend + networks: + - pcs + labels: + caddy_0: docmostmcp-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 80}}" + caddy_1: docmostmcp-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 80}}" + caddy_2: docmostmcp-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 80}}" + +networks: + pcs: + name: pcs + external: true + +x-casaos: + architectures: + - amd64 + - arm64 + main: docmostmcp-backend + hostname: docmostmcp-${APP_DOMAIN} + scheme: https + port_map: "443" + index: /?hash=${AUTH_HASH} + envs: + - container: DOCMOST_BASE_URL + description: + en_us: Internal URL to reach Docmost. Default `http://docmost` works for the companion Docmost app on this server. + fr_fr: URL interne pour atteindre Docmost. Par defaut `http://docmost` fonctionne pour l'application Docmost compagnon sur ce serveur. + - container: DOCMOST_EMAIL + description: + en_us: Email of the Docmost user the MCP authenticates as. Use a dedicated service account, not your admin. + fr_fr: Email de l'utilisateur Docmost que le MCP utilise pour s'authentifier. Utilisez un compte de service dedie, pas votre compte admin. + - container: DOCMOST_PASSWORD + description: + en_us: Password for the Docmost user above. Stored in /DATA/AppData/docmostmcp/config.json. + fr_fr: Mot de passe pour l'utilisateur Docmost ci-dessus. Stocke dans /DATA/AppData/docmostmcp/config.json. + - container: DOCMOST_TIMEOUT + description: + en_us: HTTP timeout (seconds) for Docmost API calls. Default 30. + fr_fr: Delai d'expiration HTTP (en secondes) pour les appels a l'API Docmost. Par defaut 30. + author: Aptero + category: Developer + description: + en_us: | + **Docmost knowledge base control for AI assistants via MCP** + + Docmost MCP exposes the [Docmost](https://docmost.com) REST API as an MCP (Model Context Protocol) server, letting LLMs and AI assistants list and search spaces, read pages, create and update content, move and duplicate pages, and manage comments — all against your self-hosted Docmost instance. + + Wraps the community [`aleksvin8888/local-docmost-mcp`](https://github.com/aleksvin8888/local-docmost-mcp) Python server with a streamable-HTTP transport shim so any MCP-compatible client can connect (Claude Desktop, Claude Code, Cursor, VS Code, etc.). + + **Think of it like:** A programmable remote control for your Docmost wiki, driven by natural language. + + **Perfect if you:** Have a self-hosted Docmost and want AI assistants to read from, write to, or maintain it without paying for Docmost's Enterprise plan (which is the only tier with built-in MCP support). + fr_fr: | + **Controle de base de connaissances Docmost pour les assistants IA via MCP** + + Docmost MCP expose l'API REST de [Docmost](https://docmost.com) en tant que serveur MCP (Model Context Protocol), permettant aux LLMs et assistants IA de lister et rechercher des espaces, lire des pages, creer et mettre a jour du contenu, deplacer et dupliquer des pages, et gerer les commentaires — sur votre instance Docmost auto-hebergee. + + Enveloppe le serveur Python communautaire [`aleksvin8888/local-docmost-mcp`](https://github.com/aleksvin8888/local-docmost-mcp) avec un shim de transport streamable-HTTP pour que tout client compatible MCP puisse se connecter (Claude Desktop, Claude Code, Cursor, VS Code, etc.). + + **Parfait si vous:** Avez un Docmost auto-heberge et voulez que des assistants IA lisent, ecrivent ou maintiennent son contenu sans payer le plan Enterprise de Docmost (le seul niveau avec support MCP integre). + developer: Aptero + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/DocmostMCP/icon.png + tagline: + en_us: MCP server for Docmost — read, write, search, move pages + fr_fr: Serveur MCP pour Docmost — lire, ecrire, rechercher, deplacer des pages + title: + en_us: Docmost MCP + chips: + - key: mcp + name: + en_us: MCP + tips: + before_install: + en_us: | + **Fill the install form with your Docmost credentials** + + On the install screen, you'll see fields for `DOCMOST_EMAIL` and `DOCMOST_PASSWORD`. The first time the container starts it bakes those into `/DATA/AppData/docmostmcp/config.json`. After that, the bundled **config UI** at `/` is the easiest way to update them: click **Open** on the app card to land on the UI, where you can edit credentials, run a live **Test connection** against Docmost, and copy the ready-to-paste public MCP URL with the auth hash baked in. + + **Prerequisites:** + 1. A running **Docmost** instance reachable from the `pcs` Docker network (typically the companion Docmost app installed from this store) + 2. A Docmost user account — **strongly recommended** to create a dedicated service account in Docmost rather than reusing admin, since this MCP can read/write/move every page that account can see + + **MCP Integration (for AI/LLMs):** + + **Option 1: Public URL (with hash auth)** + ```json + { + "mcpServers": { + "docmost-mcp": { + "type": "http", + "url": "https://docmostmcp-${APP_DOMAIN}/mcp?hash=${AUTH_HASH}" + } + } + } + ``` + + **Option 2: Local Docker connection (no auth required)** + + For AI assistants running on the same server, connect directly via the Docker network: + ```json + { + "mcpServers": { + "docmost-mcp": { + "type": "http", + "url": "http://docmostmcp:8000/mcp" + } + } + } + ``` + *Note: Requires the AI assistant to be on the `pcs` Docker network.* + + **Option 3: Via Beacon (aggregated)** + + If you have **Beacon** installed, Docmost MCP auto-registers there via the bundled `beaconify` sidecar and its tools show up under the `docmost-mcp__*` namespace — no extra config. + + **Available MCP Tools:** + | Tool | Description | + |------|-------------| + | `list_spaces` | List all workspace spaces | + | `search_docs` | Full-text search across pages | + | `get_page` | Read page content as Markdown | + | `create_space` | Create a new space | + | `create_page` | Create a new page from Markdown | + | `update_page` | Update title/content (replace / append / prepend) | + | `duplicate_page` | Duplicate a page (recursive) | + | `move_page` | Move a page within the hierarchy | + | `move_page_to_space` | Move a page across spaces | + | `create_comment` | Add a comment to a page | + | `resolve_comment` | Resolve a comment with a note | + + **Security:** + - Docmost credentials are stored only in `/DATA/AppData/docmostmcp/config.json` on this server and never logged or returned via the API + - The MCP endpoint is gated by the URL hash token; the config UI is protected by your Yundera single sign-on diff --git a/Apps/DocmostMCP/icon.png b/Apps/DocmostMCP/icon.png new file mode 100644 index 0000000..1065d52 Binary files /dev/null and b/Apps/DocmostMCP/icon.png differ diff --git a/Apps/DocmostMCP/rationale.md b/Apps/DocmostMCP/rationale.md new file mode 100644 index 0000000..3d3e0bf --- /dev/null +++ b/Apps/DocmostMCP/rationale.md @@ -0,0 +1,28 @@ +# DocmostMCP — Rationale + +## `user: "0:0"` on the `docmostmcp` service + +The bundled image (`ghcr.io/yundera/docmost-mcp`) starts a small FastAPI +config-UI process and `mcp-proxy` as siblings, both reading and writing +`/data/config.json` and `/data/token.json` on the bind-mounted +`/DATA/AppData/docmostmcp/` directory. The cached JWT is rewritten on +every Docmost re-auth and the config file may be regenerated from env +vars on first start, so the container needs write access to that +directory without requiring the user to fix ownership before first +launch. Running as `0:0` avoids that prerequisite. All data stays under +`/DATA/AppData/docmostmcp/`. + +## `worph/beaconify` sidecar pinned by digest + +The Beacon sidecar (`ghcr.io/worph/beaconify`) currently only publishes +moving tags (`:latest` and `:main`). To satisfy the "no `:latest`" +guideline the sidecar is referenced via an immutable +`@sha256:…` digest. To upgrade: `docker pull ghcr.io/worph/beaconify:latest`, +read the new digest with +`docker inspect --format '{{index .RepoDigests 0}}' ghcr.io/worph/beaconify:latest`, +and bump the digest in `docker-compose.yml`. + +The sidecar exists so that — if Beacon is installed on the same server — +Docmost MCP auto-registers there under the `docmost-mcp__*` tool +namespace with zero user configuration. It's a small process (~5 MB +RSS) and harmless when Beacon isn't installed. diff --git a/Apps/DokuWiki/docker-compose.yml b/Apps/DokuWiki/docker-compose.yml new file mode 100644 index 0000000..c1cf940 --- /dev/null +++ b/Apps/DokuWiki/docker-compose.yml @@ -0,0 +1,210 @@ +name: dokuwiki + +services: + nginxhashlock: + image: ghcr.io/yundera/nginx-hash-lock:1.0.7 + container_name: dokuwiki-nginxhashlock + restart: unless-stopped + user: "0:0" + environment: + BACKEND_HOST: "dokuwiki" + BACKEND_PORT: "8080" + LISTEN_PORT: "80" + expose: + - 80 + labels: + caddy_0: dokuwiki-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 80}}" + caddy_1: dokuwiki-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 80}}" + caddy_2: dokuwiki-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 80}}" + depends_on: + - dokuwiki + deploy: + resources: + limits: + memory: 128M + cpu_shares: 80 + networks: + - pcs + dokuwiki: + image: dokuwiki/dokuwiki:2025-05-14b + container_name: dokuwiki + restart: unless-stopped + user: "0:0" + volumes: + - /DATA/AppData/$AppID/storage/:/storage + environment: + PHP_TIMEZONE: ${TZ:-UTC} + PHP_MEMORYLIMIT: 256M + PHP_UPLOADLIMIT: 128M + deploy: + resources: + limits: + memory: 512M + cpus: '0.5' + cpu_shares: 50 + networks: + - pcs + +networks: + pcs: + name: pcs + external: true + +x-casaos: + architectures: + - amd64 + - arm64 + author: Yundera Team + category: Wiki + developer: DokuWiki Community + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/DokuWiki/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/DokuWiki/screenshot-1.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/DokuWiki/screenshot-2.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/DokuWiki/screenshot-3.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/DokuWiki/thumbnail.png + index: / + tagline: + en_us: Simple wiki that doesn't require a database + ko_kr: 데이터베이스가 필요 없는 간단한 위키 + zh_cn: 不需要数据库的简单维基 + fr_fr: Wiki simple qui ne necessite pas de base de donnees + es_es: Wiki simple que no requiere base de datos + title: + en_us: DokuWiki + store_app_id: dokuwiki + main: nginxhashlock + webui_port: 80 + description: + en_us: | + **A simple, versatile wiki that needs no database** + + DokuWiki is a standards-compliant, easy to use wiki optimized for creating documentation. It stores all data in plain text files, so no database is required. Its clean and readable syntax makes it a favorite among administrators and developers. + + **What you can do:** + - Create and organize documentation with simple wiki syntax + - Manage access with built-in ACL (Access Control Lists) + - Extend functionality with hundreds of community plugins + - Customize appearance with themes and templates + - Track all changes with full revision history + - Search across all your wiki pages instantly + + **Perfect for:** Teams needing internal documentation, project wikis, knowledge bases, personal note-taking, and anyone who wants a lightweight wiki without database complexity. + ko_kr: | + **데이터베이스가 필요 없는 간단하고 다재다능한 위키** + + DokuWiki는 문서 작성에 최적화된 표준 호환, 사용하기 쉬운 위키입니다. 모든 데이터를 일반 텍스트 파일로 저장하므로 데이터베이스가 필요 없습니다. 깔끔하고 읽기 쉬운 문법으로 관리자와 개발자들에게 인기가 많습니다. + + **할 수 있는 것:** + - 간단한 위키 문법으로 문서 작성 및 정리 + - 내장 ACL(접근 제어 목록)로 접근 관리 + - 수백 개의 커뮤니티 플러그인으로 기능 확장 + - 테마와 템플릿으로 외관 커스터마이즈 + - 전체 수정 이력으로 모든 변경사항 추적 + - 모든 위키 페이지를 즉시 검색 + + **이런 분께 완벽해요:** 내부 문서가 필요한 팀, 프로젝트 위키, 지식 베이스, 개인 메모, 데이터베이스 복잡성 없이 가벼운 위키를 원하는 모든 분. + zh_cn: | + **不需要数据库的简单多功能维基** + + DokuWiki 是一个符合标准、易于使用的维基,专为创建文档而优化。它将所有数据存储在纯文本文件中,因此不需要数据库。其简洁可读的语法使其成为管理员和开发者的最爱。 + + **您可以做的事:** + - 使用简单的维基语法创建和组织文档 + - 使用内置 ACL(访问控制列表)管理访问权限 + - 使用数百个社区插件扩展功能 + - 使用主题和模板自定义外观 + - 通过完整修订历史跟踪所有更改 + - 即时搜索所有维基页面 + + **完美适合:** 需要内部文档的团队、项目维基、知识库、个人笔记,以及任何想要轻量级维基而不需要数据库复杂性的人。 + fr_fr: | + **Un wiki simple et polyvalent qui ne necessite pas de base de donnees** + + DokuWiki est un wiki conforme aux standards, facile a utiliser, optimise pour la creation de documentation. Il stocke toutes les donnees dans des fichiers texte brut, donc aucune base de donnees n'est requise. Sa syntaxe propre et lisible en fait un favori parmi les administrateurs et developpeurs. + + **Ce que vous pouvez faire:** + - Creer et organiser la documentation avec une syntaxe wiki simple + - Gerer l'acces avec les ACL integrees (listes de controle d'acces) + - Etendre les fonctionnalites avec des centaines de plugins communautaires + - Personnaliser l'apparence avec des themes et modeles + - Suivre tous les changements avec l'historique complet des revisions + - Rechercher instantanement dans toutes vos pages wiki + + **Parfait pour:** Les equipes ayant besoin de documentation interne, wikis de projets, bases de connaissances, prise de notes personnelles et toute personne souhaitant un wiki leger sans la complexite d'une base de donnees. + es_es: | + **Un wiki simple y versatil que no necesita base de datos** + + DokuWiki es un wiki compatible con estandares, facil de usar, optimizado para crear documentacion. Almacena todos los datos en archivos de texto plano, por lo que no se requiere base de datos. Su sintaxis limpia y legible lo convierte en favorito entre administradores y desarrolladores. + + **Lo que puedes hacer:** + - Crear y organizar documentacion con sintaxis wiki simple + - Gestionar acceso con ACL integradas (listas de control de acceso) + - Extender funcionalidad con cientos de plugins de la comunidad + - Personalizar apariencia con temas y plantillas + - Rastrear todos los cambios con historial completo de revisiones + - Buscar instantaneamente en todas tus paginas wiki + + **Perfecto para:** Equipos que necesitan documentacion interna, wikis de proyectos, bases de conocimiento, toma de notas personal y cualquiera que quiera un wiki ligero sin la complejidad de una base de datos. + tips: + before_install: + en_us: | + # Go to /install.php to set up your wiki + + After opening the app, the main page will load directly. You **must** manually go to `/install.php` in the URL bar to run the setup wizard. + + 1. Go to [https://dokuwiki-${APP_DOMAIN}/install.php](https://dokuwiki-${APP_DOMAIN}/install.php) + 2. Set your wiki name, superuser account, and password + 3. Choose your ACL policy (recommended: **Public Wiki**) + 4. Click Save — done! + + No database needed. All data is stored as plain text files. + ko_kr: | + # /install.php 에 직접 접속하여 위키를 설정하세요 + + 앱을 열면 메인 페이지가 바로 표시됩니다. 설정 마법사를 실행하려면 URL 주소창에 **직접** `/install.php`를 입력해야 합니다. + + 1. [https://dokuwiki-${APP_DOMAIN}/install.php](https://dokuwiki-${APP_DOMAIN}/install.php) 로 이동 + 2. 위키 이름, 관리자 계정, 비밀번호 설정 + 3. ACL 정책 선택 (권장: **Public Wiki**) + 4. 저장 클릭 — 완료! + + 데이터베이스 불필요. 모든 데이터는 텍스트 파일로 저장됩니다. + zh_cn: | + # 请手动访问 /install.php 来设置您的维基 + + 打开应用后,主页会直接加载。您**必须**在地址栏中手动输入 `/install.php` 来运行设置向导。 + + 1. 访问 [https://dokuwiki-${APP_DOMAIN}/install.php](https://dokuwiki-${APP_DOMAIN}/install.php) + 2. 设置维基名称、超级用户账户和密码 + 3. 选择 ACL 策略(推荐:**Public Wiki**) + 4. 点击保存 — 完成! + + 无需数据库。所有数据以纯文本文件存储。 + fr_fr: | + # Allez sur /install.php pour configurer votre wiki + + Apres l'ouverture de l'app, la page principale s'affiche directement. Vous **devez** saisir manuellement `/install.php` dans la barre d'adresse pour lancer l'assistant de configuration. + + 1. Allez sur [https://dokuwiki-${APP_DOMAIN}/install.php](https://dokuwiki-${APP_DOMAIN}/install.php) + 2. Definissez le nom du wiki, le compte super-utilisateur et le mot de passe + 3. Choisissez votre politique ACL (recommande: **Public Wiki**) + 4. Cliquez sur Enregistrer — termine! + + Aucune base de donnees necessaire. Toutes les donnees sont stockees en fichiers texte. + es_es: | + # Vaya a /install.php para configurar su wiki + + Al abrir la app, la pagina principal se carga directamente. **Debe** escribir manualmente `/install.php` en la barra de direcciones para ejecutar el asistente de configuracion. + + 1. Vaya a [https://dokuwiki-${APP_DOMAIN}/install.php](https://dokuwiki-${APP_DOMAIN}/install.php) + 2. Configure el nombre del wiki, cuenta de superusuario y contrasena + 3. Elija su politica ACL (recomendado: **Public Wiki**) + 4. Haga clic en Guardar — listo! + + No se necesita base de datos. Todos los datos se almacenan como archivos de texto. diff --git a/Apps/DokuWiki/icon.png b/Apps/DokuWiki/icon.png new file mode 100644 index 0000000..d32c199 Binary files /dev/null and b/Apps/DokuWiki/icon.png differ diff --git a/Apps/DokuWiki/rationale.md b/Apps/DokuWiki/rationale.md new file mode 100644 index 0000000..d08a806 --- /dev/null +++ b/Apps/DokuWiki/rationale.md @@ -0,0 +1,25 @@ +# DokuWiki — Rationale + +## What deviation / exception is being requested +Both services run as `user: "0:0"` (root). Authentication is handled via DokuWiki's first-launch setup wizard at `/install.php` (no pre-configured credentials). + +## Why it is necessary +- **DokuWiki**: The official image runs Apache as root and manages file permissions in `/storage`. Running as non-root causes permission errors when writing wiki pages, uploading media, and installing plugins. +- **nginx-hash-lock**: Used solely as a port proxy (8080 → 80), not for authentication. The official DokuWiki image hardcodes Apache on port 8080, which cannot be changed via environment variables. Caddy expects port 80, so nginx-hash-lock bridges the gap. + +## Security mitigations in place +- All volumes map exclusively to `/DATA/AppData/$AppID/` — no access to user directories +- No privileged mode, no elevated capabilities (cap_add removed) +- Memory limits on both services (nginx: 128M, DokuWiki: 512M) +- CPU limits on DokuWiki (0.5 cores) +- DokuWiki's built-in ACL (Access Control Lists) handles user authentication and authorization +- Setup wizard (`/install.php`) requires admin account creation on first launch + +## Alternatives considered and rejected +- `user: $PUID:$PGID` — causes Apache file ownership conflicts; DokuWiki cannot write pages or manage plugins +- Removing nginx-hash-lock — not possible because the upstream image hardcodes port 8080 and Caddy expects port 80 + +## Data protection +- All wiki data (pages, media, config) persists in `/DATA/AppData/$AppID/storage/` +- Plain text files — no database migration needed +- Data survives uninstall/reinstall diff --git a/Apps/DokuWiki/screenshot-1.png b/Apps/DokuWiki/screenshot-1.png new file mode 100644 index 0000000..e3becae Binary files /dev/null and b/Apps/DokuWiki/screenshot-1.png differ diff --git a/Apps/DokuWiki/screenshot-2.png b/Apps/DokuWiki/screenshot-2.png new file mode 100644 index 0000000..56d7b39 Binary files /dev/null and b/Apps/DokuWiki/screenshot-2.png differ diff --git a/Apps/DokuWiki/screenshot-3.png b/Apps/DokuWiki/screenshot-3.png new file mode 100644 index 0000000..9a2d390 Binary files /dev/null and b/Apps/DokuWiki/screenshot-3.png differ diff --git a/Apps/DokuWiki/thumbnail.png b/Apps/DokuWiki/thumbnail.png new file mode 100644 index 0000000..e3becae Binary files /dev/null and b/Apps/DokuWiki/thumbnail.png differ diff --git a/Apps/Guacamole/docker-compose.yml b/Apps/Guacamole/docker-compose.yml index 5a580fe..b60f1fb 100644 --- a/Apps/Guacamole/docker-compose.yml +++ b/Apps/Guacamole/docker-compose.yml @@ -130,6 +130,10 @@ x-casaos: | Username | Password | | -------- | -------- | | `guacadmin` | `guacadmin` | + + For better performance, you can access Guacamole directly at + https://guacamole-${APP_PUBLIC_IP_DASH}.sslip.io/guacamole/ instead of going + through the nsl.sh gateway. zh_cn: | 默认账号 diff --git a/Apps/Hubs/docker-compose.yml b/Apps/Hubs/docker-compose.yml new file mode 100644 index 0000000..b67da1f --- /dev/null +++ b/Apps/Hubs/docker-compose.yml @@ -0,0 +1,419 @@ +name: hubs + +services: + hubs-client: + image: hubsfoundation/hubs:stable-3111 + container_name: hubs-client + restart: unless-stopped + user: "0:0" + cpu_shares: 80 + environment: + DOMAIN: "${APP_DOMAIN}" + SUB_DOMAIN: "hubs" + turkeyCfg_thumbnail_server: "hubsnearspark-${APP_DOMAIN}" + turkeyCfg_base_assets_path: "https://hubsassets-${APP_DOMAIN}/hubs/" + turkeyCfg_non_cors_proxy_domains: "hubs-${APP_DOMAIN},hubsassets-${APP_DOMAIN}" + turkeyCfg_reticulum_server: "hubs-${APP_DOMAIN}" + turkeyCfg_cors_proxy_server: "hubs-${APP_DOMAIN}" + turkeyCfg_shortlink_domain: "hubs-${APP_DOMAIN}" + turkeyCfg_tier: "p1" + PUID: $PUID + PGID: $PGID + TZ: UTC + expose: + - 8080 + networks: + - hubs-net + - pcs + labels: + caddy_0: hubsassets-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "https://{{upstreams 8080}}" + caddy_0.reverse_proxy.transport: http + caddy_0.reverse_proxy.transport.tls: "" + caddy_0.reverse_proxy.transport.tls_insecure_skip_verify: "" + caddy_1: hubsassets-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "https://{{upstreams 8080}}" + caddy_1.reverse_proxy.transport: http + caddy_1.reverse_proxy.transport.tls: "" + caddy_1.reverse_proxy.transport.tls_insecure_skip_verify: "" + caddy_2: hubsassets-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "https://{{upstreams 8080}}" + caddy_2.reverse_proxy.transport: http + caddy_2.reverse_proxy.transport.tls: "" + caddy_2.reverse_proxy.transport.tls_insecure_skip_verify: "" + + db: + image: postgres:14-alpine + container_name: hubs-db + restart: unless-stopped + user: "0:0" + cpu_shares: 70 + environment: + POSTGRES_USER: $POSTGRES_USER + POSTGRES_PASSWORD: $POSTGRES_PASSWORD + POSTGRES_DB: $POSTGRES_DB + PUID: $PUID + PGID: $PGID + TZ: UTC + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + expose: + - 5432 + volumes: + - /DATA/AppData/hubs/pgdata:/var/lib/postgresql/data + networks: + - hubs-net + + hubs: + image: hubsfoundation/reticulum:stable-855 + container_name: hubs-reticulum + restart: unless-stopped + user: "0:0" + cpu_shares: 70 + environment: + POD_IP: "127.0.0.1" + POD_NAME: "reticulum-1" + turkeyCfg_POD_NS: "hubs" + turkeyCfg_HUB_DOMAIN: "hubs-${APP_DOMAIN}" + turkeyCfg_DOMAIN: "hubs-${APP_DOMAIN}" + turkeyCfg_RET_HOST: "hubs-${APP_DOMAIN}" + turkeyCfg_LINK_HOST: "hubslink-${APP_DOMAIN}" + turkeyCfg_ASSETS_HOST: "hubsassets-${APP_DOMAIN}" + turkeyCfg_DIALOG_HOST: "hubsdialog-${APP_DOMAIN}" + turkeyCfg_NEARSPARK_HOST: "hubsnearspark-${APP_DOMAIN}" + turkeyCfg_SPOKE_HOST: "hubsspoke-${APP_DOMAIN}" + turkeyCfg_DB_HOST: "db" + turkeyCfg_DB_HOST_T: "db" + turkeyCfg_APP_EMAIL: "${APP_EMAIL}" + turkeyCfg_MAILER_RELAY: "smtp" + turkeyCfg_MAILER_PORT: "587" + turkeyCfg_MAILER_TLS: "never" + turkeyCfg_DB_USER: $turkeyCfg_DB_USER + turkeyCfg_DB_PASS: $turkeyCfg_DB_PASS + turkeyCfg_DB_NAME: $turkeyCfg_DB_NAME + turkeyCfg_NODE_COOKIE: $turkeyCfg_NODE_COOKIE + turkeyCfg_GUARDIAN_KEY: $turkeyCfg_GUARDIAN_KEY + turkeyCfg_PHX_KEY: $turkeyCfg_PHX_KEY + turkeyCfg_DASHBOARD_ACCESS_KEY: $turkeyCfg_DASHBOARD_ACCESS_KEY + turkeyCfg_POSTGREST_PASSWORD: $turkeyCfg_POSTGREST_PASSWORD + turkeyCfg_PERMS_KEY: $turkeyCfg_PERMS_KEY + PUID: $PUID + PGID: $PGID + TZ: UTC + expose: + - 4000 + - 4001 + volumes: + - /DATA/AppData/hubs/retstorage:/storage + - /DATA/AppData/hubs/config.toml.template:/home/ret/config.toml.template:ro + depends_on: + db: + condition: service_healthy + networks: + - hubs-net + - pcs + labels: + caddy_0: hubs-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 4001}}" + caddy_1: hubs-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 4001}}" + caddy_2: hubs-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 4001}}" + caddy_3: hubslink-${APP_DOMAIN} + caddy_3.import: gateway_tls + caddy_3.reverse_proxy: "{{upstreams 4001}}" + caddy_4: hubslink-${APP_PUBLIC_IP_DASH}.nip.io + caddy_4.import: gateway_tls + caddy_4.reverse_proxy: "{{upstreams 4001}}" + caddy_5: hubslink-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_5.reverse_proxy: "{{upstreams 4001}}" + + postgrest: + image: mozillareality/postgrest:8 + container_name: hubs-postgrest + restart: unless-stopped + user: "0:0" + cpu_shares: 50 + environment: + PGRST_DB_URI: $PGRST_DB_URI + PGRST_JWT_SECRET: $PGRST_JWT_SECRET + PGRST_LOG_LEVEL: info + PGRST_DB_SCHEMA: ret0_admin + PGRST_DB_ANON_ROLE: postgres + PUID: $PUID + PGID: $PGID + TZ: UTC + expose: + - 3000 + depends_on: + db: + condition: service_healthy + networks: + - hubs-net + + spoke: + image: hubsfoundation/spoke:stable-95 + container_name: hubs-spoke + restart: unless-stopped + user: "0:0" + cpu_shares: 50 + environment: + DOMAIN: "${APP_DOMAIN}" + SUB_DOMAIN: "hubsspoke" + turkeyCfg_thumbnail_server: "hubsnearspark-${APP_DOMAIN}" + turkeyCfg_base_assets_path: "https://hubsspoke-${APP_DOMAIN}/spoke/" + turkeyCfg_non_cors_proxy_domains: "hubs-${APP_DOMAIN},hubsspoke-${APP_DOMAIN}" + turkeyCfg_reticulum_server: "hubs-${APP_DOMAIN}" + turkeyCfg_cors_proxy_server: "hubs-${APP_DOMAIN}" + turkeyCfg_shortlink_domain: "hubs-${APP_DOMAIN}" + turkeyCfg_hubs_server: "hubs-${APP_DOMAIN}" + PUID: $PUID + PGID: $PGID + TZ: UTC + expose: + - 8080 + networks: + - hubs-net + - pcs + labels: + caddy_0: hubsspoke-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "https://{{upstreams 8080}}" + caddy_0.reverse_proxy.transport: http + caddy_0.reverse_proxy.transport.tls: "" + caddy_0.reverse_proxy.transport.tls_insecure_skip_verify: "" + caddy_1: hubsspoke-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "https://{{upstreams 8080}}" + caddy_1.reverse_proxy.transport: http + caddy_1.reverse_proxy.transport.tls: "" + caddy_1.reverse_proxy.transport.tls_insecure_skip_verify: "" + caddy_2: hubsspoke-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "https://{{upstreams 8080}}" + caddy_2.reverse_proxy.transport: http + caddy_2.reverse_proxy.transport.tls: "" + caddy_2.reverse_proxy.transport.tls_insecure_skip_verify: "" + + dialog: + image: hubsfoundation/dialog:stable-331 + container_name: hubs-dialog + restart: unless-stopped + user: "0:0" + cpu_shares: 70 + environment: + AUTH_KEY: /app/certs/perms.pub.pem + HTTPS_CERT_FULLCHAIN: /app/certs/fullchain.pem + HTTPS_CERT_PRIVKEY: /app/certs/privkey.pem + INTERACTIVE: "false" + MEDIASOUP_MIN_PORT: 40000 + MEDIASOUP_MAX_PORT: 40050 + MEDIASOUP_ANNOUNCED_IP: "${APP_PUBLIC_IPV4}" + DEBUG: "*INFO* *WARN* *ERROR*" + PUID: $PUID + PGID: $PGID + TZ: UTC + volumes: + - /DATA/AppData/hubs/perms.pub.pem:/app/certs/perms.pub.pem:ro + command: ["node", "index.js"] + expose: + - 4443 + ports: + - "40000-40050:40000-40050/udp" + - "40000-40050:40000-40050/tcp" + networks: + - hubs-net + - pcs + labels: + caddy_0: hubsdialog-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "https://{{upstreams 4443}}" + caddy_0.reverse_proxy.transport: http + caddy_0.reverse_proxy.transport.tls: "" + caddy_0.reverse_proxy.transport.tls_insecure_skip_verify: "" + caddy_1: hubsdialog-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "https://{{upstreams 4443}}" + caddy_1.reverse_proxy.transport: http + caddy_1.reverse_proxy.transport.tls: "" + caddy_1.reverse_proxy.transport.tls_insecure_skip_verify: "" + caddy_2: hubsdialog-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "https://{{upstreams 4443}}" + caddy_2.reverse_proxy.transport: http + caddy_2.reverse_proxy.transport.tls: "" + caddy_2.reverse_proxy.transport.tls_insecure_skip_verify: "" + + nearspark: + image: hubsfoundation/nearspark:stable-28 + container_name: hubs-nearspark + restart: unless-stopped + cpu_shares: 30 + environment: + PUID: $PUID + PGID: $PGID + TZ: UTC + expose: + - 5000 + networks: + - hubs-net + - pcs + labels: + caddy_0: hubsnearspark-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 5000}}" + caddy_1: hubsnearspark-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 5000}}" + caddy_2: hubsnearspark-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 5000}}" + + photomnemonic: + image: hubsfoundation/photomnemonic:stable-75 + container_name: hubs-photomnemonic + restart: unless-stopped + cpu_shares: 30 + environment: + PUID: $PUID + PGID: $PGID + TZ: UTC + expose: + - 5000 + networks: + - hubs-net + +networks: + hubs-net: + name: hubs-net + pcs: + name: pcs + external: true + +x-casaos: + architectures: + - amd64 + main: hubs + + author: Hubs Foundation + developer: Hubs Foundation + category: Communication + + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Hubs/icon.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Hubs/thumbnail.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Hubs/screenshot-1.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Hubs/screenshot-2.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Hubs/screenshot-3.png + + title: + en_us: Hubs + + tagline: + en_us: Self-hosted social VR rooms in your browser + fr_fr: Salons VR sociaux auto-hébergés dans votre navigateur + es_es: Salas de VR social autoalojadas en tu navegador + zh_cn: 自托管的浏览器内社交 VR 房间 + ko_kr: 브라우저에서 즐기는 셀프호스팅 소셜 VR 룸 + + description: + en_us: | + Hubs is an open-source social VR platform from the Hubs Foundation. Create persistent virtual rooms for meetings, classes, events, or hangouts — accessible from any browser, no install required, with full WebXR support for VR headsets. + fr_fr: | + Hubs est une plateforme de VR sociale open-source de la Hubs Foundation. Créez des salons virtuels persistants pour des réunions, cours, événements ou rencontres — accessibles depuis n'importe quel navigateur, sans installation, avec une prise en charge complète de WebXR pour les casques VR. + es_es: | + Hubs es una plataforma de VR social de código abierto de la Hubs Foundation. Crea salas virtuales persistentes para reuniones, clases, eventos o encuentros — accesibles desde cualquier navegador, sin instalación, con soporte completo de WebXR para visores de VR. + zh_cn: | + Hubs 是 Hubs Foundation 推出的开源社交 VR 平台。创建持久化的虚拟房间,用于会议、课程、活动或聚会 —— 直接通过浏览器访问,无需安装,并完整支持 WebXR VR 头显。 + ko_kr: | + Hubs는 Hubs Foundation의 오픈소스 소셜 VR 플랫폼입니다. 회의·수업·이벤트·모임을 위한 영구 가상 룸을 만들 수 있으며, 어떤 브라우저에서도 설치 없이 접속할 수 있고 VR 헤드셋용 WebXR을 완벽 지원합니다. + + pre-install-cmd: | + mkdir -p /DATA/AppData/hubs/pgdata /DATA/AppData/hubs/retstorage + # Heal pgdata ownership on every install — postgres runs as uid 70 in + # the alpine image, but the framework's recursive chown of /DATA/AppData + # can re-lock it to PUID and trigger a reticulum crashloop. + chown -R 70:70 /DATA/AppData/hubs/pgdata 2>/dev/null || true + # Bootstrap secrets, perms keypair, and config template via hubs-seed. + # Idempotent — safe to re-run. Always refreshes APP_DOMAIN / + # APP_PUBLIC_IP_DASH / etc. in .env, which prevents the empty-IP_DASH + # outage where Caddy labels render as "hubs-.nip.io" and CF Worker + # requests fall through to the catch-all (200, 0 bytes). + docker run --rm \ + -v /DATA/AppData/hubs:/data/hubs \ + -v /DATA/AppData/casaos/apps/hubs:/data/compose \ + -e APP_DOMAIN -e APP_PUBLIC_IP_DASH -e APP_PUBLIC_IPV4 \ + -e APP_EMAIL -e PUID -e PGID \ + ghcr.io/worph/hubs-seed:2.0.0 bootstrap + + post-install-cmd: | + docker run --rm \ + --network hubs-net \ + -v /DATA/AppData/casaos/apps/hubs/.env:/secrets.env:ro \ + -e SECRETS_ENV_FILE=/secrets.env \ + -e RET_INTERNAL_URL=https://hubs-reticulum:4000 \ + -e RET_PUBLIC_URL=https://hubs-${APP_DOMAIN} \ + -e APP_EMAIL=${APP_EMAIL} \ + ghcr.io/worph/hubs-seed:2.0.0 + + tips: + before_install: + en_us: | + **🔑 Initial admin: `${APP_EMAIL}`** — the only account with admin rights. Sign in with this email at https://hubs-${APP_DOMAIN} to receive a magic-link. + + **Operator surfaces** (after admin sign-in): + - [Admin panel](https://hubs-${APP_DOMAIN}/admin) + - [Spoke scene editor](https://hubs-${APP_DOMAIN}/spoke/projects) + + **Voice / video** needs UDP ports 40000-40050 reachable on the server's public IP. HTTPS-only tunnels leave signaling working but audio silent. + + **Resources:** ~3 GB RAM, ~2 vCPU. Suitable for rooms up to ~10 concurrent users. + **More info:** [Hubs Community on Yundera](https://yundera.com/blog/best-apps-1/hubs-community-available-in-one-click-40) + fr_fr: | + **🔑 Administrateur initial : `${APP_EMAIL}`** — seul compte avec les droits admin. Connectez-vous avec cet email sur https://hubs-${APP_DOMAIN} pour recevoir un lien magique. + + **Interfaces d'administration** (après connexion admin) : + - [Panneau admin](https://hubs-${APP_DOMAIN}/admin) + - [Éditeur de scène Spoke](https://hubs-${APP_DOMAIN}/spoke/projects) + + **Voix / vidéo** : les ports UDP 40000-40050 doivent être joignables sur l'IP publique du serveur. Un tunnel HTTPS uniquement laisse la signalisation fonctionnelle mais coupe le média. + + **Ressources :** ~3 Go de RAM, ~2 vCPU. Adapté aux salons jusqu'à ~10 utilisateurs simultanés. + **More info:** [Hubs Community on Yundera](https://yundera.com/blog/best-apps-1/hubs-community-available-in-one-click-40) + es_es: | + **🔑 Administrador inicial: `${APP_EMAIL}`** — la única cuenta con permisos de admin. Inicia sesión con ese correo en https://hubs-${APP_DOMAIN} para recibir un enlace mágico. + + **Interfaces de administración** (tras iniciar sesión como admin): + - [Panel de admin](https://hubs-${APP_DOMAIN}/admin) + - [Editor de escenas Spoke](https://hubs-${APP_DOMAIN}/spoke/projects) + + **Voz / vídeo:** los puertos UDP 40000-40050 deben estar accesibles en la IP pública del servidor. Un túnel solo HTTPS deja la señalización funcionando pero corta el audio. + + **Recursos:** ~3 GB de RAM, ~2 vCPU. Adecuado para salas de hasta ~10 usuarios simultáneos. + **More info:** [Hubs Community on Yundera](https://yundera.com/blog/best-apps-1/hubs-community-available-in-one-click-40) + zh_cn: | + **🔑 初始管理员:`${APP_EMAIL}`** —— 唯一拥有管理员权限的账户。在 https://hubs-${APP_DOMAIN} 使用该邮箱登录以接收魔法链接。 + + **管理员界面**(管理员登录后): + - [管理面板](https://hubs-${APP_DOMAIN}/admin) + - [Spoke 场景编辑器](https://hubs-${APP_DOMAIN}/spoke/projects) + + **语音 / 视频**:服务器公网 IP 的 UDP 端口 40000-40050 必须开放。仅 HTTPS 隧道可保持信令但会丢失媒体。 + + **资源:** 约 3 GB 内存、~2 vCPU。适合 ~10 名以下并发用户的房间。 + ko_kr: | + **🔑 초기 관리자: `${APP_EMAIL}`** — 관리자 권한을 가진 유일한 계정입니다. https://hubs-${APP_DOMAIN} 에서 이 이메일로 로그인하면 매직 링크를 받게 됩니다. + + **운영자 화면** (관리자 로그인 후): + - [관리 패널](https://hubs-${APP_DOMAIN}/admin) + - [Spoke 씬 에디터](https://hubs-${APP_DOMAIN}/spoke/projects) + + **음성 / 영상**: 서버의 공인 IP에서 UDP 40000-40050 포트가 열려 있어야 합니다. HTTPS 전용 터널 환경에서는 시그널링만 동작하고 미디어는 끊깁니다. + + **리소스:** RAM 약 3 GB, vCPU 약 2개. 동시 접속 ~10명 이하 룸에 적합합니다. + **More info:** [Hubs Community on Yundera](https://yundera.com/blog/best-apps-1/hubs-community-available-in-one-click-40) diff --git a/Apps/Hubs/icon.png b/Apps/Hubs/icon.png new file mode 100644 index 0000000..aaefa66 Binary files /dev/null and b/Apps/Hubs/icon.png differ diff --git a/Apps/Hubs/rationale.md b/Apps/Hubs/rationale.md new file mode 100644 index 0000000..d69641f --- /dev/null +++ b/Apps/Hubs/rationale.md @@ -0,0 +1,46 @@ +# Hubs — Rationale + +## What deviation / exception is being requested + +Six of the eight services in the Hubs stack run as `user: "0:0"`: + +- `db` (`postgres:14-alpine`) +- `hubs` (`hubsfoundation/reticulum:stable-855`) — Phoenix backend, the user-facing service +- `postgrest` (`mozillareality/postgrest:8`) +- `hubs-client` (`hubsfoundation/hubs:stable-3111`) — static asset server +- `spoke` (`hubsfoundation/spoke:stable-95`) — scene editor +- `dialog` (`hubsfoundation/dialog:stable-331`) — Mediasoup WebRTC SFU + +The remaining two (`nearspark`, `photomnemonic`) inherit the default `PUID:PGID`. + +## Why it is necessary + +Every container running as root does so because its **upstream entrypoint expects to start as root and drop privileges itself**: + +- `postgres:14-alpine` requires root to `chown -R postgres:postgres "$PGDATA"` before `gosu postgres` re-execs the postmaster as uid 70. Running it as a non-root user up front bypasses that chown and produces the `Permission denied: global/pg_filenode.map` failure mode (which is exactly what triggered the original outage on staging). +- The Hubs Foundation images (`reticulum`, `hubs`, `spoke`, `dialog`) ship with an entrypoint that templates `/home/ret/config.toml` from env vars at startup, writes it under `/home/ret`, and then exec's the application. The template-render step needs write access to a path created at image build time as root. +- `mozillareality/postgrest` has the same upstream-defined entrypoint pattern. + +Forcing these images to run unprivileged would require maintaining a downstream rebuild for each — a maintenance burden disproportionate to the security improvement, given the AppData-only volume topology described below. + +## Security mitigations in place + +- **All bind mounts stay inside `/DATA/AppData/hubs/`.** No service mounts `/DATA/Documents`, `/DATA/Downloads`, `/DATA/Media`, or `/DATA/Gallery`. A root-process escape from any Hubs container can only reach the app's own AppData tree, not user-owned content. +- **No `ports:` on the user-facing services.** Only `dialog` exposes host ports (UDP/TCP 40000-40050), required by the Mediasoup SFU for WebRTC media. Everything else is reachable solely via the internal `hubs-net` network and the `pcs` network through Caddy reverse-proxy labels. +- **Caddy gateway termination.** All HTTP/HTTPS traffic is terminated by the PCS's `mesh-router-caddy`, not the Hubs containers themselves. Public Caddy labels point at internal `expose:` ports on the `pcs` network, so direct host-port exposure is limited to the SFU media range. +- **Per-service `cpu_shares`** prevent a runaway service (e.g. compromised `nearspark` thumbnail proxy) from starving the host. +- **Bootstrap secrets are mode `0600`**, owned by `PUID:PGID`. The compose `.env`, `perms.key.pem`, and `perms.pub.pem` are not world-readable. +- **Postgres data dir is owned by uid 70** (the in-image postgres user), enforced on every install by an `chown -R 70:70` in `pre-install-cmd` so a stray framework-level chown can't relock it and crashloop the database. + +## Alternatives considered and rejected + +1. **Rebuild upstream images with `USER 1000`.** Rejected — would require maintaining seven downstream forks (`reticulum`, `hubs`, `spoke`, `dialog`, `nearspark`, `photomnemonic`, plus `postgres` if we wanted full coverage) and continuously rebasing on Hubs Foundation's stable tags. The maintenance cost outweighs the marginal security benefit given the AppData-only mount topology. +2. **Run Postgres as `user: "70:70"` directly.** Rejected — the Postgres image's chown step inside the entrypoint requires root. Skipping it leaves `pgdata` owned by uid 1000 (host PUID) and the postgres process (uid 70) cannot read its own data files. We hit exactly this failure mode on the initial Hubs rollout on staging. +3. **Wrap each upstream image with a sidecar that does the chown then signals the main service.** Rejected as over-engineered for the threat model. + +## Data protection + +- All persistent state lives under `/DATA/AppData/hubs/` and survives uninstall/reinstall. The compose's `pre-install-cmd` calls `hubs-seed bootstrap` which is **idempotent** — on re-run it preserves existing secrets in `.env` (DB password, NODE_COOKIE, GUARDIAN_KEY, PHX_KEY, DASHBOARD_ACCESS_KEY, POSTGREST_PASSWORD, PERMS_KEY) and reuses the existing RSA keypair. Regenerating the keypair would invalidate every minted Guardian admin token. +- Framework-passthrough env vars (`APP_DOMAIN`, `APP_PUBLIC_IP_DASH`, `APP_PUBLIC_IPV4`, `APP_EMAIL`, `PUID`, `PGID`) **are** refreshed on every install — that is intentional, so a redeploy fixes drifted Caddy labels (the original outage's root cause). +- The compose's `.env` file is mode `0600`. The RSA private key (`perms.key.pem`) is mode `0600`. Both are owned by `PUID:PGID`. +- No user-owned content (Documents/Downloads/Media/Gallery) is touched by any Hubs container; the blast radius of a root container escape is bounded to `/DATA/AppData/hubs/`. diff --git a/Apps/Hubs/screenshot-1.png b/Apps/Hubs/screenshot-1.png new file mode 100644 index 0000000..f0739e5 Binary files /dev/null and b/Apps/Hubs/screenshot-1.png differ diff --git a/Apps/Hubs/screenshot-2.png b/Apps/Hubs/screenshot-2.png new file mode 100644 index 0000000..5ce5ea3 Binary files /dev/null and b/Apps/Hubs/screenshot-2.png differ diff --git a/Apps/Hubs/screenshot-3.png b/Apps/Hubs/screenshot-3.png new file mode 100644 index 0000000..0fe82f6 Binary files /dev/null and b/Apps/Hubs/screenshot-3.png differ diff --git a/Apps/Hubs/thumbnail.png b/Apps/Hubs/thumbnail.png new file mode 100644 index 0000000..e9f0e3c Binary files /dev/null and b/Apps/Hubs/thumbnail.png differ diff --git a/Apps/Immich/docker-compose.yml b/Apps/Immich/docker-compose.yml index 6cd81ab..2391ccc 100644 --- a/Apps/Immich/docker-compose.yml +++ b/Apps/Immich/docker-compose.yml @@ -2,7 +2,7 @@ name: immich services: immich: - image: ghcr.io/immich-app/immich-server:v2.6.3 + image: ghcr.io/immich-app/immich-server:v2.7.5 container_name: immich user: $PUID:$PGID cpu_shares: 70 @@ -41,7 +41,7 @@ services: - pcs immich-machine-learning: - image: ghcr.io/immich-app/immich-machine-learning:v2.6.3 + image: ghcr.io/immich-app/immich-machine-learning:v2.7.5 container_name: immich_machine_learning cpu_shares: 5 deploy: @@ -63,7 +63,7 @@ services: - immich redis: - image: docker.io/valkey/valkey:9@sha256:3eeb09785cd61ec8e3be35f8804c8892080f3ca21934d628abc24ee4ed1698f6 + image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 container_name: immich-redis cpu_shares: 10 user: 0:0 # no volume mount so can run as root @@ -143,84 +143,6 @@ x-casaos: * **Multi-user support**, so everyone in your home has their own space Perfect for photographers, families, or anyone who wants to take back control of their memories. Immich works beautifully on your Yundera Personal Cloud Server, so your media stays **yours : forever**. - en_gb: | - Immich gives you a modern, self-hosted photo and video gallery, without the limits or tracking of traditional cloud services. - - Automatically back up your photos and videos from your phone. Organise them by date, location, or face. Browse everything from a sleek web app, stream in 4K to your smart TV, and share albums privately with family and friends : no account or app needed. - - Unlike other platforms, Immich runs on your own private server. That means: - - * **Unlimited storage** - You can upgrade the storage anytime at market price on your Yundera interface - * **No data mining** - * **Lightning-fast access on your local network** - * **Multi-user support**, so everyone in your home has their own space - - Perfect for photographers, families, or anyone who wants to take back control of their memories. Immich works beautifully on your Yundera Personal Cloud Server, so your media stays **yours : forever**. - it_it: | - Immich ti offre una galleria moderna di foto e video auto-ospitata, senza i limiti o il tracciamento dei servizi cloud tradizionali. - - Effettua automaticamente il backup delle tue foto e video dal telefono. Organizzali per data, posizione o volto. Sfoglia tutto da un'elegante app web, trasmetti in 4K alla tua smart TV e condividi album privatamente con familiari e amici: non serve account o app. - - A differenza di altre piattaforme, Immich funziona sul tuo server privato. Questo significa: - - * **Archiviazione illimitata** - Puoi aggiornare lo spazio di archiviazione in qualsiasi momento a prezzo di mercato sulla tua interfaccia Yundera - * **Nessun data mining** - * **Accesso ultra-veloce sulla tua rete locale** - * **Supporto multi-utente**, così tutti in casa hanno il proprio spazio - - Perfetto per fotografi, famiglie o chiunque voglia riprendere il controllo dei propri ricordi. Immich funziona meravigliosamente sul tuo Server Cloud Personale Yundera, così i tuoi media rimangono **tuoi : per sempre**. - nb_no: | - Immich gir deg et moderne, selvdrevet foto- og videogalleri, uten begrensningene eller sporingen fra tradisjonelle skytjenester. - - Sikkerhetskopier automatisk dine bilder og videoer fra telefonen. Organiser dem etter dato, sted eller ansikt. Bla gjennom alt fra en elegant nettapp, strøm i 4K til din smart-TV, og del album privat med familie og venner: ingen konto eller app nødvendig. - - I motsetning til andre plattformer kjører Immich på din egen private server. Det betyr: - - * **Ubegrenset lagring** - Du kan oppgradere lagringen når som helst til markedspris på ditt Yundera-grensesnitt - * **Ingen datautvinning** - * **Lynrask tilgang på ditt lokale nettverk** - * **Støtte for flere brukere**, så alle i hjemmet ditt har sitt eget rom - - Perfekt for fotografer, familier eller alle som vil ta tilbake kontrollen over sine minner. Immich fungerer vakkert på din Yundera Personal Cloud Server, så mediene dine forblir **dine : for alltid**. - zh_cn: | - Immich为您提供现代化的自托管照片和视频画廊,没有传统云服务的限制或跟踪。 - - 从手机自动备份您的照片和视频。按日期、位置或人脸整理它们。从精美的网络应用浏览所有内容,在智能电视上以4K流媒体播放,与家人朋友私密分享相册:无需账户或应用程序。 - - 与其他平台不同,Immich在您自己的私人服务器上运行。这意味着: - - * **无限存储** - 您可以随时在Yundera界面以市场价格升级存储 - * **无数据挖掘** - * **本地网络的闪电般快速访问** - * **多用户支持**,让家中每个人都有自己的空间 - - 非常适合摄影师、家庭或任何想要重新控制自己回忆的人。Immich在您的Yundera个人云服务器上完美运行,让您的媒体**永远属于您**。 - ja_jp: | - Immichは、従来のクラウドサービスの制限や追跡なしに、モダンなセルフホスト型の写真・動画ギャラリーを提供します。 - - スマートフォンから写真や動画を自動バックアップ。日付、場所、顔で整理できます。洗練されたWebアプリですべてを閲覧し、スマートTVで4Kストリーミングし、家族や友人とアルバムをプライベートに共有:アカウントやアプリは不要です。 - - 他のプラットフォームとは異なり、Immichはあなた自身のプライベートサーバーで動作します。これは以下を意味します: - - * **無制限ストレージ** - Yunderaインターフェースでいつでも市場価格でストレージをアップグレード可能 - * **データマイニングなし** - * **ローカルネットワークでの超高速アクセス** - * **マルチユーザーサポート**、家庭の全員が自分のスペースを持てます - - 写真家、家族、または自分の思い出のコントロールを取り戻したい誰にでも最適です。ImmichはYundera Personal Cloud Serverで美しく動作し、あなたのメディアは**永遠にあなたのもの**です。 - ko_kr: | - Immich는 기존 클라우드 서비스의 제한이나 추적 없이 현대적인 자체 호스팅 사진 및 비디오 갤러리를 제공합니다。 - - 휴대폰에서 사진과 비디오를 자동으로 백업하세요。날짜、위치 또는 얼굴별로 정리하세요。세련된 웹 앱에서 모든 것을 탐색하고、스마트 TV에서 4K로 스트리밍하며、가족 및 친구들과 앨범을 비공개로 공유하세요 : 계정이나 앱이 필요하지 않습니다。 - - 다른 플랫폼과 달리 Immich는 자신만의 개인 서버에서 실행됩니다。이것의 의미는: - - * **무제한 저장소** - Yundera 인터페이스에서 언제든지 시장 가격으로 저장소를 업그레이드할 수 있습니다 - * **데이터 마이닝 없음** - * **로컬 네트워크에서의 초고속 접근** - * **멀티 사용자 지원**、집안의 모든 사람이 자신만의 공간을 가질 수 있습니다 - - 사진작가、가족 또는 자신의 추억을 다시 통제하고 싶은 누구에게나 완벽합니다。Immich는 Yundera 개인 클라우드 서버에서 아름답게 작동하므로 미디어가 **영원히 당신의 것**으로 남습니다。 fr_fr: | Immich vous offre une galerie moderne de photos et vidéos auto-hébergée, sans les limites ou le suivi des services cloud traditionnels. @@ -234,114 +156,9 @@ x-casaos: * **Support multi-utilisateur**, pour que chacun dans votre foyer ait son propre espace Parfait pour les photographes, les familles, ou quiconque veut reprendre le contrôle de ses souvenirs. Immich fonctionne parfaitement sur votre serveur cloud personnel Yundera, ainsi vos médias restent **les vôtres : pour toujours**. - de_de: | - Immich bietet Ihnen eine moderne, selbstgehostete Foto- und Videogalerie ohne die Einschränkungen oder das Tracking traditioneller Cloud-Dienste. - - Sichern Sie automatisch Ihre Fotos und Videos von Ihrem Telefon. Organisieren Sie sie nach Datum, Ort oder Gesicht. Durchstöbern Sie alles über eine elegante Web-App, streamen Sie in 4K auf Ihren Smart-TV und teilen Sie Alben privat mit Familie und Freunden: kein Account oder App erforderlich. - - Anders als andere Plattformen läuft Immich auf Ihrem eigenen privaten Server. Das bedeutet: - - * **Unbegrenzter Speicher** - Sie können den Speicher jederzeit zum Marktpreis über Ihr Yundera-Interface erweitern - * **Kein Data-Mining** - * **Blitzschneller Zugriff in Ihrem lokalen Netzwerk** - * **Multi-User-Unterstützung**, damit jeder in Ihrem Zuhause seinen eigenen Bereich hat - - Perfekt für Fotografen, Familien oder jeden, der die Kontrolle über seine Erinnerungen zurückgewinnen möchte. Immich funktioniert wunderbar auf Ihrem Yundera Personal Cloud Server, damit Ihre Medien **für immer Ihnen gehören**. - sv_se: | - Immich ger dig ett modernt, självhostat foto- och videogalleri, utan begränsningarna eller spårningen från traditionella molntjänster. - - Säkerhetskopiera automatiskt dina foton och videor från din telefon. Organisera dem efter datum, plats eller ansikte. Bläddra igenom allt från en elegant webbapp, streama i 4K till din smart-TV och dela album privat med familj och vänner: inget konto eller app behövs. - - Till skillnad från andra plattformar körs Immich på din egen privata server. Det betyder: - - * **Obegränsad lagring** - Du kan uppgradera lagringen när som helst till marknadspris på ditt Yundera-gränssnitt - * **Ingen datautvinning** - * **Blixtsnabb åtkomst på ditt lokala nätverk** - * **Stöd för flera användare**, så alla i ditt hem har sitt eget utrymme - - Perfekt för fotografer, familjer eller alla som vill ta tillbaka kontrollen över sina minnen. Immich fungerar vackert på din Yundera Personal Cloud Server, så dina medier förblir **dina : för alltid**. - el_gr: | - Το Immich σας προσφέρει μια σύγχρονη, αυτο-φιλοξενούμενη συλλογή φωτογραφιών και βίντεο, χωρίς τους περιορισμούς ή την παρακολούθηση των παραδοσιακών υπηρεσιών cloud. - - Δημιουργήστε αυτόματα αντίγραφα ασφαλείας των φωτογραφιών και βίντεό σας από το τηλέφωνό σας. Οργανώστε τα κατά ημερομηνία, τοποθεσία ή πρόσωπο. Περιηγηθείτε σε όλα από μια κομψή web εφαρμογή, κάντε streaming σε 4K στη smart TV σας και μοιραστείτε άλμπουμ ιδιωτικά με την οικογένεια και τους φίλους σας : δεν χρειάζεται λογαριασμός ή εφαρμογή. - - Σε αντίθεση με άλλες πλατφόρμες, το Immich λειτουργεί στον δικό σας ιδιωτικό διακομιστή. Αυτό σημαίνει: - - * **Απεριόριστη αποθήκευση** - Μπορείτε να αναβαθμίσετε την αποθήκευση ανά πάσα στιγμή σε τιμή αγοράς στο περιβάλλον Yundera - * **Χωρίς εξόρυξη δεδομένων** - * **Αστραπιαία πρόσβαση στο τοπικό σας δίκτυο** - * **Υποστήριξη πολλαπλών χρηστών**, ώστε όλοι στο σπίτι σας να έχουν τον δικό τους χώρο - - Ιδανικό για φωτογράφους, οικογένειες ή οποιονδήποτε θέλει να ανακτήσει τον έλεγχο των αναμνήσεών του. Το Immich λειτουργεί υπέροχα στον Yundera Personal Cloud Server σας, έτσι τα μέσα σας παραμένουν **δικά σας : για πάντα**. - hr_hr: | - Immich vam pruža modernu, samo-hostovanu galeriju fotografija i videozapisa, bez ograničenja ili praćenja tradicionalnih cloud servisa. - - Automatski napravite sigurnosne kopije svojih fotografija i videozapisa s telefona. Organizirajte ih po datumu, lokaciji ili licu. Pregledajte sve putem elegantne web aplikacije, streamajte u 4K na vaš pametni TV i privatno dijelite albume s obitelji i prijateljima : nije potreban račun ili aplikacija. - - Za razliku od drugih platformi, Immich radi na vašem vlastitom privatnom serveru. To znači: - - * **Neograničeno spremište** - Možete nadograditi spremište u bilo koje vrijeme po tržišnoj cijeni na vašem Yundera sučelju - * **Nema rudarenja podataka** - * **Munja-brz pristup na vašoj lokalnoj mreži** - * **Podrška za više korisnika**, tako da svatko u vašem domu ima svoj vlastiti prostor - - Savršeno za fotografe, obitelji ili bilo koga tko želi povratiti kontrolu nad svojim uspomenama. Immich radi prekrasno na vašem Yundera Personal Cloud Serveru, tako da vaši mediji ostaju **vaši : zauvijek**. - pt_pt: | - O Immich oferece-lhe uma galeria moderna de fotos e vídeos auto-hospedada, sem as limitações ou rastreamento dos serviços cloud tradicionais. - - Faça automaticamente backup das suas fotos e vídeos do telefone. Organize-os por data, localização ou rosto. Navegue por tudo numa elegante aplicação web, transmita em 4K para a sua smart TV e partilhe álbuns privadamente com família e amigos : não é necessária conta ou aplicação. - - Ao contrário de outras plataformas, o Immich executa no seu próprio servidor privado. Isso significa: - - * **Armazenamento ilimitado** - Pode atualizar o armazenamento a qualquer momento ao preço de mercado na sua interface Yundera - * **Sem mineração de dados** - * **Acesso ultra-rápido na sua rede local** - * **Suporte multi-utilizador**, para que todos em sua casa tenham o seu próprio espaço - - Perfeito para fotógrafos, famílias ou qualquer pessoa que queira recuperar o controlo das suas memórias. O Immich funciona magnificamente no seu Servidor Cloud Pessoal Yundera, para que os seus conteúdos permaneçam **seus : para sempre**. - ru_ru: | - Immich предоставляет вам современную самостоятельно размещенную галерею фотографий и видео, без ограничений и отслеживания традиционных облачных сервисов. - - Автоматически создавайте резервные копии фотографий и видео с телефона. Организуйте их по дате, местоположению или лицу. Просматривайте всё через элегантное веб-приложение, транслируйте в 4K на ваш умный телевизор и делитесь альбомами приватно с семьёй и друзьями : не нужен аккаунт или приложение. - - В отличие от других платформ, Immich работает на вашем собственном частном сервере. Это означает: - - * **Неограниченное хранилище** - Вы можете обновить хранилище в любое время по рыночной цене в вашем интерфейсе Yundera - * **Никакого сбора данных** - * **Молниеносный доступ в вашей локальной сети** - * **Поддержка нескольких пользователей**, чтобы у каждого в вашем доме было своё пространство - - Идеально для фотографов, семей или любого, кто хочет вернуть контроль над своими воспоминаниями. Immich прекрасно работает на вашем Yundera Personal Cloud Server, поэтому ваши медиа остаются **вашими : навсегда**. - tr_tr: | - Immich size geleneksel bulut hizmetlerinin sınırlamaları veya takibi olmadan modern, kendi kendine barındırılan bir fotoğraf ve video galerisi sunar. - - Telefonunuzdan fotoğraflarınızı ve videolarınızı otomatik olarak yedekleyin. Onları tarih, konum veya yüze göre düzenleyin. Her şeyi şık bir web uygulamasından göz atın, akıllı TV'nizde 4K olarak yayınlayın ve aile ve arkadaşlarınızla albümleri özel olarak paylaşın : hesap veya uygulama gerekmez. - - Diğer platformların aksine, Immich kendi özel sunucunuzda çalışır. Bu şu anlama gelir: - - * **Sınırsız depolama** - Yundera arayüzünüzde istediğiniz zaman piyasa fiyatından depolamayı yükseltebilirsiniz - * **Veri madenciliği yok** - * **Yerel ağınızda şimşek hızında erişim** - * **Çoklu kullanıcı desteği**, böylece evinizdeki herkes kendi alanına sahip olabilir - - Fotoğrafçılar, aileler veya anılarının kontrolünü geri almak isteyen herkes için mükemmel. Immich, Yundera Kişisel Bulut Sunucunuzda harika çalışır, böylece medyanız **sizin : sonsuza kadar** kalır. tagline: en_us: Your Private Photo Library, Fully Under Your Control - en_gb: Your Private Photo Library, Fully Under Your Control - it_it: La tua libreria fotografica privata, completamente sotto il tuo controllo - nb_no: Ditt private fotobibliotek, fullt under din kontroll - zh_cn: 您的私人照片库,完全在您的掌控之下 - ja_jp: あなたのプライベート写真ライブラリ、完全にあなたのコントロール下で - ko_kr: 완전히 통제하는 개인 사진 라이브러리 fr_fr: Votre bibliothèque photo privée, entièrement sous votre contrôle - de_de: Ihre private Fotobibliothek, vollständig unter Ihrer Kontrolle - sv_se: Ditt privata fotobibliotek, helt under din kontroll - el_gr: Η ιδιωτική σας φωτογραφική βιβλιοθήκη, πλήρως υπό τον έλεγχό σας - hr_hr: Vaša privatna fotografska knjižnica, potpuno pod vašom kontrolom - pt_pt: A sua biblioteca de fotos privada, totalmente sob o seu controlo - ru_ru: Ваша личная фотобиблиотека, полностью под вашим контролем - tr_tr: Tamamen kontrolünüz altında olan özel fotoğraf kütüphaneniz - es_es: Tu biblioteca de fotos privada, completamente bajo tu control title: en_us: Immich tips: @@ -349,119 +166,45 @@ x-casaos: en_us: | **Setup Information:** - 📱 **Mobile App**: After installation, download the Immich mobile app by scanning the QR code from the web interface settings page. + 🔐 **First Setup**: + 1. Wait 5 minutes for Immich to fully start + 2. Visit `https://immich-$APP_PUBLIC_IP_DASH.sslip.io/` and create your admin account + 3. **Always use this sslip.io URL** for unlimited uploads - ⏰ **Startup Time**: Immich takes approximately 5 minutes to fully start up. Please wait before accessing the web panel. + | Access Type | URL | Upload Limit | + |-------------|-----|--------------| + | **Recommended** | `https://immich-$APP_PUBLIC_IP_DASH.sslip.io/` | ∞ Unlimited | + | Standard | `https://immich-$APP_DOMAIN/` | 100 MB | - 🔐 **First Setup**: Create your admin account on first visit to the web interface. + 📱 **Mobile App**: Download the Immich app and connect using the sslip.io URL above for unlimited photo/video backup. 🔍 **OCR (Text Search)**: Immich v2.x includes OCR to search text in your photos. To enable OCR for existing photos, go to Administration > Job Settings and run the OCR job. - 📁 **Direct Upload from PCS**: To upload files directly from your host (useful for initial import of existing photo libraries or large file imports): - 1. Generate an API key in Immich (Account Settings > API Keys) - Immich is multi-user so you can create a api keys for different users if needed - 2. Login: `docker exec immich immich login-key http://localhost YOUR_API_KEY` - 3. Place files in `/DATA/Gallery/` and run: `docker exec immich immich upload --recursive /import` - it_it: | - **Informazioni di configurazione:** - - 📱 **App mobile**: Dopo l'installazione, scarica l'app mobile Immich scansionando il codice QR dalla pagina delle impostazioni dell'interfaccia web. - - ⏰ **Tempo di avvio**: Immich impiega circa 5 minuti per avviarsi completamente. Attendere prima di accedere al pannello web. - - 🔐 **Prima configurazione**: Crea il tuo account amministratore alla prima visita dell'interfaccia web. - nb_no: | - **Oppsettsinformasjon:** - - 📱 **Mobilapp**: Etter installasjon, last ned Immich-mobilappen ved å skanne QR-koden fra innstillingssiden på nettgrensesnittet. - - ⏰ **Oppstartstid**: Immich tar omtrent 5 minutter å starte opp fullstendig. Vennligst vent før du åpner nettgrensesnittet. - - 🔐 **Første oppsett**: Opprett din administratorkonto ved første besøk til nettgrensesnittet. - zh_cn: | - **设置信息:** - - 📱 **移动应用**:安装后,通过扫描网页界面设置页面的二维码下载 Immich 移动应用。 - - ⏰ **启动时间**:Immich 需要大约 5 分钟才能完全启动。请在访问网页面板前等待。 - - 🔐 **首次设置**:首次访问网页界面时创建您的管理员账户。 - ja_jp: | - **セットアップ情報:** - - 📱 **モバイルアプリ**: インストール後、Webインターフェースの設定ページからQRコードをスキャンしてImmichモバイルアプリをダウンロードしてください。 - - ⏰ **起動時間**: Immichは完全に起動するまで約5分かかります。Webパネルにアクセスする前にお待ちください。 - - 🔐 **初回設定**: Webインターフェースに初回アクセス時に管理者アカウントを作成してください。 - ko_kr: | - **설정 정보:** - - 📱 **모바일 앱**: 설치 후, 웹 인터페이스 설정 페이지의 QR 코드를 스캔하여 Immich 모바일 앱을 다운로드하세요. - - ⏰ **시작 시간**: Immich가 완전히 시작되는 데 약 5분이 걸립니다. 웹 패널에 접근하기 전에 기다려 주세요. - - 🔐 **첫 설정**: 웹 인터페이스에 처음 방문할 때 관리자 계정을 생성하세요. + 📁 **Import Existing Photos**: To import photos already on your PCS: + 1. Place files in `/DATA/Gallery/` using the Files app + 2. In Immich, go to **Administration → External Libraries** + 3. Create a library, add import path: `/import` + 4. Click **Scan Library** + fr_fr: | **Informations de configuration :** - - 📱 **Application mobile** : Après l'installation, téléchargez l'application mobile Immich en scannant le code QR depuis la page des paramètres de l'interface web. - - ⏰ **Temps de démarrage** : Immich prend environ 5 minutes pour démarrer complètement. Veuillez patienter avant d'accéder au panneau web. - - 🔐 **Première configuration** : Créez votre compte administrateur lors de votre première visite de l'interface web. - de_de: | - **Setup-Informationen:** - - 📱 **Mobile App**: Nach der Installation laden Sie die Immich Mobile App herunter, indem Sie den QR-Code von der Einstellungsseite der Web-Oberfläche scannen. - - ⏰ **Startzeit**: Immich benötigt etwa 5 Minuten, um vollständig zu starten. Bitte warten Sie, bevor Sie auf das Web-Panel zugreifen. - - 🔐 **Erste Einrichtung**: Erstellen Sie Ihr Administratorkonto beim ersten Besuch der Web-Oberfläche. - sv_se: | - **Installationsinformation:** - - 📱 **Mobilapp**: Efter installation, ladda ner Immich-mobilappen genom att skanna QR-koden från inställningssidan i webbgränssnittet. - - ⏰ **Starttid**: Immich tar cirka 5 minuter att starta helt. Vänligen vänta innan du öppnar webbpanelen. - - 🔐 **Första inställning**: Skapa ditt administratörskonto vid första besöket till webbgränssnittet. - el_gr: | - **Πληροφορίες ρύθμισης:** - - 📱 **Εφαρμογή κινητού**: Μετά την εγκατάσταση, κατεβάστε την εφαρμογή κινητού Immich σκανάροντας τον κωδικό QR από τη σελίδα ρυθμίσεων της διαδικτυακής διεπαφής. - - ⏰ **Χρόνος εκκίνησης**: Το Immich χρειάζεται περίπου 5 λεπτά για να ξεκινήσει πλήρως. Παρακαλώ περιμένετε πριν προσπελάσετε το web panel. - - 🔐 **Πρώτη ρύθμιση**: Δημιουργήστε τον λογαριασμό διαχειριστή κατά την πρώτη επίσκεψή σας στη διαδικτυακή διεπαφή. - hr_hr: | - **Informacije o postavci:** - - 📱 **Mobilna aplikacija**: Nakon instalacije, preuzmite Immich mobilnu aplikaciju skeniranjem QR koda s stranice postavki web sučelja. - - ⏰ **Vrijeme pokretanja**: Immich treba približno 5 minuta da se potpuno pokrene. Molimo pričekajte prije pristupanja web panelu. - - 🔐 **Prva postavka**: Stvorite svoj administratorski račun pri prvom posjetu web sučelju. - pt_pt: | - **Informações de configuração:** - - 📱 **Aplicação móvel**: Após a instalação, descarregue a aplicação móvel Immich digitalizando o código QR da página de definições da interface web. - - ⏰ **Tempo de arranque**: O Immich demora aproximadamente 5 minutos para iniciar completamente. Por favor aguarde antes de aceder ao painel web. - - 🔐 **Primeira configuração**: Crie a sua conta de administrador na primeira visita à interface web. - ru_ru: | - **Информация о настройке:** - - 📱 **Мобильное приложение**: После установки загрузите мобильное приложение Immich, отсканировав QR-код со страницы настроек веб-интерфейса. - - ⏰ **Время запуска**: Immich требуется примерно 5 минут для полного запуска. Пожалуйста, подождите перед доступом к веб-панели. - - 🔐 **Первая настройка**: Создайте учётную запись администратора при первом посещении веб-интерфейса. - tr_tr: | - **Kurulum Bilgileri:** - - 📱 **Mobil Uygulama**: Kurulumdan sonra, web arayüzü ayarlar sayfasındaki QR kodunu tarayarak Immich mobil uygulamasını indirin. - - ⏰ **Başlatma Süresi**: Immich'in tamamen başlaması yaklaşık 5 dakika sürer. Web paneline erişmeden önce lütfen bekleyin. - - 🔐 **İlk Kurulum**: Web arayüzünü ilk ziyaret ettiğinizde yönetici hesabınızı oluşturun. \ No newline at end of file + + 🔐 **Première configuration** : + 1. Attendez 5 minutes le démarrage complet d'Immich + 2. Visitez `https://immich-$APP_PUBLIC_IP_DASH.sslip.io/` et créez votre compte admin + 3. **Utilisez toujours cette URL sslip.io** pour des uploads illimités + + | Type d'accès | URL | Limite d'upload | + |--------------|-----|-----------------| + | **Recommandé** | `https://immich-$APP_PUBLIC_IP_DASH.sslip.io/` | ∞ Illimité | + | Standard | `https://immich-$APP_DOMAIN/` | 100 Mo | + + 📱 **Application mobile** : Téléchargez l'app Immich et connectez-vous avec l'URL sslip.io ci-dessus pour une sauvegarde illimitée de vos photos/vidéos. + + 🔍 **OCR (Recherche de texte)** : Immich v2.x inclut l'OCR pour rechercher du texte dans vos photos. Pour activer l'OCR sur les photos existantes, allez dans Administration > Job Settings et lancez le job OCR. + + 📁 **Importer des photos existantes** : Pour importer des photos déjà sur votre PCS : + 1. Placez les fichiers dans `/DATA/Gallery/` via l'app Files + 2. Dans Immich, allez dans **Administration → External Libraries** + 3. Créez une bibliothèque, ajoutez le chemin : `/import` + 4. Cliquez sur **Scan Library** diff --git a/Apps/Jellyfin/docker-compose.yml.disabled b/Apps/Jellyfin/docker-compose.yml similarity index 54% rename from Apps/Jellyfin/docker-compose.yml.disabled rename to Apps/Jellyfin/docker-compose.yml index f29e176..951c28e 100644 --- a/Apps/Jellyfin/docker-compose.yml.disabled +++ b/Apps/Jellyfin/docker-compose.yml @@ -1,34 +1,65 @@ name: jellyfin services: + jellyfin-proxy: + image: ghcr.io/yundera/nginx-hash-lock:1.0.7 + container_name: jellyfin-proxy + restart: unless-stopped + expose: + - 80 + labels: + caddy_0: jellyfin-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 80}}" + caddy_1: jellyfin-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 80}}" + caddy_2: jellyfin-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 80}}" + user: "0:0" + cpu_shares: 80 + environment: + BACKEND_HOST: jellyfin + BACKEND_PORT: "8096" + LISTEN_PORT: "80" + AUTH_DISABLED: "true" + deploy: + resources: + limits: + memory: 128M + depends_on: + - jellyfin + networks: + - pcs + jellyfin: - image: jellyfin/jellyfin:10.10.7 + image: jellyfin/jellyfin:10.11.11 container_name: jellyfin restart: unless-stopped - #ports: - # Port mappings can be uncommented when the ip feature is available - # - "8920:8920" - # - "7359:7359/udp" - # - "1900:1900/udp" - expose: - - 8096 # Web UI port for NSL router / NSL 라우터용 웹 UI 포트 volumes: - - /DATA/AppData/jellyfin/config:/config # Jellyfin configuration files / Jellyfin 설정 파일 - - /DATA/AppData/jellyfin/cache:/cache # Jellyfin cache data / Jellyfin 캐시 데이터 - - /DATA/Media/Movies:/media/movies # Movies library / 영화 라이브러리 - - /DATA/Media/TV Shows:/media/tvshows # TV Shows library / TV 쇼 라이브러리 - - /DATA/Media/Music:/media/music # Music library / 음악 라이브러리 - - /DATA/Downloads:/media/downloads # Downloads folder / 다운로드 폴더 - user: $PUID:$PGID # Run as user for file permissions / 파일 권한을 위한 사용자로 실행 + - /DATA/AppData/$AppID/config/:/config + - /DATA/AppData/$AppID/cache/:/cache + - /DATA/Media/Movies/:/media/movies + - /DATA/Media/TV Shows/:/media/tvshows + - /DATA/Media/Music/:/media/music + - /DATA/Downloads/:/media/downloads + user: $PUID:$PGID environment: - PGID: $PGID # Process Group ID / 프로세스 그룹 ID - PUID: $PUID # Process User ID / 프로세스 사용자 ID - TZ: $TZ # System timezone / 시스템 시간대 + PGID: $PGID + PUID: $PUID + TZ: $TZ cpu_shares: 50 deploy: resources: limits: - memory: 256M # Memory limit for Jellyfin / Jellyfin 메모리 제한 + memory: 1024M + networks: + - pcs + +networks: + pcs: + name: pcs + external: true x-casaos: architectures: @@ -59,38 +90,98 @@ x-casaos: title: en_us: Jellyfin store_app_id: jellyfin - main: jellyfin + main: jellyfin-proxy is_uncontrolled: false - webui_port: 8096 + webui_port: 80 tips: before_install: en_us: | 💡 **Tips**: Jellyfin gives you a Netflix-style interface for the TV shows and movies stored on your computer or server. - + 📖 **Setup guide**: [Set up your own streaming service](https://yundera.com/blog/tutorial-1/set-up-your-own-netflix-style-streaming-service-with-jellyfin-on-yundera-3) - + 🎬 **Enjoy!** ❓ Questions? Contact us at help@yundera.com + + **Getting Started:** + 1. Open Jellyfin after installation + 2. Follow the setup wizard to create your admin account + 3. Add your media libraries (Movies, TV Shows, Music) + + **Media Location:** + - Movies: `/DATA/Media/Movies/` + - TV Shows: `/DATA/Media/TV Shows/` + - Music: `/DATA/Media/Music/` + + **Tip:** Jellyfin is completely free with no subscriptions or limitations. ko_kr: | 💡 **팁**: Jellyfin은 컴퓨터나 서버에 저장된 TV 프로그램과 영화를 위한 Netflix 스타일 인터페이스를 제공합니다. - + 📖 **설정 가이드**: [나만의 스트리밍 서비스 설정하기](https://yundera.com/blog/tutorial-1/set-up-your-own-netflix-style-streaming-service-with-jellyfin-on-yundera-3) - + 🎬 **즐기세요!** ❓ 궁금한 점이 있으신가요? help@yundera.com으로 연락주세요 + + **시작하기:** + 1. 설치 후 Jellyfin 열기 + 2. 설정 마법사를 따라 관리자 계정 생성 + 3. 미디어 라이브러리 추가 (영화, TV 프로그램, 음악) + + **미디어 위치:** + - 영화: `/DATA/Media/Movies/` + - TV 프로그램: `/DATA/Media/TV Shows/` + - 음악: `/DATA/Media/Music/` + + **팁:** Jellyfin은 구독이나 제한 없이 완전히 무료입니다. zh_cn: | 💡 **提示**: Jellyfin 为存储在您计算机或服务器上的电视节目和电影提供 Netflix 风格的界面。 - + 📖 **设置指南**: [设置您自己的流媒体服务](https://yundera.com/blog/tutorial-1/set-up-your-own-netflix-style-streaming-service-with-jellyfin-on-yundera-3) - + 🎬 **尽情享受!** ❓ 有问题?请联系 help@yundera.com + + **开始使用:** + 1. 安装后打开 Jellyfin + 2. 按照设置向导创建管理员账户 + 3. 添加您的媒体库(电影、电视节目、音乐) + + **媒体位置:** + - 电影:`/DATA/Media/Movies/` + - 电视节目:`/DATA/Media/TV Shows/` + - 音乐:`/DATA/Media/Music/` + + **提示:** Jellyfin 完全免费,没有订阅或限制。 es_es: | 💡 **Consejos**: Jellyfin te ofrece una interfaz estilo Netflix para los programas de TV y películas almacenados en tu computadora o servidor. - + 📖 **Guía de configuración**: [Configura tu propio servicio de streaming](https://yundera.com/blog/tutorial-1/set-up-your-own-netflix-style-streaming-service-with-jellyfin-on-yundera-3) - + 🎬 **¡Disfruta!** ❓ ¿Preguntas? Contáctanos en help@yundera.com + + **Para empezar:** + 1. Abre Jellyfin después de la instalación + 2. Sigue el asistente de configuración para crear tu cuenta de administrador + 3. Añade tus bibliotecas multimedia (Películas, Series, Música) + + **Ubicación de medios:** + - Películas: `/DATA/Media/Movies/` + - Series: `/DATA/Media/TV Shows/` + - Música: `/DATA/Media/Music/` + + **Consejo:** Jellyfin es completamente gratuito sin suscripciones ni limitaciones. fr_fr: | 💡 **Conseils**: Jellyfin vous offre une interface de style Netflix pour les émissions TV et films stockés sur votre ordinateur ou serveur. - + 📖 **Guide de configuration**: [Configurez votre propre service de streaming](https://yundera.com/blog/tutorial-1/set-up-your-own-netflix-style-streaming-service-with-jellyfin-on-yundera-3) - + 🎬 **Profitez-en !** ❓ Des questions ? Contactez-nous à help@yundera.com + + **Pour commencer :** + 1. Ouvrez Jellyfin après l'installation + 2. Suivez l'assistant de configuration pour créer votre compte administrateur + 3. Ajoutez vos bibliothèques multimédia (Films, Séries TV, Musique) + + **Emplacement des médias :** + - Films : `/DATA/Media/Movies/` + - Séries TV : `/DATA/Media/TV Shows/` + - Musique : `/DATA/Media/Music/` + + **Astuce :** Jellyfin est entièrement gratuit sans abonnement ni limitation. diff --git a/Apps/Jellyfin/rationale.md b/Apps/Jellyfin/rationale.md new file mode 100644 index 0000000..09b692e --- /dev/null +++ b/Apps/Jellyfin/rationale.md @@ -0,0 +1,26 @@ +# Jellyfin — Rationale + +## What deviation / exception is being requested +The nginx-hash-lock sidecar runs as `user: 0:0` (root) with `AUTH_DISABLED: "true"` — it acts as a plain reverse proxy without hash-lock or OIDC authentication. The Jellyfin backend runs as `user: $PUID:$PGID` and accesses user media directories (`/DATA/Media/Movies`, `/DATA/Media/TV Shows`, `/DATA/Media/Music`, `/DATA/Downloads`). + +## Why it is necessary +- **jellyfin-proxy (nginx-hash-lock)**: Runs as root to bind to port 80. Auth is disabled because Jellyfin has its own first-launch onboarding wizard that requires the user to create an admin account — this is an explicitly-listed valid exception in CONTRIBUTING.md's Security checklist. +- **jellyfin**: Runs as `$PUID:$PGID` to access user-owned media files in `/DATA/Media/` and `/DATA/Downloads/`. This is the Mixed Usage pattern. + +## Security mitigations in place +- Jellyfin's built-in onboarding wizard forces admin account creation on first launch (cannot be bypassed) +- App data volumes map to `/DATA/AppData/$AppID/` only +- User media directories are the user's own files; Jellyfin reads them for indexing and streaming +- No privileged mode on any service +- Memory limits on both services (128M proxy, 1024M Jellyfin) +- Caddy labels only on the proxy sidecar; backend has no public routes + +## Alternatives considered and rejected +- OIDC/hash-lock authentication — Jellyfin's native authentication is more appropriate; adding an external auth layer in front of Jellyfin's own login creates a confusing double-login experience +- Running proxy as non-root — nginx requires root to bind to port 80 + +## Data protection +- Jellyfin config persists in `/DATA/AppData/$AppID/config/` +- Cache persists in `/DATA/AppData/$AppID/cache/` +- User media directories contain the user's own files (read-only access for indexing/streaming) +- All data survives uninstall/reinstall diff --git a/Apps/Lidarr/docker-compose.yml b/Apps/Lidarr/docker-compose.yml new file mode 100644 index 0000000..9904181 --- /dev/null +++ b/Apps/Lidarr/docker-compose.yml @@ -0,0 +1,77 @@ +name: lidarr + +services: + lidarr: + image: lscr.io/linuxserver/lidarr:3.1.0 + container_name: lidarr + restart: unless-stopped + user: $PUID:$PGID + expose: + - 8686 + labels: + caddy_0: lidarr-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 8686}}" + caddy_1: lidarr-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 8686}}" + caddy_2: lidarr-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 8686}}" + networks: + - pcs + environment: + TZ: $TZ + volumes: + - /DATA/AppData/lidarr/config:/config # dedicated Lidarr config + - /DATA/Media/Music:/music # shared music library (same folder Jellyfin reads) + - /DATA/Downloads:/downloads # shared downloads folder + cpu_shares: 10 + deploy: + resources: + limits: + memory: 512M + +networks: + pcs: + name: pcs + external: true + +x-casaos: + main: lidarr + index: / + pre-install-cmd: | + mkdir -p /DATA/AppData/lidarr/config "/DATA/Media/Music" /DATA/Downloads + tips: + before_install: + en_us: | + 🎵 **Lidarr** is a music collection manager — it monitors, downloads and organises your music automatically. + + 📂 Config lives in `/DATA/AppData/lidarr/`, music in `/DATA/Media/Music` (the same folder Jellyfin/Navidrome read), downloads in `/DATA/Downloads`. + + 🔗 Pair it with **Prowlarr** (indexers) and **qBittorrent** (download client) from the store — reach them by container name `prowlarr:9696` and `qbittorrent:80` when they run on the same server. + + ❓ Questions? Contact us at help@yundera.com + architectures: + - amd64 + - arm64 + author: Yundera Team + category: Media + developer: Servarr (Lidarr) + description: + en_us: Lidarr is a music collection manager for Usenet and BitTorrent users. It monitors multiple RSS feeds for new tracks from your favourite artists, grabs, sorts and renames them, and integrates with download clients like qBittorrent and indexers via Prowlarr. Writes to the same music library folder used by Jellyfin and Navidrome. + fr_fr: Lidarr est un gestionnaire de collection musicale pour les utilisateurs Usenet et BitTorrent. Il surveille plusieurs flux RSS pour les nouveaux titres de vos artistes favoris, les récupère, les trie et les renomme, et s'intègre aux clients de téléchargement comme qBittorrent et aux indexeurs via Prowlarr. Écrit dans le même dossier de bibliothèque musicale utilisé par Jellyfin et Navidrome. + es_es: Lidarr es un gestor de colección musical para usuarios de Usenet y BitTorrent. Monitorea múltiples fuentes RSS de nuevas pistas de tus artistas favoritos, las descarga, ordena y renombra, e se integra con clientes de descarga como qBittorrent e indexadores vía Prowlarr. Escribe en la misma carpeta de biblioteca musical usada por Jellyfin y Navidrome. + zh_cn: Lidarr 是面向 Usenet 和 BitTorrent 用户的音乐收藏管理器。它监控多个 RSS 源以获取您喜爱艺术家的新曲目,抓取、整理并重命名,并通过 Prowlarr 与 qBittorrent 等下载客户端和索引器集成。写入与 Jellyfin 和 Navidrome 相同的音乐库文件夹。 + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Lidarr/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Lidarr/screenshot-1.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Lidarr/thumbnail.png + tagline: + en_us: Automatically find, download and organise your music collection. + fr_fr: Trouvez, téléchargez et organisez automatiquement votre collection musicale. + es_es: Encuentra, descarga y organiza automáticamente tu colección musical. + zh_cn: 自动查找、下载并整理您的音乐收藏。 + title: + en_us: Lidarr + store_app_id: lidarr + is_uncontrolled: false diff --git a/Apps/Lidarr/icon.png b/Apps/Lidarr/icon.png new file mode 100644 index 0000000..b21129b Binary files /dev/null and b/Apps/Lidarr/icon.png differ diff --git a/Apps/Lidarr/screenshot-1.png b/Apps/Lidarr/screenshot-1.png new file mode 100644 index 0000000..2951abb Binary files /dev/null and b/Apps/Lidarr/screenshot-1.png differ diff --git a/Apps/Lidarr/thumbnail.png b/Apps/Lidarr/thumbnail.png new file mode 100644 index 0000000..b21129b Binary files /dev/null and b/Apps/Lidarr/thumbnail.png differ diff --git a/Apps/N8NMCP/docker-compose.yml b/Apps/N8NMCP/docker-compose.yml index 2aaf48d..1e02c8a 100644 --- a/Apps/N8NMCP/docker-compose.yml +++ b/Apps/N8NMCP/docker-compose.yml @@ -1,10 +1,10 @@ name: n8nmcp services: - # Main MCP server — reachable at n8nmcp:9640 on the pcs network - n8nmcp: + # Main MCP server — reachable at n8nmcp-backend:9640 on the pcs network + n8nmcp-backend: image: ghcr.io/worph/n8nmcp:1.0.1 - container_name: n8nmcp - hostname: n8nmcp + container_name: n8nmcp-backend + hostname: n8nmcp-backend # supervisord is PID 1 and MUST start as root so it can drop privileges for # each child program. Running as a non-root uid fails with "Can't drop # privilege as nonroot user". Child processes drop to their own uid as @@ -35,9 +35,15 @@ services: networks: - pcs - # Nginx-hash-lock provides perimeter authentication for the web UI + # AppShield provides perimeter authentication (human SSO + machine hash) for the web UI n8nmcp-proxy: - image: ghcr.io/yundera/nginx-hash-lock:1.0.5 + image: ghcr.io/yundera/appshield:2.0.3 + container_name: n8nmcp + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: n8nmcp restart: unless-stopped cpu_shares: 50 expose: @@ -46,13 +52,14 @@ services: environment: ALLOWED_PATHS: "mcp" AUTH_HASH: $AUTH_HASH - BACKEND_HOST: n8nmcp + BACKEND_HOST: n8nmcp-backend BACKEND_PORT: "9640" LISTEN_PORT: "80" - USER: "ADMIN" - PASSWORD: $APP_DEFAULT_PASSWORD + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" depends_on: - - n8nmcp + - n8nmcp-backend networks: - pcs labels: @@ -74,7 +81,7 @@ x-casaos: architectures: - amd64 - arm64 - main: n8nmcp + main: n8nmcp-backend index: /?hash=$AUTH_HASH author: Aptero category: Developer @@ -137,7 +144,7 @@ x-casaos: 2. In n8n: **Settings → n8n API → Create an API key** **Setup (2 steps):** - 1. Open this app's Web UI (you'll be prompted for the `ADMIN` password `$APP_DEFAULT_PASSWORD`) + 1. Open this app's Web UI (you'll sign in with your Yundera account — no extra password) 2. Paste the **n8n Base URL** (`http://n8n:80` is pre-filled for the companion app) and the **API key**, click **Test connection** → **Save** **MCP Integration (for AI/LLMs):** @@ -190,4 +197,4 @@ x-casaos: **Security:** - The API key is stored only in `/DATA/AppData/n8nmcp/config.json` and is never logged or returned via the API - - The Web UI sits behind the Yundera hash-lock — you'll need the admin password to access it + - The Web UI is protected by your Yundera single sign-on — no extra login needed diff --git a/Apps/Netdata/docker-compose.yml b/Apps/Netdata/docker-compose.yml index 4a4bc58..d9aee61 100644 --- a/Apps/Netdata/docker-compose.yml +++ b/Apps/Netdata/docker-compose.yml @@ -2,15 +2,23 @@ name: netdata services: nginxhashlock: - image: ghcr.io/yundera/nginx-hash-lock:latest - container_name: netdata-nginxhashlock + image: ghcr.io/yundera/appshield:2.0.3 + container_name: netdata + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: netdata restart: unless-stopped user: "root" environment: AUTH_HASH: $AUTH_HASH - BACKEND_HOST: "netdata" + BACKEND_HOST: "netdata-backend" BACKEND_PORT: "19999" LISTEN_PORT: "80" + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" expose: - 80 labels: @@ -23,7 +31,7 @@ services: caddy_2: netdata-${APP_PUBLIC_IP_DASH}.sslip.io caddy_2.reverse_proxy: "{{upstreams 80}}" depends_on: - - netdata + - netdata-backend deploy: resources: limits: @@ -35,9 +43,10 @@ services: cap_add: - SYS_ADMIN - NET_ADMIN - netdata: + netdata-backend: image: netdata/netdata:v2.10.3 - hostname: netdata + container_name: netdata-backend + hostname: netdata-backend user: 0:0 environment: PUID: $PUID diff --git a/Apps/NextcloudMCP/docker-compose.yml b/Apps/NextcloudMCP/docker-compose.yml index a19c281..8f33add 100644 --- a/Apps/NextcloudMCP/docker-compose.yml +++ b/Apps/NextcloudMCP/docker-compose.yml @@ -1,13 +1,13 @@ name: nextcloudmcp services: # Main MCP wrapper — exposes /mcp (Nextcloud tools), /talk-mcp (Talk tools), - # and the Web UI on 9650. Reachable at nextcloudmcp:9650 inside the pcs + # and the Web UI on 9650. Reachable at nextcloudmcp-backend:9650 inside the pcs # network so Beacon and other MCP clients can call it directly (no hash # auth needed for container-to-container traffic). - nextcloudmcp: + nextcloudmcp-backend: image: ghcr.io/worph/nextcloud-mcp:1.0.2 - container_name: nextcloudmcp - hostname: nextcloudmcp + container_name: nextcloudmcp-backend + hostname: nextcloudmcp-backend # supervisord is PID 1 and MUST start as root so it can drop privileges for # each child program. Running as a non-root uid fails with "Can't drop # privilege as nonroot user". Child processes drop to their own uid as @@ -45,10 +45,16 @@ services: # Nginx-hash-lock gates the Web UI so the Nextcloud app-password you paste # into it isn't reachable by anyone guessing the subdomain. MCP clients - # talking to `nextcloudmcp:9650` directly on the pcs network bypass this, + # talking to `nextcloudmcp-backend:9650` directly on the pcs network bypass this, # which is what we want — Beacon discovery, Claude Code, etc. stay fast. nextcloudmcp-proxy: - image: ghcr.io/yundera/nginx-hash-lock:1.0.5 + image: ghcr.io/yundera/appshield:2.0.3 + container_name: nextcloudmcp + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: nextcloudmcp restart: unless-stopped cpu_shares: 50 expose: @@ -56,13 +62,14 @@ services: user: "root" environment: AUTH_HASH: $AUTH_HASH - BACKEND_HOST: nextcloudmcp + BACKEND_HOST: nextcloudmcp-backend BACKEND_PORT: "9650" LISTEN_PORT: "80" - USER: "ADMIN" - PASSWORD: $APP_DEFAULT_PASSWORD + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" depends_on: - - nextcloudmcp + - nextcloudmcp-backend networks: - pcs labels: @@ -84,7 +91,7 @@ x-casaos: architectures: - amd64 - arm64 - main: nextcloudmcp + main: nextcloudmcp-backend index: /?hash=$AUTH_HASH author: Aptero category: Utilities @@ -157,7 +164,7 @@ x-casaos: Nextcloud MCP needs access to a Nextcloud account (the sibling `Nextcloud` app works great). After installation: 1. In Nextcloud, go to **Settings → Personal → Security → Devices & sessions** and click **Create new app password** (name it `nextcloudmcp`). - 2. Open the Nextcloud MCP Web UI (you'll be prompted for the `ADMIN` password `$APP_DEFAULT_PASSWORD`) and paste the URL, username, and app-password. + 2. Open the Nextcloud MCP Web UI (you'll sign in with your Yundera account — no extra password) and paste the URL, username, and app-password. 3. Click **Test connection**, then **Save**. 4. (Optional) In the **LLM Target** panel, click **Rescan Beacon** — if an LLM MCP (e.g. ClaudeCode) is installed on the same server, it's auto-selected. 5. (Optional) Enable **Auto-respond** to let Claude reply to your Talk DMs and `@` mentions automatically. @@ -193,11 +200,11 @@ x-casaos: "mcpServers": { "nextcloud-mcp": { "type": "http", - "url": "http://nextcloudmcp:9650/mcp" + "url": "http://nextcloudmcp-backend:9650/mcp" }, "nextcloud-talk-mcp": { "type": "http", - "url": "http://nextcloudmcp:9650/talk-mcp" + "url": "http://nextcloudmcp-backend:9650/talk-mcp" } } } @@ -230,14 +237,14 @@ x-casaos: **Security:** - Your Nextcloud app-password is stored only in `/DATA/AppData/nextcloudmcp/config.json` and is never logged or returned via the API. - - The Web UI sits behind the Yundera hash-lock — you'll need the admin password to access it. + - The Web UI is protected by your Yundera single sign-on — no extra login needed. fr_fr: | **Demarrage** Nextcloud MCP a besoin d'acces a un compte Nextcloud (l'app soeur `Nextcloud` fonctionne parfaitement). Apres installation: 1. Dans Nextcloud, allez dans **Parametres → Personnel → Securite → Appareils et sessions** et cliquez sur **Creer un nouveau mot de passe d'application** (nommez-le `nextcloudmcp`). - 2. Ouvrez l'interface Web de Nextcloud MCP (le mot de passe `ADMIN` `$APP_DEFAULT_PASSWORD` vous sera demande) et collez l'URL, le nom d'utilisateur et le mot de passe d'application. + 2. Ouvrez l'interface Web de Nextcloud MCP (vous vous connecterez avec votre compte Yundera — aucun mot de passe supplementaire) et collez l'URL, le nom d'utilisateur et le mot de passe d'application. 3. Cliquez sur **Tester la connexion**, puis **Enregistrer**. 4. (Optionnel) Dans le panneau **LLM Target**, cliquez sur **Rescan Beacon** — si un LLM MCP (ex. ClaudeCode) est installe sur le meme serveur, il est auto-selectionne. 5. (Optionnel) Activez **Reponse automatique** pour laisser Claude repondre automatiquement a vos messages prives Talk et aux mentions `@`. @@ -273,11 +280,11 @@ x-casaos: "mcpServers": { "nextcloud-mcp": { "type": "http", - "url": "http://nextcloudmcp:9650/mcp" + "url": "http://nextcloudmcp-backend:9650/mcp" }, "nextcloud-talk-mcp": { "type": "http", - "url": "http://nextcloudmcp:9650/talk-mcp" + "url": "http://nextcloudmcp-backend:9650/talk-mcp" } } } @@ -294,14 +301,14 @@ x-casaos: **Securite:** - Votre mot de passe d'application Nextcloud est stocke uniquement dans `/DATA/AppData/nextcloudmcp/config.json` et n'est jamais journalise ni retourne via l'API. - - L'interface Web est protegee par le hash-lock Yundera — le mot de passe admin est requis pour y acceder. + - L'interface Web est protegee par votre authentification unique Yundera (SSO) — aucune connexion supplementaire. zh_cn: | **快速开始** Nextcloud MCP 需要访问 Nextcloud 账户(同级 `Nextcloud` 应用即可)。安装后: 1. 在 Nextcloud 中,转到 **设置 → 个人 → 安全 → 设备和会话**,点击 **创建新的应用密码**(命名为 `nextcloudmcp`)。 - 2. 打开 Nextcloud MCP Web UI(会提示输入 `ADMIN` 密码 `$APP_DEFAULT_PASSWORD`),粘贴 URL、用户名和应用密码。 + 2. 打开 Nextcloud MCP Web UI(使用您的 Yundera 账户登录——无需额外密码),粘贴 URL、用户名和应用密码。 3. 点击 **测试连接**,然后 **保存**。 4. (可选)在 **LLM Target** 面板中,点击 **重新扫描 Beacon** — 如果同一服务器上安装了 LLM MCP(例如 ClaudeCode),它将被自动选中。 5. (可选)启用 **自动回复**,让 Claude 自动回复您的 Talk 私信和 `@<您>` 提及。 @@ -331,7 +338,7 @@ x-casaos: "mcpServers": { "nextcloud-mcp": { "type": "http", - "url": "http://nextcloudmcp:9650/mcp" + "url": "http://nextcloudmcp-backend:9650/mcp" } } } @@ -340,4 +347,4 @@ x-casaos: **安全:** - Nextcloud 应用密码仅存储在 `/DATA/AppData/nextcloudmcp/config.json` 中,不会被记录或通过 API 返回。 - - Web UI 位于 Yundera hash-lock 之后 — 需要管理员密码才能访问。 + - Web UI 受您的 Yundera 单点登录 (SSO) 保护——无需额外登录。 diff --git a/Apps/NoteDiscovery/docker-compose.yml b/Apps/NoteDiscovery/docker-compose.yml new file mode 100644 index 0000000..65ff0ab --- /dev/null +++ b/Apps/NoteDiscovery/docker-compose.yml @@ -0,0 +1,223 @@ +name: notediscovery + +services: + notediscovery: + image: ghcr.io/gamosoft/notediscovery:0.19.2 + container_name: notediscovery + restart: unless-stopped + user: "0:0" + networks: + - pcs + expose: + - "80" + labels: + caddy_0: notediscovery-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 80}}" + caddy_1: notediscovery-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 80}}" + caddy_2: notediscovery-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 80}}" + volumes: + - /DATA/AppData/$AppID/data/:/app/data + environment: + TZ: $TZ + PORT: 80 + AUTHENTICATION_ENABLED: true + AUTHENTICATION_PASSWORD: $APP_DEFAULT_PASSWORD + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:80/health')"] + interval: 60s + timeout: 3s + retries: 3 + start_period: 5s + deploy: + resources: + limits: + memory: 512M + cpu_shares: 50 + +networks: + pcs: + name: pcs + external: true + +x-casaos: + architectures: + - amd64 + - arm64 + main: notediscovery + webui_port: 80 + author: Yundera Team + category: Documents + developer: gamosoft + store_app_id: notediscovery + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/NoteDiscovery/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/NoteDiscovery/screenshot-1.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/NoteDiscovery/screenshot-2.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/NoteDiscovery/screenshot-3.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/NoteDiscovery/screenshot-1.png + description: + en_us: | + **Your private, self-hosted knowledge base** + + NoteDiscovery is a lightweight, privacy-first note-taking app. Your notes are stored as plain markdown files — no vendor lock-in, no data sent to external services. + + **Key Features:** + - Plain markdown files with no vendor lock-in + - Graph view for visualizing connected notes + - Instant full-text search across all notes + - LaTeX/MathJax mathematical equation support + - Mermaid diagram creation + - HTML export functionality + - Built-in password protection + - Dark and light mode + + **Perfect for:** Anyone who wants a private, fast, and simple knowledge base with full data ownership. + ko_kr: | + **프라이빗 셀프 호스팅 지식 베이스** + + NoteDiscovery는 가볍고 프라이버시 우선의 노트 앱입니다. 노트는 일반 마크다운 파일로 저장되어 벤더 종속 없이, 외부 서비스로 데이터가 전송되지 않습니다. + + **주요 기능:** + - 벤더 종속 없는 일반 마크다운 파일 저장 + - 연결된 노트를 시각화하는 그래프 뷰 + - 모든 노트에 걸친 즉각 전문 검색 + - LaTeX/MathJax 수학 수식 지원 + - Mermaid 다이어그램 생성 + - HTML 내보내기 기능 + - 내장 비밀번호 보호 + - 다크 및 라이트 모드 + + **이런 분께 완벽해요:** 완전한 데이터 소유권을 가진 프라이빗하고 빠르며 심플한 지식 베이스를 원하는 모든 분. + zh_cn: | + **您的私有自托管知识库** + + NoteDiscovery 是一个轻量级、隐私优先的笔记应用。您的笔记以纯 Markdown 文件存储——无供应商锁定,无数据发送到外部服务。 + + **主要功能:** + - 纯 Markdown 文件存储,无供应商锁定 + - 图形视图可视化连接的笔记 + - 跨所有笔记的即时全文搜索 + - LaTeX/MathJax 数学公式支持 + - Mermaid 图表创建 + - HTML 导出功能 + - 内置密码保护 + - 深色和浅色模式 + + **非常适合:** 任何想要拥有完全数据所有权的私有、快速、简单知识库的人。 + fr_fr: | + **Votre base de connaissances privée et auto-hébergée** + + NoteDiscovery est une application de prise de notes légère et axée sur la confidentialité. Vos notes sont stockées sous forme de fichiers Markdown — sans verrouillage fournisseur, sans envoi de données à des services externes. + + **Fonctionnalités principales :** + - Fichiers Markdown sans verrouillage fournisseur + - Vue graphique pour visualiser les notes connectées + - Recherche plein texte instantanée dans toutes les notes + - Support des équations mathématiques LaTeX/MathJax + - Création de diagrammes Mermaid + - Fonctionnalité d'export HTML + - Protection par mot de passe intégrée + - Mode sombre et clair + + **Parfait pour :** Toute personne souhaitant une base de connaissances privée, rapide et simple avec une propriété complète des données. + es_es: | + **Tu base de conocimiento privada y auto-alojada** + + NoteDiscovery es una aplicación de notas ligera y centrada en la privacidad. Tus notas se almacenan como archivos Markdown — sin bloqueo de proveedor, sin envío de datos a servicios externos. + + **Características principales:** + - Archivos Markdown sin bloqueo de proveedor + - Vista de grafo para visualizar notas conectadas + - Búsqueda de texto completo instantánea en todas las notas + - Soporte de ecuaciones matemáticas LaTeX/MathJax + - Creación de diagramas Mermaid + - Funcionalidad de exportación HTML + - Protección por contraseña integrada + - Modo oscuro y claro + + **Perfecto para:** Cualquiera que quiera una base de conocimiento privada, rápida y simple con propiedad completa de los datos. + tagline: + en_us: Private self-hosted knowledge base with markdown notes + ko_kr: 마크다운 노트 기반 프라이빗 셀프 호스팅 지식 베이스 + zh_cn: 基于 Markdown 笔记的私有自托管知识库 + fr_fr: Base de connaissances privée auto-hébergée avec notes Markdown + es_es: Base de conocimiento privada auto-alojada con notas Markdown + title: + en_us: NoteDiscovery + tips: + before_install: + en_us: | + **Getting Started:** + + 1. After installation, visit your NoteDiscovery instance + 2. Log in with password: `$APP_DEFAULT_PASSWORD` + 3. Start creating your first note + + **Features:** + - Notes are stored as plain markdown files + - Graph view to visualize note connections + - Full-text search across all notes + - LaTeX, Mermaid diagrams supported + + **Note:** Password protection is enabled by default. + ko_kr: | + **시작하기:** + + 1. 설치 후 NoteDiscovery 인스턴스를 방문하세요 + 2. 비밀번호로 로그인: `$APP_DEFAULT_PASSWORD` + 3. 첫 번째 노트 작성을 시작하세요 + + **기능:** + - 노트는 일반 마크다운 파일로 저장 + - 노트 연결을 시각화하는 그래프 뷰 + - 모든 노트에 걸친 전문 검색 + - LaTeX, Mermaid 다이어그램 지원 + + **참고:** 비밀번호 보호가 기본으로 활성화되어 있습니다. + zh_cn: | + **开始使用:** + + 1. 安装后,访问您的 NoteDiscovery 实例 + 2. 使用密码登录:`$APP_DEFAULT_PASSWORD` + 3. 开始创建您的第一个笔记 + + **功能:** + - 笔记以纯 Markdown 文件存储 + - 图形视图可视化笔记连接 + - 跨所有笔记的全文搜索 + - 支持 LaTeX、Mermaid 图表 + + **注意:** 密码保护默认已启用。 + fr_fr: | + **Commencer :** + + 1. Après l'installation, visitez votre instance NoteDiscovery + 2. Connectez-vous avec le mot de passe : `$APP_DEFAULT_PASSWORD` + 3. Commencez à créer votre première note + + **Fonctionnalités :** + - Notes stockées sous forme de fichiers Markdown + - Vue graphique pour visualiser les connexions + - Recherche plein texte dans toutes les notes + - Support LaTeX, diagrammes Mermaid + + **Note :** La protection par mot de passe est activée par défaut. + es_es: | + **Comenzar:** + + 1. Después de la instalación, visite su instancia de NoteDiscovery + 2. Inicie sesión con la contraseña: `$APP_DEFAULT_PASSWORD` + 3. Comience a crear su primera nota + + **Características:** + - Notas almacenadas como archivos Markdown + - Vista de grafo para visualizar conexiones + - Búsqueda de texto completo en todas las notas + - Soporte para LaTeX, diagramas Mermaid + + **Nota:** La protección por contraseña está activada por defecto. + index: / diff --git a/Apps/NoteDiscovery/icon.png b/Apps/NoteDiscovery/icon.png new file mode 100644 index 0000000..5b67f4b Binary files /dev/null and b/Apps/NoteDiscovery/icon.png differ diff --git a/Apps/NoteDiscovery/rationale.md b/Apps/NoteDiscovery/rationale.md new file mode 100644 index 0000000..5efcd06 --- /dev/null +++ b/Apps/NoteDiscovery/rationale.md @@ -0,0 +1,22 @@ +# NoteDiscovery — Rationale + +## What deviation / exception is being requested +Runs as `user: 0:0` (root). + +## Why it is necessary +The NoteDiscovery container image requires root to bind to port 80 and manage file permissions in `/app/data`. The application writes markdown note files and search indexes that need consistent ownership. + +## Security mitigations in place +- All volumes map exclusively to `/DATA/AppData/$AppID/` — no access to user directories +- No privileged mode +- Memory limited to 512M via `deploy.resources.limits` +- cpu_shares set to 50 (standard) +- Password protection enabled by default (`AUTHENTICATION_ENABLED: true`) +- Only port 80 exposed internally (not published to host) + +## Alternatives considered and rejected +- `user: $PUID:$PGID` — causes permission errors when the application tries to write note files and indexes to `/app/data` + +## Data protection +- All persistent data stored under `/DATA/AppData/$AppID/data/` +- Notes are stored as plain markdown files, easily portable diff --git a/Apps/NoteDiscovery/screenshot-1.png b/Apps/NoteDiscovery/screenshot-1.png new file mode 100644 index 0000000..7f4aa43 Binary files /dev/null and b/Apps/NoteDiscovery/screenshot-1.png differ diff --git a/Apps/NoteDiscovery/screenshot-2.png b/Apps/NoteDiscovery/screenshot-2.png new file mode 100644 index 0000000..20327ab Binary files /dev/null and b/Apps/NoteDiscovery/screenshot-2.png differ diff --git a/Apps/NoteDiscovery/screenshot-3.png b/Apps/NoteDiscovery/screenshot-3.png new file mode 100644 index 0000000..ac6efe8 Binary files /dev/null and b/Apps/NoteDiscovery/screenshot-3.png differ diff --git a/Apps/Odoo/docker-compose.yml b/Apps/Odoo/docker-compose.yml index e55ab95..12b908a 100644 --- a/Apps/Odoo/docker-compose.yml +++ b/Apps/Odoo/docker-compose.yml @@ -30,7 +30,7 @@ services: odoo: image: odoo:19.0 user: "0:0" - command: odoo --http-port=80 + command: odoo --http-port=80 --data-dir=/var/lib/odoo depends_on: postgres: condition: service_healthy diff --git a/Apps/Ollama/docker-compose.yml b/Apps/Ollama/docker-compose.yml new file mode 100644 index 0000000..cfb336d --- /dev/null +++ b/Apps/Ollama/docker-compose.yml @@ -0,0 +1,213 @@ +name: ollama + +services: + ollama: + image: ghcr.io/open-webui/open-webui:0.9.6 + user: 0:0 + container_name: ollama + restart: unless-stopped + expose: + - 8080 + labels: + caddy_0: ollama-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 8080}}" + caddy_1: ollama-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 8080}}" + caddy_2: ollama-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 8080}}" + networks: + - default + - pcs + environment: + OLLAMA_BASE_URL: http://ollama-api:11434 + WEBUI_URL: https://ollama-${APP_DOMAIN} + WEBUI_SECRET_KEY: $APP_DEFAULT_PASSWORD + volumes: + - /DATA/AppData/ollama/webui:/app/backend/data + depends_on: + - ollama-api + cpu_shares: 50 + deploy: + resources: + limits: + memory: 1G + + ollama-api: + image: ollama/ollama:0.30.7 + user: 0:0 + container_name: ollama-api + restart: unless-stopped + expose: + - 11434 + networks: + - default + environment: + OLLAMA_KEEP_ALIVE: "30m" + OLLAMA_NUM_PARALLEL: "1" + OLLAMA_MAX_LOADED_MODELS: "1" + volumes: + - /DATA/AppData/ollama/data:/root/.ollama + cpu_shares: 20 + # No memory limit: model size varies and inference needs all available RAM (see rationale.md) + +networks: + default: + name: ollama_default + pcs: + name: pcs + external: true + +x-casaos: + main: ollama + index: / + store_app_id: ollama + pre-install-cmd: | + mkdir -p /DATA/AppData/ollama/data && + mkdir -p /DATA/AppData/ollama/webui + post-install-cmd: | + docker exec ollama curl -fsS --retry 90 --retry-delay 2 --retry-connrefused http://ollama-api:11434/api/version && docker exec ollama-api ollama pull llama3.2:1b + architectures: + - amd64 + - arm64 + author: Yundera Team + category: Chat + developer: Open WebUI + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Ollama/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Ollama/screenshot-1.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Ollama/screenshot-2.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Ollama/screenshot-3.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Ollama/thumbnail.png + title: + en_us: Ollama (CPU) + tagline: + en_us: Run and chat with private AI models on your own server + fr_fr: Exécutez et discutez avec des modèles d'IA privés sur votre propre serveur + es_es: Ejecuta y chatea con modelos de IA privados en tu propio servidor + zh_cn: 在您自己的服务器上运行并与私有AI模型聊天 + ko_kr: 자체 서버에서 비공개 AI 모델을 실행하고 대화하세요 + description: + en_us: | + Ollama with Open WebUI gives you a private, self-hosted ChatGPT-style assistant that runs entirely on your own server — no data leaves your machine. + + **Key features:** + • Chat with open-source models (Llama, Qwen, Gemma, DeepSeek, and more) + • Download and manage models from the built-in interface + • Multiple conversations with full history, saved locally + • Markdown, code highlighting, and document/image chat + • Multi-user support with admin controls + + **Perfect for:** Anyone who wants a fully private AI chat assistant without sending prompts to a cloud provider. + + **Note:** Runs on CPU by default — start with small models (1–3B). Larger models need more RAM or a GPU. + fr_fr: | + Ollama avec Open WebUI vous offre un assistant privé de type ChatGPT, auto-hébergé et fonctionnant entièrement sur votre propre serveur — aucune donnée ne quitte votre machine. + + **Fonctionnalités clés :** + • Discutez avec des modèles open source (Llama, Qwen, Gemma, DeepSeek, etc.) + • Téléchargez et gérez les modèles depuis l'interface intégrée + • Plusieurs conversations avec historique complet, enregistrées localement + • Markdown, coloration syntaxique et discussion avec documents/images + • Support multi-utilisateurs avec contrôles administrateur + + **Parfait pour :** Toute personne souhaitant un assistant IA totalement privé sans envoyer ses requêtes à un fournisseur cloud. + + **Remarque :** Fonctionne sur CPU par défaut — commencez par de petits modèles (1–3B). Les modèles plus grands nécessitent plus de RAM ou un GPU. + es_es: | + Ollama con Open WebUI te ofrece un asistente privado tipo ChatGPT, autoalojado y que funciona completamente en tu propio servidor: ningún dato sale de tu máquina. + + **Características clave:** + • Chatea con modelos de código abierto (Llama, Qwen, Gemma, DeepSeek y más) + • Descarga y gestiona modelos desde la interfaz integrada + • Múltiples conversaciones con historial completo, guardadas localmente + • Markdown, resaltado de código y chat con documentos/imágenes + • Soporte multiusuario con controles de administrador + + **Perfecto para:** Cualquiera que quiera un asistente de IA totalmente privado sin enviar sus mensajes a un proveedor en la nube. + + **Nota:** Funciona en CPU por defecto: empieza con modelos pequeños (1–3B). Los modelos más grandes necesitan más RAM o una GPU. + zh_cn: | + Ollama 搭配 Open WebUI 为您提供一个私有的、自托管的 ChatGPT 式助手,完全运行在您自己的服务器上——没有任何数据离开您的设备。 + + **主要功能:** + • 与开源模型聊天(Llama、Qwen、Gemma、DeepSeek 等) + • 通过内置界面下载和管理模型 + • 多个对话,保留完整历史记录,本地保存 + • 支持 Markdown、代码高亮以及文档/图片对话 + • 多用户支持及管理员控制 + + **适合人群:** 任何想要完全私有的 AI 聊天助手、又不愿将提示词发送给云服务商的人。 + + **注意:** 默认使用 CPU 运行——请从小模型(1–3B)开始。更大的模型需要更多内存或 GPU。 + ko_kr: | + Open WebUI와 함께하는 Ollama는 전적으로 자체 서버에서 실행되는 비공개 자체 호스팅 ChatGPT형 어시스턴트를 제공합니다 — 어떤 데이터도 기기를 벗어나지 않습니다. + + **주요 기능:** + • 오픈소스 모델과 대화 (Llama, Qwen, Gemma, DeepSeek 등) + • 내장 인터페이스에서 모델 다운로드 및 관리 + • 전체 기록이 로컬에 저장되는 다중 대화 + • 마크다운, 코드 강조, 문서/이미지 대화 + • 관리자 제어가 포함된 다중 사용자 지원 + + **이런 분께 좋습니다:** 프롬프트를 클라우드 제공업체로 보내지 않고 완전히 비공개로 AI 채팅 어시스턴트를 사용하고 싶은 모든 분. + + **참고:** 기본적으로 CPU에서 실행됩니다 — 작은 모델(1–3B)부터 시작하세요. 더 큰 모델은 더 많은 RAM 또는 GPU가 필요합니다. + tips: + before_install: + en_us: | + **Getting Started** + + Open WebUI uses its own account system. **The first account you create becomes the administrator** — open the app, click *Sign up*, and register with your email and a password. + + **How to use:** + 1. Open the app and create your account (first sign-up = admin) + 2. Go to **Admin Panel → Settings → Models** and pull a model (e.g. `llama3.2:1b`) + 3. Start a new chat and select your model + + **Note:** There is no email-based password reset — keep your credentials safe. Runs on CPU by default; start with small models (1–3B). + fr_fr: | + **Pour commencer** + + Open WebUI utilise son propre système de comptes. **Le premier compte que vous créez devient l'administrateur** — ouvrez l'app, cliquez sur *S'inscrire* et enregistrez-vous avec votre e-mail et un mot de passe. + + **Comment utiliser :** + 1. Ouvrez l'app et créez votre compte (première inscription = admin) + 2. Allez dans **Panneau d'administration → Paramètres → Modèles** et téléchargez un modèle (ex. `llama3.2:1b`) + 3. Démarrez une nouvelle conversation et sélectionnez votre modèle + + **Remarque :** Il n'y a pas de réinitialisation de mot de passe par e-mail — conservez vos identifiants en lieu sûr. Fonctionne sur CPU par défaut ; commencez par de petits modèles (1–3B). + es_es: | + **Para empezar** + + Open WebUI usa su propio sistema de cuentas. **La primera cuenta que crees se convierte en administrador** — abre la app, haz clic en *Registrarse* y regístrate con tu correo y una contraseña. + + **Cómo usar:** + 1. Abre la app y crea tu cuenta (primer registro = admin) + 2. Ve a **Panel de administración → Configuración → Modelos** y descarga un modelo (ej. `llama3.2:1b`) + 3. Inicia un nuevo chat y selecciona tu modelo + + **Nota:** No hay restablecimiento de contraseña por correo — guarda bien tus credenciales. Funciona en CPU por defecto; empieza con modelos pequeños (1–3B). + zh_cn: | + **开始使用** + + Open WebUI 使用自己的账户系统。**您创建的第一个账户将成为管理员** — 打开应用,点击 *注册*,使用您的邮箱和密码进行注册。 + + **使用方法:** + 1. 打开应用并创建账户(首次注册 = 管理员) + 2. 进入 **管理面板 → 设置 → 模型** 并下载一个模型(例如 `llama3.2:1b`) + 3. 开始新对话并选择您的模型 + + **注意:** 没有基于邮箱的密码重置功能——请妥善保管您的凭据。默认使用 CPU 运行;请从小模型(1–3B)开始。 + ko_kr: | + **시작하기** + + Open WebUI는 자체 계정 시스템을 사용합니다. **처음 만든 계정이 관리자가 됩니다** — 앱을 열고 *회원가입*을 클릭한 뒤 이메일과 비밀번호로 등록하세요. + + **사용 방법:** + 1. 앱을 열고 계정을 생성하세요 (첫 가입 = 관리자) + 2. **관리자 패널 → 설정 → 모델**로 이동하여 모델을 다운로드하세요 (예: `llama3.2:1b`) + 3. 새 대화를 시작하고 모델을 선택하세요 + + **참고:** 이메일 기반 비밀번호 재설정이 없습니다 — 자격 증명을 안전하게 보관하세요. 기본적으로 CPU에서 실행되며, 작은 모델(1–3B)부터 시작하세요. diff --git a/Apps/Ollama/icon.png b/Apps/Ollama/icon.png new file mode 100644 index 0000000..8cd2cf1 Binary files /dev/null and b/Apps/Ollama/icon.png differ diff --git a/Apps/Ollama/rationale.md b/Apps/Ollama/rationale.md new file mode 100644 index 0000000..293364b --- /dev/null +++ b/Apps/Ollama/rationale.md @@ -0,0 +1,24 @@ +# Rationale + +## No memory limit on `ollama-api` + +The store guidelines require a memory limit on every service. The `ollama-api` +(inference) container intentionally has **no `deploy.resources.limits.memory`**. + +**Why:** +- The RAM footprint is determined entirely by the model the user pulls at + runtime (a 1B model needs ~1.5 GB, a 7B/8B model needs ~5–6 GB), so no single + static limit fits all cases. +- A limit smaller than the loaded model forces the model into swap, which on a + CPU-only PCS makes every generated token do disk I/O — inference becomes + effectively unusable (observed: a 5.2 GB model under a 4 GB cap pinned the + container at 99.99% and drove the host into 2+ GB of swap). +- Ollama only loads a model on demand and unloads it after `OLLAMA_KEEP_ALIVE`, + so idle memory use stays low; it does not hold the cap's worth of RAM + continuously. + +The web UI container (`ollama`) keeps its 1 GB limit — only the inference +engine is uncapped. + +**User guidance:** choose a model that fits the server's RAM. On a typical +~8 GB CPU-only PCS, stick to 1B–3B models for responsive performance. diff --git a/Apps/Ollama/screenshot-1.png b/Apps/Ollama/screenshot-1.png new file mode 100644 index 0000000..4478efb Binary files /dev/null and b/Apps/Ollama/screenshot-1.png differ diff --git a/Apps/Ollama/screenshot-2.png b/Apps/Ollama/screenshot-2.png new file mode 100644 index 0000000..35f9da3 Binary files /dev/null and b/Apps/Ollama/screenshot-2.png differ diff --git a/Apps/Ollama/screenshot-3.png b/Apps/Ollama/screenshot-3.png new file mode 100644 index 0000000..66eceab Binary files /dev/null and b/Apps/Ollama/screenshot-3.png differ diff --git a/Apps/Ollama/thumbnail.png b/Apps/Ollama/thumbnail.png new file mode 100644 index 0000000..0031ce7 Binary files /dev/null and b/Apps/Ollama/thumbnail.png differ diff --git a/Apps/OllamaNvidia/docker-compose.yml b/Apps/OllamaNvidia/docker-compose.yml new file mode 100644 index 0000000..9563ae1 --- /dev/null +++ b/Apps/OllamaNvidia/docker-compose.yml @@ -0,0 +1,246 @@ +name: ollama-nvidia + +services: + ollama: + image: ghcr.io/open-webui/open-webui:0.9.6 + user: 0:0 + container_name: ollama-nvidia + restart: unless-stopped + expose: + - 8080 + labels: + caddy_0: ollama-nvidia-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 8080}}" + caddy_1: ollama-nvidia-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 8080}}" + caddy_2: ollama-nvidia-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 8080}}" + networks: + - default + - pcs + environment: + OLLAMA_BASE_URL: http://ollama-api:11434 + WEBUI_URL: https://ollama-nvidia-${APP_DOMAIN} + WEBUI_SECRET_KEY: $APP_DEFAULT_PASSWORD + volumes: + - /DATA/AppData/ollama-nvidia/webui:/app/backend/data + depends_on: + - ollama-api + cpu_shares: 50 + deploy: + resources: + limits: + memory: 1G + + ollama-api: + image: ollama/ollama:0.30.7 + user: 0:0 + container_name: ollama-nvidia-api + restart: unless-stopped + expose: + - 11434 + networks: + - default + environment: + OLLAMA_KEEP_ALIVE: "30m" + OLLAMA_NUM_PARALLEL: "1" + OLLAMA_MAX_LOADED_MODELS: "1" + volumes: + - /DATA/AppData/ollama-nvidia/data:/root/.ollama + cpu_shares: 20 + # No memory limit: model size varies and inference needs all available VRAM/RAM (see rationale.md). + # GPU passthrough requires the NVIDIA Container Toolkit on the host (see before_install tip). + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: + - gpu + +networks: + default: + name: ollama-nvidia_default + pcs: + name: pcs + external: true + +x-casaos: + main: ollama + index: / + store_app_id: ollama-nvidia + pre-install-cmd: | + mkdir -p /DATA/AppData/ollama-nvidia/data && + mkdir -p /DATA/AppData/ollama-nvidia/webui + post-install-cmd: | + docker exec ollama-nvidia curl -fsS --retry 90 --retry-delay 2 --retry-connrefused http://ollama-api:11434/api/version && docker exec ollama-nvidia-api ollama pull llama3.2:1b + architectures: + - amd64 + author: Yundera Team + category: Chat + developer: Open WebUI + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/OllamaNvidia/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/OllamaNvidia/screenshot-1.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/OllamaNvidia/screenshot-2.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/OllamaNvidia/screenshot-3.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/OllamaNvidia/thumbnail.png + title: + en_us: Ollama (Nvidia GPU) + tagline: + en_us: GPU-accelerated private AI models on your own server + fr_fr: Modèles d'IA privés accélérés par GPU sur votre propre serveur + es_es: Modelos de IA privados acelerados por GPU en tu propio servidor + zh_cn: 在您自己的服务器上运行 GPU 加速的私有 AI 模型 + ko_kr: 자체 서버에서 GPU로 가속되는 비공개 AI 모델 + description: + en_us: | + Ollama with Open WebUI gives you a private, self-hosted ChatGPT-style assistant that runs entirely on your own server — no data leaves your machine. This edition runs inference on an **Nvidia GPU** for dramatically faster responses and support for larger models. + + **Key features:** + • GPU-accelerated inference (Nvidia CUDA) + • Chat with open-source models (Llama, Qwen, Gemma, DeepSeek, and more) + • Download and manage models from the built-in interface + • Multiple conversations with full history, saved locally + • Markdown, code highlighting, and document/image chat + • Multi-user support with admin controls + + **Perfect for:** Anyone with an Nvidia GPU who wants a fast, fully private AI chat assistant without sending prompts to a cloud provider. + + **Requirements:** An Nvidia GPU plus the **NVIDIA Container Toolkit** installed and configured in Docker on the host. If your server has no GPU, install **Ollama (CPU)** instead. + fr_fr: | + Ollama avec Open WebUI vous offre un assistant privé de type ChatGPT, auto-hébergé et fonctionnant entièrement sur votre propre serveur — aucune donnée ne quitte votre machine. Cette édition exécute l'inférence sur un **GPU Nvidia** pour des réponses bien plus rapides et la prise en charge de modèles plus grands. + + **Fonctionnalités clés :** + • Inférence accélérée par GPU (Nvidia CUDA) + • Discutez avec des modèles open source (Llama, Qwen, Gemma, DeepSeek, etc.) + • Téléchargez et gérez les modèles depuis l'interface intégrée + • Plusieurs conversations avec historique complet, enregistrées localement + • Markdown, coloration syntaxique et discussion avec documents/images + • Support multi-utilisateurs avec contrôles administrateur + + **Parfait pour :** Toute personne disposant d'un GPU Nvidia souhaitant un assistant IA rapide et totalement privé sans envoyer ses requêtes à un fournisseur cloud. + + **Prérequis :** Un GPU Nvidia ainsi que le **NVIDIA Container Toolkit** installé et configuré dans Docker sur l'hôte. Si votre serveur n'a pas de GPU, installez plutôt **Ollama (CPU)**. + es_es: | + Ollama con Open WebUI te ofrece un asistente privado tipo ChatGPT, autoalojado y que funciona completamente en tu propio servidor: ningún dato sale de tu máquina. Esta edición ejecuta la inferencia en una **GPU Nvidia** para respuestas mucho más rápidas y compatibilidad con modelos más grandes. + + **Características clave:** + • Inferencia acelerada por GPU (Nvidia CUDA) + • Chatea con modelos de código abierto (Llama, Qwen, Gemma, DeepSeek y más) + • Descarga y gestiona modelos desde la interfaz integrada + • Múltiples conversaciones con historial completo, guardadas localmente + • Markdown, resaltado de código y chat con documentos/imágenes + • Soporte multiusuario con controles de administrador + + **Perfecto para:** Cualquiera con una GPU Nvidia que quiera un asistente de IA rápido y totalmente privado sin enviar sus mensajes a un proveedor en la nube. + + **Requisitos:** Una GPU Nvidia y el **NVIDIA Container Toolkit** instalado y configurado en Docker en el host. Si tu servidor no tiene GPU, instala **Ollama (CPU)** en su lugar. + zh_cn: | + Ollama 搭配 Open WebUI 为您提供一个私有的、自托管的 ChatGPT 式助手,完全运行在您自己的服务器上——没有任何数据离开您的设备。此版本在 **Nvidia GPU** 上进行推理,响应速度大幅提升,并支持更大的模型。 + + **主要功能:** + • GPU 加速推理(Nvidia CUDA) + • 与开源模型聊天(Llama、Qwen、Gemma、DeepSeek 等) + • 通过内置界面下载和管理模型 + • 多个对话,保留完整历史记录,本地保存 + • 支持 Markdown、代码高亮以及文档/图片对话 + • 多用户支持及管理员控制 + + **适合人群:** 拥有 Nvidia GPU、希望获得快速且完全私有的 AI 聊天助手、又不愿将提示词发送给云服务商的人。 + + **要求:** 一块 Nvidia GPU,并在主机的 Docker 中安装和配置 **NVIDIA Container Toolkit**。如果您的服务器没有 GPU,请改装 **Ollama (CPU)**。 + ko_kr: | + Open WebUI와 함께하는 Ollama는 전적으로 자체 서버에서 실행되는 비공개 자체 호스팅 ChatGPT형 어시스턴트를 제공합니다 — 어떤 데이터도 기기를 벗어나지 않습니다. 이 에디션은 **Nvidia GPU**에서 추론을 실행하여 훨씬 빠른 응답과 더 큰 모델을 지원합니다. + + **주요 기능:** + • GPU 가속 추론 (Nvidia CUDA) + • 오픈소스 모델과 대화 (Llama, Qwen, Gemma, DeepSeek 등) + • 내장 인터페이스에서 모델 다운로드 및 관리 + • 전체 기록이 로컬에 저장되는 다중 대화 + • 마크다운, 코드 강조, 문서/이미지 대화 + • 관리자 제어가 포함된 다중 사용자 지원 + + **이런 분께 좋습니다:** Nvidia GPU를 보유하고, 프롬프트를 클라우드 제공업체로 보내지 않으면서 빠르고 완전히 비공개인 AI 채팅 어시스턴트를 원하는 모든 분. + + **요구 사항:** Nvidia GPU와 호스트의 Docker에 설치·구성된 **NVIDIA Container Toolkit**. 서버에 GPU가 없다면 **Ollama (CPU)**를 대신 설치하세요. + tips: + before_install: + en_us: | + **Requires an Nvidia GPU** + + This edition only starts on a host that has an Nvidia GPU **and** the **NVIDIA Container Toolkit** installed and wired into Docker (`nvidia-ctk runtime configure` + Docker restarted). On a host without it, the container will fail to start with *"could not select device driver 'nvidia'"* — use **Ollama (CPU)** instead. + + **Getting Started** + + Open WebUI uses its own account system. **The first account you create becomes the administrator** — open the app, click *Sign up*, and register with your email and a password. + + **How to use:** + 1. Open the app and create your account (first sign-up = admin) + 2. Go to **Admin Panel → Settings → Models** and pull a model (e.g. `llama3.2:1b`) + 3. Start a new chat and select your model + + **Note:** There is no email-based password reset — keep your credentials safe. + fr_fr: | + **Nécessite un GPU Nvidia** + + Cette édition ne démarre que sur un hôte disposant d'un GPU Nvidia **et** du **NVIDIA Container Toolkit** installé et intégré à Docker (`nvidia-ctk runtime configure` + Docker redémarré). Sans cela, le conteneur ne démarrera pas (*« could not select device driver 'nvidia' »*) — utilisez plutôt **Ollama (CPU)**. + + **Pour commencer** + + Open WebUI utilise son propre système de comptes. **Le premier compte que vous créez devient l'administrateur** — ouvrez l'app, cliquez sur *S'inscrire* et enregistrez-vous avec votre e-mail et un mot de passe. + + **Comment utiliser :** + 1. Ouvrez l'app et créez votre compte (première inscription = admin) + 2. Allez dans **Panneau d'administration → Paramètres → Modèles** et téléchargez un modèle (ex. `llama3.2:1b`) + 3. Démarrez une nouvelle conversation et sélectionnez votre modèle + + **Remarque :** Il n'y a pas de réinitialisation de mot de passe par e-mail — conservez vos identifiants en lieu sûr. + es_es: | + **Requiere una GPU Nvidia** + + Esta edición solo arranca en un host con una GPU Nvidia **y** el **NVIDIA Container Toolkit** instalado e integrado en Docker (`nvidia-ctk runtime configure` + Docker reiniciado). Sin ello, el contenedor no arrancará (*"could not select device driver 'nvidia'"*) — usa **Ollama (CPU)** en su lugar. + + **Para empezar** + + Open WebUI usa su propio sistema de cuentas. **La primera cuenta que crees se convierte en administrador** — abre la app, haz clic en *Registrarse* y regístrate con tu correo y una contraseña. + + **Cómo usar:** + 1. Abre la app y crea tu cuenta (primer registro = admin) + 2. Ve a **Panel de administración → Configuración → Modelos** y descarga un modelo (ej. `llama3.2:1b`) + 3. Inicia un nuevo chat y selecciona tu modelo + + **Nota:** No hay restablecimiento de contraseña por correo — guarda bien tus credenciales. + zh_cn: | + **需要 Nvidia GPU** + + 此版本仅在已安装 Nvidia GPU **并且**已将 **NVIDIA Container Toolkit** 安装并接入 Docker(`nvidia-ctk runtime configure` 并重启 Docker)的主机上启动。否则容器将无法启动(*"could not select device driver 'nvidia'"*)——请改用 **Ollama (CPU)**。 + + **开始使用** + + Open WebUI 使用自己的账户系统。**您创建的第一个账户将成为管理员** — 打开应用,点击 *注册*,使用您的邮箱和密码进行注册。 + + **使用方法:** + 1. 打开应用并创建账户(首次注册 = 管理员) + 2. 进入 **管理面板 → 设置 → 模型** 并下载一个模型(例如 `llama3.2:1b`) + 3. 开始新对话并选择您的模型 + + **注意:** 没有基于邮箱的密码重置功能——请妥善保管您的凭据。 + ko_kr: | + **Nvidia GPU가 필요합니다** + + 이 에디션은 Nvidia GPU와 **NVIDIA Container Toolkit**이 설치되어 Docker에 연결된(`nvidia-ctk runtime configure` 후 Docker 재시작) 호스트에서만 시작됩니다. 그렇지 않으면 컨테이너가 *"could not select device driver 'nvidia'"* 오류로 시작되지 않습니다 — 대신 **Ollama (CPU)**를 사용하세요. + + **시작하기** + + Open WebUI는 자체 계정 시스템을 사용합니다. **처음 만든 계정이 관리자가 됩니다** — 앱을 열고 *회원가입*을 클릭한 뒤 이메일과 비밀번호로 등록하세요. + + **사용 방법:** + 1. 앱을 열고 계정을 생성하세요 (첫 가입 = 관리자) + 2. **관리자 패널 → 설정 → 모델**로 이동하여 모델을 다운로드하세요 (예: `llama3.2:1b`) + 3. 새 대화를 시작하고 모델을 선택하세요 + + **참고:** 이메일 기반 비밀번호 재설정이 없습니다 — 자격 증명을 안전하게 보관하세요. diff --git a/Apps/OllamaNvidia/icon.png b/Apps/OllamaNvidia/icon.png new file mode 100644 index 0000000..8cd2cf1 Binary files /dev/null and b/Apps/OllamaNvidia/icon.png differ diff --git a/Apps/OllamaNvidia/rationale.md b/Apps/OllamaNvidia/rationale.md new file mode 100644 index 0000000..8298fdb --- /dev/null +++ b/Apps/OllamaNvidia/rationale.md @@ -0,0 +1,46 @@ +# Rationale + +## No memory limit on `ollama-api` + +The store guidelines require a memory limit on every service. The `ollama-api` +(inference) container intentionally has **no `deploy.resources.limits.memory`**. + +**Why:** +- The RAM/VRAM footprint is determined entirely by the model the user pulls at + runtime (a 1B model needs ~1.5 GB, a 7B/8B model needs ~5–6 GB), so no single + static limit fits all cases. +- The model is loaded into GPU VRAM; the host-side memory limit can still force + swap during load/offload, so an arbitrary cap only hurts. +- Ollama only loads a model on demand and unloads it after `OLLAMA_KEEP_ALIVE`, + so idle memory use stays low; it does not hold the cap's worth of RAM + continuously. + +The web UI container (`ollama`) keeps its 1 GB limit — only the inference +engine is uncapped. + +## GPU edition — host prerequisite + +This edition reserves an Nvidia device: + +```yaml +deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] +``` + +Docker can only satisfy that reservation when the host has an Nvidia GPU **and** +the **NVIDIA Container Toolkit** installed and registered as a Docker runtime +(`nvidia-ctk runtime configure --runtime=docker` followed by a Docker restart). +We deliberately do **not** install the toolkit from a `pre-install-cmd`: +configuring the runtime requires restarting the Docker daemon, which would kill +the very install step performing it. The toolkit is therefore expected to be +provisioned on the host image (Radiant / Contabo GPU instances, or +user-supplied hardware via nsl.sh). On a host without it the container fails to +start; CPU-only users should install **Ollama (CPU)** instead. + +**User guidance:** pick a model that fits the GPU's VRAM for full acceleration; +models larger than VRAM spill to system RAM/CPU and run much slower. diff --git a/Apps/OllamaNvidia/screenshot-1.png b/Apps/OllamaNvidia/screenshot-1.png new file mode 100644 index 0000000..4478efb Binary files /dev/null and b/Apps/OllamaNvidia/screenshot-1.png differ diff --git a/Apps/OllamaNvidia/screenshot-2.png b/Apps/OllamaNvidia/screenshot-2.png new file mode 100644 index 0000000..35f9da3 Binary files /dev/null and b/Apps/OllamaNvidia/screenshot-2.png differ diff --git a/Apps/OllamaNvidia/screenshot-3.png b/Apps/OllamaNvidia/screenshot-3.png new file mode 100644 index 0000000..66eceab Binary files /dev/null and b/Apps/OllamaNvidia/screenshot-3.png differ diff --git a/Apps/OllamaNvidia/thumbnail.png b/Apps/OllamaNvidia/thumbnail.png new file mode 100644 index 0000000..0031ce7 Binary files /dev/null and b/Apps/OllamaNvidia/thumbnail.png differ diff --git a/Apps/Prowlarr/docker-compose.yml b/Apps/Prowlarr/docker-compose.yml new file mode 100644 index 0000000..0cebb18 --- /dev/null +++ b/Apps/Prowlarr/docker-compose.yml @@ -0,0 +1,78 @@ +name: prowlarr + +services: + prowlarr: + image: lscr.io/linuxserver/prowlarr:2.4.0 + container_name: prowlarr + restart: unless-stopped + user: $PUID:$PGID + expose: + - 9696 + labels: + caddy_0: prowlarr-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 9696}}" + caddy_1: prowlarr-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 9696}}" + caddy_2: prowlarr-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 9696}}" + networks: + - pcs + environment: + TZ: $TZ + volumes: + - /DATA/AppData/prowlarr/config:/config # dedicated Prowlarr config + cpu_shares: 10 + deploy: + resources: + limits: + memory: 512M + +networks: + pcs: + name: pcs + external: true + +x-casaos: + main: prowlarr + index: / + pre-install-cmd: | + mkdir -p /DATA/AppData/prowlarr/config + tips: + before_install: + en_us: | + 🔍 **Prowlarr** is an indexer manager — it manages your trackers/indexers in one place and syncs them to Radarr, Sonarr and other *arr apps automatically. + + 📂 Config lives in `/DATA/AppData/prowlarr/`. + + 🔗 **Wire up the full media stack** (each app is available standalone in the store, reachable by container name on the same server): + - **Radarr** (`radarr:7878`) & **Sonarr** (`sonarr:8989`) — add them as *Applications* in Prowlarr so your indexers sync to them automatically. + - **qBittorrent** (`qbittorrent:80`) — add it as a *Download Client* inside **Radarr/Sonarr** for automated grabs (a download client added in Prowlarr only powers Prowlarr's own manual searches and does not sync to apps). + - **Jellyfin** — point it at `/DATA/Media` to stream everything Radarr/Sonarr import. + + ❓ Questions? Contact us at help@yundera.com + architectures: + - amd64 + - arm64 + author: Yundera Team + category: Media + developer: Servarr (Prowlarr) + description: + en_us: Prowlarr is an indexer manager and proxy built on the *arr stack. It integrates seamlessly with Radarr, Sonarr, Lidarr and Readarr, managing all of your trackers and indexers from a single interface and keeping them in sync. Pairs with the Radarr, Sonarr and Lidarr standalone apps in this store. + fr_fr: Prowlarr est un gestionnaire et proxy d'indexeurs construit sur la suite *arr. Il s'intègre parfaitement avec Radarr, Sonarr, Lidarr et Readarr, gérant tous vos trackers et indexeurs depuis une seule interface et les gardant synchronisés. S'associe aux apps Radarr, Sonarr et Lidarr autonomes de ce store. + es_es: Prowlarr es un gestor y proxy de indexadores construido sobre el stack *arr. Se integra perfectamente con Radarr, Sonarr, Lidarr y Readarr, gestionando todos tus trackers e indexadores desde una sola interfaz y manteniéndolos sincronizados. Se combina con las apps Radarr, Sonarr y Lidarr independientes de esta tienda. + zh_cn: Prowlarr 是基于 *arr 堆栈构建的索引器管理器和代理。它与 Radarr、Sonarr、Lidarr 和 Readarr 无缝集成,在单一界面中管理您的所有跟踪器和索引器并保持同步。与本商店中的 Radarr、Sonarr 和 Lidarr 独立应用配合使用。 + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Prowlarr/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Prowlarr/screenshot-1.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Prowlarr/thumbnail.png + tagline: + en_us: Manage all your indexers in one place and sync them to every *arr app. + fr_fr: Gérez tous vos indexeurs en un seul endroit et synchronisez-les avec chaque app *arr. + es_es: Gestiona todos tus indexadores en un solo lugar y sincronízalos con cada app *arr. + zh_cn: 在一处管理所有索引器,并同步到每个 *arr 应用。 + title: + en_us: Prowlarr + store_app_id: prowlarr + is_uncontrolled: false diff --git a/Apps/Prowlarr/icon.png b/Apps/Prowlarr/icon.png new file mode 100644 index 0000000..c2d5a02 Binary files /dev/null and b/Apps/Prowlarr/icon.png differ diff --git a/Apps/Prowlarr/screenshot-1.png b/Apps/Prowlarr/screenshot-1.png new file mode 100644 index 0000000..2951abb Binary files /dev/null and b/Apps/Prowlarr/screenshot-1.png differ diff --git a/Apps/Prowlarr/thumbnail.png b/Apps/Prowlarr/thumbnail.png new file mode 100644 index 0000000..c2d5a02 Binary files /dev/null and b/Apps/Prowlarr/thumbnail.png differ diff --git a/Apps/Radarr/docker-compose.yml b/Apps/Radarr/docker-compose.yml new file mode 100644 index 0000000..94b0c70 --- /dev/null +++ b/Apps/Radarr/docker-compose.yml @@ -0,0 +1,77 @@ +name: radarr + +services: + radarr: + image: lscr.io/linuxserver/radarr:6.2.1 + container_name: radarr + restart: unless-stopped + user: $PUID:$PGID + expose: + - 7878 + labels: + caddy_0: radarr-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 7878}}" + caddy_1: radarr-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 7878}}" + caddy_2: radarr-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 7878}}" + networks: + - pcs + environment: + TZ: $TZ + volumes: + - /DATA/AppData/radarr/config:/config # dedicated Radarr config + - /DATA/Media/Movies:/movies # shared movie library + - /DATA/Downloads:/downloads # shared downloads folder + cpu_shares: 10 + deploy: + resources: + limits: + memory: 512M + +networks: + pcs: + name: pcs + external: true + +x-casaos: + main: radarr + index: / + pre-install-cmd: | + mkdir -p /DATA/AppData/radarr/config "/DATA/Media/Movies" /DATA/Downloads + tips: + before_install: + en_us: | + 🎬 **Radarr** is a movie collection manager — it monitors, downloads and organises your movies automatically. + + 📂 Config lives in `/DATA/AppData/radarr/`, movies in `/DATA/Media/Movies`, downloads in `/DATA/Downloads` — the same folders Jellyfin reads, so they share one library. + + 🔗 Pair it with **Prowlarr** (indexers) and **qBittorrent** (download client) from the store — reach them by container name `prowlarr:9696` and `qbittorrent:80` when they run on the same server. + + ❓ Questions? Contact us at help@yundera.com + architectures: + - amd64 + - arm64 + author: Yundera Team + category: Media + developer: Servarr (Radarr) + description: + en_us: Radarr is a movie collection manager for Usenet and BitTorrent users. It monitors multiple RSS feeds for new movies, grabs, sorts and renames them, and integrates with download clients like qBittorrent and indexers via Prowlarr. Writes into the same `/DATA/Media/Movies` library Jellyfin reads, so your movies are ready to stream. + fr_fr: Radarr est un gestionnaire de collection de films pour les utilisateurs Usenet et BitTorrent. Il surveille plusieurs flux RSS pour les nouveaux films, les récupère, les trie et les renomme, et s'intègre aux clients de téléchargement comme qBittorrent et aux indexeurs via Prowlarr. Écrit dans la même bibliothèque `/DATA/Media/Movies` que Jellyfin lit. + es_es: Radarr es un gestor de colección de películas para usuarios de Usenet y BitTorrent. Monitorea múltiples fuentes RSS de nuevas películas, las descarga, ordena y renombra, e se integra con clientes de descarga como qBittorrent e indexadores vía Prowlarr. Escribe en la misma biblioteca `/DATA/Media/Movies` que lee Jellyfin. + zh_cn: Radarr 是面向 Usenet 和 BitTorrent 用户的电影收藏管理器。它监控多个 RSS 源以获取新电影,抓取、整理并重命名,并通过 Prowlarr 与 qBittorrent 等下载客户端和索引器集成。写入 Jellyfin 读取的同一 /DATA/Media/Movies 媒体库。 + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Radarr/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Radarr/screenshot-1.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Radarr/thumbnail.png + tagline: + en_us: Automatically find, download and organise your movie collection. + fr_fr: Trouvez, téléchargez et organisez automatiquement votre collection de films. + es_es: Encuentra, descarga y organiza automáticamente tu colección de películas. + zh_cn: 自动查找、下载并整理您的电影收藏。 + title: + en_us: Radarr + store_app_id: radarr + is_uncontrolled: false diff --git a/Apps/Radarr/icon.png b/Apps/Radarr/icon.png new file mode 100644 index 0000000..417f77f Binary files /dev/null and b/Apps/Radarr/icon.png differ diff --git a/Apps/Radarr/screenshot-1.png b/Apps/Radarr/screenshot-1.png new file mode 100644 index 0000000..2951abb Binary files /dev/null and b/Apps/Radarr/screenshot-1.png differ diff --git a/Apps/Radarr/thumbnail.png b/Apps/Radarr/thumbnail.png new file mode 100644 index 0000000..417f77f Binary files /dev/null and b/Apps/Radarr/thumbnail.png differ diff --git a/Apps/Seafile/docker-compose.yml b/Apps/Seafile/docker-compose.yml index aac01d9..ae601d4 100644 --- a/Apps/Seafile/docker-compose.yml +++ b/Apps/Seafile/docker-compose.yml @@ -115,9 +115,6 @@ services: caddy_0.2_handle_path: "/sdoc-server/*" caddy_0.2_handle_path.0_rewrite: "* {uri}" caddy_0.2_handle_path.1_reverse_proxy: "seadoc:80" - # Rewrite bare / to /accounts/login/ to avoid 302 redirect (CF worker drops body on redirects) - caddy_0.@root.path: "/" - caddy_0.3_rewrite: "@root /accounts/login/" # Default: main seafile app caddy_0.4_reverse_proxy: "{{upstreams 80}}" @@ -132,8 +129,6 @@ services: caddy_1.2_handle_path: "/sdoc-server/*" caddy_1.2_handle_path.0_rewrite: "* {uri}" caddy_1.2_handle_path.1_reverse_proxy: "seadoc:80" - caddy_1.@root.path: "/" - caddy_1.3_rewrite: "@root /accounts/login/" caddy_1.4_reverse_proxy: "{{upstreams 80}}" # --- caddy_2: sslip.io fallback (no gateway_tls) --- @@ -146,8 +141,6 @@ services: caddy_2.2_handle_path: "/sdoc-server/*" caddy_2.2_handle_path.0_rewrite: "* {uri}" caddy_2.2_handle_path.1_reverse_proxy: "seadoc:80" - caddy_2.@root.path: "/" - caddy_2.3_rewrite: "@root /accounts/login/" caddy_2.4_reverse_proxy: "{{upstreams 80}}" depends_on: db: @@ -340,6 +333,19 @@ x-casaos: EOF echo "SMTP configuration added" fi + # Ensure reverse-proxy HTTPS settings are present (mandatory for Caddy TLS termination) + if [ -f "$SEAHUB_SETTINGS" ] && ! grep -q "SECURE_PROXY_SSL_HEADER" "$SEAHUB_SETTINGS"; then + echo "Adding reverse-proxy HTTPS configuration..." + cat >> "$SEAHUB_SETTINGS" << EOF + + # Reverse proxy HTTPS settings (Caddy terminates TLS, forwards X-Forwarded-Proto) + USE_X_FORWARDED_HOST = True + SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + SERVICE_URL = 'https://seafile-$APP_DOMAIN' + FILE_SERVER_ROOT = 'https://seafile-$APP_DOMAIN/seafhttp' + EOF + echo "Reverse-proxy HTTPS configuration added" + fi # Single restart to apply all config changes echo "Restarting Seafile to apply configuration..." docker restart seafile diff --git a/Apps/SegmentPlayer/docker-compose.yml b/Apps/SegmentPlayer/docker-compose.yml index 499c1d1..d09cc62 100644 --- a/Apps/SegmentPlayer/docker-compose.yml +++ b/Apps/SegmentPlayer/docker-compose.yml @@ -3,7 +3,7 @@ name: segment services: segmentplayer-backend: - image: ghcr.io/worph/segmentplayer:1.3.6 + image: ghcr.io/worph/segmentplayer:1.4.2 container_name: segmentplayer-backend deploy: resources: @@ -21,9 +21,14 @@ services: networks: - segment-internal - segmentplayer: - image: ghcr.io/yundera/nginx-hash-lock:1.0.5 - container_name: segmentplayer + segment: + image: ghcr.io/yundera/appshield:2.0.3 + container_name: segment + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: segment restart: unless-stopped user: "0:0" expose: @@ -38,12 +43,12 @@ services: caddy_2: segment-${APP_PUBLIC_IP_DASH}.sslip.io caddy_2.reverse_proxy: "{{upstreams 80}}" environment: - AUTH_HASH: $AUTH_HASH - USER: "segment" - PASSWORD: $APP_DEFAULT_PASSWORD BACKEND_HOST: "segmentplayer-backend" BACKEND_PORT: "80" LISTEN_PORT: "80" + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" depends_on: - segmentplayer-backend networks: @@ -59,9 +64,9 @@ networks: external: true x-casaos: - index: /?hash=$AUTH_HASH + index: / webui_port: 80 - main: segmentplayer + main: segment architectures: - amd64 diff --git a/Apps/SegmentStremioAddon/docker-compose.yml b/Apps/SegmentStremioAddon/docker-compose.yml index 7db3a73..a0f8424 100644 --- a/Apps/SegmentStremioAddon/docker-compose.yml +++ b/Apps/SegmentStremioAddon/docker-compose.yml @@ -2,7 +2,13 @@ name: ssa services: ssa-proxy: - image: ghcr.io/yundera/nginx-hash-lock:1.0.5 + image: ghcr.io/yundera/appshield:2.0.3 + container_name: ssa + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: ssa restart: unless-stopped user: "0:0" expose: @@ -18,22 +24,23 @@ services: caddy_2.reverse_proxy: "{{upstreams 80}}" environment: AUTH_HASH: $AUTH_HASH - USER: "segment" - PASSWORD: $APP_DEFAULT_PASSWORD - BACKEND_HOST: "ssa" + BACKEND_HOST: "ssa-backend" BACKEND_PORT: "80" LISTEN_PORT: "80" ALLOWED_PATHS: "/api/" + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" depends_on: - - ssa + - ssa-backend networks: - ssa-internal - pcs cpu_shares: 50 - ssa: + ssa-backend: image: ghcr.io/worph/segment-stremio-addon:1.3.0 - container_name: ssa + container_name: ssa-backend deploy: resources: limits: @@ -61,7 +68,7 @@ networks: x-casaos: index: /?hash=$AUTH_HASH webui_port: 80 - main: ssa + main: ssa-backend access_domain: ssa-username.nsl.sh architectures: diff --git a/Apps/Sonarr/docker-compose.yml b/Apps/Sonarr/docker-compose.yml new file mode 100644 index 0000000..540340e --- /dev/null +++ b/Apps/Sonarr/docker-compose.yml @@ -0,0 +1,77 @@ +name: sonarr + +services: + sonarr: + image: lscr.io/linuxserver/sonarr:4.0.17 + container_name: sonarr + restart: unless-stopped + user: $PUID:$PGID + expose: + - 8989 + labels: + caddy_0: sonarr-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 8989}}" + caddy_1: sonarr-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 8989}}" + caddy_2: sonarr-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 8989}}" + networks: + - pcs + environment: + TZ: $TZ + volumes: + - /DATA/AppData/sonarr/config:/config # dedicated Sonarr config + - /DATA/Media/TV Shows:/tv # shared TV library (same folder Jellyfin reads) + - /DATA/Downloads:/downloads # shared downloads folder + cpu_shares: 10 + deploy: + resources: + limits: + memory: 512M + +networks: + pcs: + name: pcs + external: true + +x-casaos: + main: sonarr + index: / + pre-install-cmd: | + mkdir -p /DATA/AppData/sonarr/config "/DATA/Media/TV Shows" /DATA/Downloads + tips: + before_install: + en_us: | + 📺 **Sonarr** is a TV series collection manager — it monitors, downloads and organises your shows automatically as new episodes air. + + 📂 Config lives in `/DATA/AppData/sonarr/`, TV shows in `/DATA/Media/TV Shows`, downloads in `/DATA/Downloads` — the same media folders Jellyfin reads, so everything shares one library. + + 🔗 Pair it with **Prowlarr** (indexers) and **qBittorrent** (download client) from the store — reach them by container name `prowlarr:9696` and `qbittorrent:80` when they run on the same server. + + ❓ Questions? Contact us at help@yundera.com + architectures: + - amd64 + - arm64 + author: Yundera Team + category: Media + developer: Servarr (Sonarr) + description: + en_us: Sonarr is a TV series collection manager for Usenet and BitTorrent users. It monitors multiple RSS feeds for new episodes, grabs, sorts and renames them, and integrates with download clients like qBittorrent and indexers via Prowlarr. Uses the same `/DATA/Media/TV Shows` library folder Jellyfin reads. + fr_fr: Sonarr est un gestionnaire de collection de séries TV pour les utilisateurs Usenet et BitTorrent. Il surveille plusieurs flux RSS pour les nouveaux épisodes, les récupère, les trie et les renomme, et s'intègre aux clients de téléchargement comme qBittorrent et aux indexeurs via Prowlarr. Utilise le même dossier de bibliothèque TV que Jellyfin lit. + es_es: Sonarr es un gestor de colección de series de TV para usuarios de Usenet y BitTorrent. Monitorea múltiples fuentes RSS de nuevos episodios, los descarga, ordena y renombra, e se integra con clientes de descarga como qBittorrent e indexadores vía Prowlarr. Usa la misma carpeta de biblioteca de TV que lee Jellyfin. + zh_cn: Sonarr 是面向 Usenet 和 BitTorrent 用户的电视剧收藏管理器。它监控多个 RSS 源以获取新剧集,抓取、整理并重命名,并通过 Prowlarr 与 qBittorrent 等下载客户端和索引器集成。使用与 Jellyfin 相同的电视库文件夹。 + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Sonarr/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Sonarr/screenshot-1.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Sonarr/thumbnail.png + tagline: + en_us: Automatically find, download and organise your TV show collection. + fr_fr: Trouvez, téléchargez et organisez automatiquement votre collection de séries TV. + es_es: Encuentra, descarga y organiza automáticamente tu colección de series de TV. + zh_cn: 自动查找、下载并整理您的电视剧收藏。 + title: + en_us: Sonarr + store_app_id: sonarr + is_uncontrolled: false diff --git a/Apps/Sonarr/icon.png b/Apps/Sonarr/icon.png new file mode 100644 index 0000000..8e08f05 Binary files /dev/null and b/Apps/Sonarr/icon.png differ diff --git a/Apps/Sonarr/screenshot-1.png b/Apps/Sonarr/screenshot-1.png new file mode 100644 index 0000000..2951abb Binary files /dev/null and b/Apps/Sonarr/screenshot-1.png differ diff --git a/Apps/Sonarr/thumbnail.png b/Apps/Sonarr/thumbnail.png new file mode 100644 index 0000000..8e08f05 Binary files /dev/null and b/Apps/Sonarr/thumbnail.png differ diff --git a/Apps/Spliit/docker-compose.yml b/Apps/Spliit/docker-compose.yml index 83676be..fef8858 100644 --- a/Apps/Spliit/docker-compose.yml +++ b/Apps/Spliit/docker-compose.yml @@ -1,17 +1,23 @@ name: spliit services: nginxhashlock: - image: ghcr.io/yundera/nginx-hash-lock:latest - container_name: spliit-nginxhashlock + image: ghcr.io/yundera/appshield:2.0.3 + container_name: spliit + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: spliit restart: unless-stopped user: "root" environment: AUTH_HASH: $AUTH_HASH - USER: admin - PASSWORD: spliit - BACKEND_HOST: "spliit" + BACKEND_HOST: "spliit-backend" BACKEND_PORT: "3000" LISTEN_PORT: "80" + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" expose: - 80 labels: @@ -24,7 +30,7 @@ services: caddy_2: spliit-${APP_PUBLIC_IP_DASH}.sslip.io caddy_2.reverse_proxy: "{{upstreams 80}}" depends_on: - spliit: + spliit-backend: condition: service_started db: condition: service_healthy @@ -66,9 +72,9 @@ services: limits: memory: 512M - spliit: + spliit-backend: image: ghcr.io/spliit-app/spliit:1.19.0 - container_name: spliit + container_name: spliit-backend restart: unless-stopped user: "0:0" cpu_shares: 50 diff --git a/Apps/Stremio/docker-compose.yml b/Apps/Stremio/docker-compose.yml index 002a42b..01997f7 100644 --- a/Apps/Stremio/docker-compose.yml +++ b/Apps/Stremio/docker-compose.yml @@ -2,8 +2,13 @@ name: stremio services: stremio: - image: ghcr.io/yundera/nginx-hash-lock:1.0.5 + image: ghcr.io/yundera/appshield:2.0.3 container_name: stremio + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: stremio restart: unless-stopped user: "root" expose: @@ -21,6 +26,9 @@ services: BACKEND_HOST: "stremiocommunity" BACKEND_PORT: "8080" LISTEN_PORT: "80" + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" tmpfs: - /var/cache/nginx:size=50M depends_on: diff --git a/Apps/TelegramMCP/docker-compose.yml b/Apps/TelegramMCP/docker-compose.yml new file mode 100644 index 0000000..cfdd53f --- /dev/null +++ b/Apps/TelegramMCP/docker-compose.yml @@ -0,0 +1,194 @@ +name: telegrammcp +services: + # Main MCP server — reachable at telegrammcp-backend:9634 on the pcs network. + # Serves the web UI (/), the MCP endpoint (/mcp) and the Telegram webhook + # callback (/webhook), and answers Beacon's UDP discovery on port 9099. + telegrammcp-backend: + image: ghcr.io/worph/telegram-mcp:1.1.21 + container_name: telegrammcp-backend + hostname: telegrammcp-backend + user: $PUID:$PGID + restart: unless-stopped + expose: + - 9634 + - 9099 + environment: + - PUID=$PUID + - PGID=$PGID + - TZ=$TZ + - PORT=9634 + - DISCOVERY_PORT=9099 + # Advertised public base URL. The bot registers its Telegram webhook at + # ${PUBLIC_URL}/webhook and hands this out as the public MCP URL. + - PUBLIC_URL=https://telegrammcp-${APP_DOMAIN} + volumes: + # config.json (bot token + MCP target) is created here on first start + # — CONFIG_PATH=/app/data/config.json is baked into the image. + - type: bind + source: /DATA/AppData/telegrammcp/ + target: /app/data + networks: + - pcs + + # Nginx-hash-lock provides perimeter authentication for the web UI. + # /mcp and /webhook are listed in ALLOWED_PATHS so they bypass the login + # flow: MCP clients and Telegram's webhook callers cannot do the cookie + # dance. Everything else (the config UI at /) sits behind hash + password. + telegrammcp-proxy: + image: ghcr.io/yundera/appshield:2.0.3 + container_name: telegrammcp + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: telegrammcp + restart: unless-stopped + cpu_shares: 50 + expose: + - 80 + user: "root" + environment: + ALLOWED_PATHS: "mcp,webhook" + AUTH_HASH: $AUTH_HASH + BACKEND_HOST: telegrammcp-backend + BACKEND_PORT: "9634" + LISTEN_PORT: "80" + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" + depends_on: + - telegrammcp-backend + networks: + - pcs + labels: + caddy_0: telegrammcp-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 80}}" + caddy_1: telegrammcp-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 80}}" + caddy_2: telegrammcp-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 80}}" + +networks: + pcs: + name: pcs + external: true + +x-casaos: + architectures: + - amd64 + main: telegrammcp-backend + hostname: telegrammcp-${APP_DOMAIN} + scheme: https + port_map: "443" + index: /?hash=${AUTH_HASH} + author: Aptero + category: Chat + description: + en_us: | + **Telegram control for AI assistants via MCP** + + Telegram MCP is a bidirectional bridge between [Telegram](https://telegram.org) and the Model Context Protocol (MCP). It is both an MCP **server** — exposing `send_message` and `send_photo` tools so LLMs can message you on Telegram — and an MCP **client** — forwarding the messages your bot receives to a target MCP server for AI processing. + + Wraps the [`worph/telegram-mcp`](https://github.com/worph/telegram-mcp) server (built on grammY) with a streamable-HTTP transport so any MCP-compatible client can connect (Claude Desktop, Claude Code, Cursor, VS Code, etc.), plus a Web UI for entering the bot token and Beacon auto-discovery. + + **What you can do:** + - Let an AI assistant send you Telegram messages and photos (alerts, summaries, results) + - Route incoming Telegram messages to an LLM and reply automatically + - Approve actions from Telegram with an inline-keyboard permission flow + - Run the bot in polling or webhook mode — configured from the built-in Web UI + + **Think of it like:** A two-way remote between your Telegram chat and any AI assistant. + + **Perfect if you:** Want Claude / Cursor / any MCP client to reach you on Telegram, or to drive an LLM from a Telegram bot — and pair it with other MCP apps (Beacon, n8n, Chronos) for end-to-end AI workflows. + fr_fr: | + **Controle de Telegram pour les assistants IA via MCP** + + Telegram MCP est un pont bidirectionnel entre [Telegram](https://telegram.org) et le Model Context Protocol (MCP). Il est a la fois un **serveur** MCP — exposant les outils `send_message` et `send_photo` pour que les LLMs puissent vous ecrire sur Telegram — et un **client** MCP — transferant les messages recus par votre bot vers un serveur MCP cible pour traitement par IA. + + Enveloppe le serveur [`worph/telegram-mcp`](https://github.com/worph/telegram-mcp) (base sur grammY) avec un transport streamable-HTTP pour que tout client compatible MCP puisse se connecter (Claude Desktop, Claude Code, Cursor, VS Code, etc.), plus une interface Web pour saisir le token du bot et la decouverte automatique via Beacon. + + **Ce que vous pouvez faire:** + - Laisser un assistant IA vous envoyer des messages et photos Telegram (alertes, resumes, resultats) + - Router les messages Telegram entrants vers un LLM et repondre automatiquement + - Approuver des actions depuis Telegram via un clavier en ligne (permission flow) + - Executer le bot en mode polling ou webhook — configure depuis l'interface Web integree + + **Parfait si vous:** Voulez que Claude / Cursor / tout client MCP vous joigne sur Telegram, ou piloter un LLM depuis un bot Telegram — et le combiner avec d'autres apps MCP (Beacon, n8n, Chronos) pour des workflows IA de bout en bout. + developer: Worph + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/TelegramMCP/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/TelegramMCP/screenshot-1.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/TelegramMCP/screenshot-2.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/TelegramMCP/screenshot-3.png + tagline: + en_us: Bidirectional Telegram-MCP bridge for AI assistants + fr_fr: Pont Telegram-MCP bidirectionnel pour les assistants IA + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/TelegramMCP/thumbnail.png + title: + en_us: Telegram MCP + chips: + - key: mcp + name: + en_us: MCP + tips: + before_install: + en_us: | + **Getting Started** + + Telegram MCP needs a Telegram bot token. You set it in the app's Web UI after install. + + **Prerequisites:** + 1. In Telegram, open [@BotFather](https://t.me/BotFather), send `/newbot`, follow the prompts, and copy the **bot token** it gives you + 2. (Optional) An MCP-compatible LLM server to forward incoming messages to — only needed if you want the bot to auto-reply + + **Setup (2 steps):** + 1. Open this app's Web UI (you'll sign in with your Yundera account — no extra password) + 2. Paste your **bot token**, pick **polling** or **webhook** mode, and click **Save**. In webhook mode the bot registers `https://telegrammcp-${APP_DOMAIN}/webhook` with Telegram automatically. + + **MCP Integration (for AI/LLMs):** + + **Option 1: Public URL (with hash auth)** + ```json + { + "mcpServers": { + "telegram": { + "type": "http", + "url": "https://telegrammcp-${APP_DOMAIN}/mcp?hash=${AUTH_HASH}" + } + } + } + ``` + + **Option 2: Local Docker connection (no auth required)** + + For AI assistants running on the same server, connect directly via the Docker network: + ```json + { + "mcpServers": { + "telegram": { + "type": "http", + "url": "http://telegrammcp-backend:9634/mcp" + } + } + } + ``` + *Note: Requires the AI assistant to be on the `pcs` Docker network.* + + **Option 3: Via Beacon (aggregated)** + + If you have **Beacon** installed, Telegram MCP auto-registers there via its built-in UDP discovery and its tools show up under the `telegram__*` namespace — no extra config. + + **Available MCP Tools:** + | Tool | Description | + |------|-------------| + | `send_message` | Send a text message to the configured Telegram chat | + | `send_photo` | Send a photo (by URL or file) to the chat | + | `echo` | Echo back input — handy for connectivity tests | + | `mcp_info` | Report the server's MCP connection info | + + **Security:** + - The bot token is stored only in `/DATA/AppData/telegrammcp/config.json` on this server and is never logged or returned via the API + - The Web UI is protected by your Yundera single sign-on — no extra login needed + - `/mcp` and `/webhook` are intentionally reachable without the login flow (MCP clients and Telegram's servers can't perform it); keep the app domain private and rely on the URL hash for the MCP endpoint diff --git a/Apps/TelegramMCP/icon.png b/Apps/TelegramMCP/icon.png new file mode 100644 index 0000000..7432ef1 Binary files /dev/null and b/Apps/TelegramMCP/icon.png differ diff --git a/Apps/TelegramMCP/rationale.md b/Apps/TelegramMCP/rationale.md new file mode 100644 index 0000000..7b01263 --- /dev/null +++ b/Apps/TelegramMCP/rationale.md @@ -0,0 +1,44 @@ +# TelegramMCP — Rationale + +## `architectures: [amd64]` only + +The upstream image (`ghcr.io/worph/telegram-mcp`) is published as a +single-arch **linux/amd64** image — no `arm64` manifest exists for any +tag (verified with `docker buildx imagetools inspect`; the second +platform reported is the `unknown/unknown` SBOM/provenance attestation, +not a runnable arch). The AppStoreLab copy of this app lists both +`amd64` and `arm64`, but that is inaccurate — an arm64 PCS cannot pull +it. This listing declares only `amd64` so CasaOS hides it on arm +hardware instead of failing at install. To add arm64 support, Worph +needs to publish a multi-arch build (or it must be rebuilt under +`ghcr.io/yundera/`). + +## No `beaconify` sidecar + +Unlike DocmostMCP (whose upstream image has no discovery support and +therefore needs a `worph/beaconify` proxy), this image ships a built-in +UDP discovery responder (`mcp-announce.cjs`, `DISCOVERY_PORT=9099`). +The main container answers Beacon's discovery broadcast directly, so the +service simply exposes `9099` — same approach as N8NMCP. No sidecar. + +## `ALLOWED_PATHS: "mcp,webhook"` on the hash-lock + +`nginx-hash-lock` lists ALLOWED_PATHS as the paths that bypass the +login/cookie flow entirely. Two paths must be reachable without it: + +- `/mcp` — MCP HTTP clients can't perform the hash-lock session dance; a + 302-to-login would break the transport. The endpoint is gated instead + by the URL hash documented in `tips.before_install`. +- `/webhook` — Telegram's servers POST update callbacks here with no + credentials and no way to add the hash, so the path must be open. + +The web UI at `/` stays behind both the hash and the `ADMIN` password +(AUTH_MODE `both`), because that is where the bot token is entered. + +## `user: $PUID:$PGID` + +The container only writes `config.json` under the bind-mounted +`/DATA/AppData/telegrammcp/` (`CONFIG_PATH=/app/data/config.json` is +baked into the image). It touches no user directories and needs no root, +so it runs as the unprivileged PCS user. All data stays under +`/DATA/AppData/telegrammcp/`. diff --git a/Apps/TelegramMCP/screenshot-1.png b/Apps/TelegramMCP/screenshot-1.png new file mode 100644 index 0000000..a9df8ff Binary files /dev/null and b/Apps/TelegramMCP/screenshot-1.png differ diff --git a/Apps/TelegramMCP/screenshot-2.png b/Apps/TelegramMCP/screenshot-2.png new file mode 100644 index 0000000..c3aa455 Binary files /dev/null and b/Apps/TelegramMCP/screenshot-2.png differ diff --git a/Apps/TelegramMCP/screenshot-3.png b/Apps/TelegramMCP/screenshot-3.png new file mode 100644 index 0000000..a9df8ff Binary files /dev/null and b/Apps/TelegramMCP/screenshot-3.png differ diff --git a/Apps/TelegramMCP/thumbnail.png b/Apps/TelegramMCP/thumbnail.png new file mode 100644 index 0000000..a9df8ff Binary files /dev/null and b/Apps/TelegramMCP/thumbnail.png differ diff --git a/Apps/Terminal/docker-compose.yml b/Apps/Terminal/docker-compose.yml index a01c224..2a14710 100644 --- a/Apps/Terminal/docker-compose.yml +++ b/Apps/Terminal/docker-compose.yml @@ -2,8 +2,13 @@ name: terminal services: terminal: - image: ghcr.io/yundera/nginx-hash-lock:1.0.5 + image: ghcr.io/yundera/appshield:2.0.3 container_name: terminal + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: terminal restart: unless-stopped expose: - "80" @@ -22,8 +27,9 @@ services: BACKEND_HOST: "ttyd" BACKEND_PORT: "7681" LISTEN_PORT: "80" - USER: "ADMIN" - PASSWORD: $APP_DEFAULT_PASSWORD + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" depends_on: - ttyd networks: diff --git a/Apps/Tribler/docker-compose.yml b/Apps/Tribler/docker-compose.yml new file mode 100644 index 0000000..c7b2ef5 --- /dev/null +++ b/Apps/Tribler/docker-compose.yml @@ -0,0 +1,155 @@ +name: tribler + +services: + # OIDC SSO front-door (Yundera Authelia). Only this container is publicly reachable. + tribler: + image: ghcr.io/yundera/appshield:2.0.3 + container_name: tribler + # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from + # os.hostname(), and the auth-registrar independently attests this app's name via the + # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the + # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. + hostname: tribler + restart: unless-stopped + user: "root" + expose: + - 80 + labels: + caddy_0: tribler-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 80}}" + caddy_1: tribler-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 80}}" + caddy_2: tribler-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 80}}" + environment: + # Presence of OIDC_REGISTRAR_URL enables OIDC mode (self-registers via container_name). + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" + BACKEND_HOST: "tribler-backend" + BACKEND_PORT: "8086" + LISTEN_PORT: "80" + tmpfs: + - /var/cache/nginx:size=50M + depends_on: + - tribler-fwd + networks: + - pcs + logging: + driver: json-file + options: + max-size: "50m" + max-file: "3" + cpu_shares: 80 + deploy: + resources: + limits: + memory: 256M + + # Tribler v8.4.2 binds its REST/Web UI to 127.0.0.1 only and offers no override + # (verified: no env var works and config edits are reset on every boot). This socat + # shim shares the backend's network namespace and republishes the UI on 0.0.0.0:8086 + # so the sidecar can reach it as tribler-backend:8086 over the pcs network. + tribler-fwd: + image: alpine/socat:1.8.0.0 + container_name: tribler-fwd + restart: unless-stopped + network_mode: "service:tribler-backend" + command: ["TCP-LISTEN:8086,fork,reuseaddr", "TCP:127.0.0.1:8085"] + depends_on: + - tribler-backend + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + cpu_shares: 50 + + # Tribler core, headless. No Caddy labels — reachable only via the pcs network. + tribler-backend: + image: ghcr.io/tribler/tribler:v8.4.2 + container_name: tribler-backend + restart: unless-stopped + user: $PUID:$PGID + command: ["-s"] + expose: + - 8086 + environment: + # api/key empty => Tribler's own key auth is disabled; access is gated by the OIDC + # sidecar. HOME=/state makes Tribler's default save path resolve to /state/Downloads, + # which is bind-mounted to the user-visible /DATA/Downloads (the path is reset to this + # default on every boot, so we mount the user folder there instead of fighting it). + CORE_API_PORT: "8085" + CORE_API_KEY: "" + TSTATEDIR: /state + HOME: /state + TMPDIR: /state/.tmp + LANG: C.UTF-8 + TZ: $TZ + volumes: + - /DATA/AppData/$AppID/state:/state + - /DATA/Downloads:/state/Downloads + networks: + - pcs + logging: + driver: json-file + options: + max-size: "50m" + max-file: "3" + cpu_shares: 50 + deploy: + resources: + limits: + memory: 1024M + +networks: + pcs: + name: pcs + external: true + +x-casaos: + architectures: + - amd64 + main: tribler + webui_port: 80 + index: /ui/ + # Ensure the state dir (and TMPDIR) exist and are owned by the app user before first boot. + pre-install-cmd: | + mkdir -p /DATA/AppData/$AppID/state/.tmp && + docker run --rm -e PUID -e PGID \ + -v /DATA/AppData/$AppID/state:/state \ + busybox:1.36 sh -c 'chown -R "$PUID:$PGID" /state' + tips: + before_install: + en_us: | + Tribler is protected by your Yundera single sign-on (Authelia) — no extra login. + Downloads are saved to `/DATA/Downloads`. + By default downloads are anonymized through Tribler's onion network (1 hop); you can + change the number of hops or disable anonymity per-download in the UI. + author: Yundera Team + category: Downloader + developer: Tribler + description: + en_us: Tribler is a decentralized, privacy-focused BitTorrent client. It routes downloads through its own built-in onion network (Tor-like), so you can search for and download torrents anonymously without any central server, VPN, or tracker dependency. + fr_fr: Tribler est un client BitTorrent décentralisé et respectueux de la vie privée. Il achemine les téléchargements via son propre réseau onion intégré (similaire à Tor), ce qui vous permet de rechercher et de télécharger des torrents de manière anonyme, sans serveur central, VPN ni tracker. + es_es: Tribler es un cliente BitTorrent descentralizado y centrado en la privacidad. Enruta las descargas a través de su propia red onion integrada (similar a Tor), para que puedas buscar y descargar torrents de forma anónima sin servidor central, VPN ni rastreadores. + zh_cn: Tribler 是一个去中心化、注重隐私的 BitTorrent 客户端。它通过内置的洋葱网络(类似 Tor)路由下载,让您无需任何中心服务器、VPN 或 Tracker 即可匿名搜索和下载种子。 + ko_kr: Tribler는 분산형 프라이버시 중심 BitTorrent 클라이언트입니다. 자체 내장 어니언 네트워크(Tor 유사)를 통해 다운로드를 라우팅하여 중앙 서버, VPN, 트래커 없이도 익명으로 토렌트를 검색하고 다운로드할 수 있습니다. + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Tribler/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Tribler/screenshot-1.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Tribler/screenshot-2.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Tribler/screenshot-3.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Tribler/thumbnail.png + tagline: + en_us: Anonymous, decentralized BitTorrent client with a built-in onion network. + fr_fr: Client BitTorrent anonyme et décentralisé avec un réseau onion intégré. + es_es: Cliente BitTorrent anónimo y descentralizado con red onion integrada. + zh_cn: 匿名、去中心化的 BitTorrent 客户端,内置洋葱网络。 + ko_kr: 내장 어니언 네트워크를 갖춘 익명 분산형 BitTorrent 클라이언트. + title: + en_us: Tribler + store_app_id: tribler + is_uncontrolled: false diff --git a/Apps/Tribler/icon.png b/Apps/Tribler/icon.png new file mode 100644 index 0000000..9ba40d5 Binary files /dev/null and b/Apps/Tribler/icon.png differ diff --git a/Apps/Tribler/rationale.md b/Apps/Tribler/rationale.md new file mode 100644 index 0000000..edf7bff --- /dev/null +++ b/Apps/Tribler/rationale.md @@ -0,0 +1,36 @@ +# Tribler — Rationale + +## What deviation / exception is being requested + +1. **`architectures: [amd64]` only** — the app is not published for `arm64`. +2. **Tribler's own REST API key is disabled** (`CORE_API_KEY=""`), i.e. the *backend* application ships without its built-in auth gate. +3. **A `socat` shim container** shares the backend's network namespace to expose Tribler on the `pcs` network. + +## Why it is necessary + +1. **amd64 only:** Upstream publishes `ghcr.io/tribler/tribler` for `linux/amd64` exclusively — there is no arm64 manifest (verified against `v8.4.2`). Listing arm64 would advertise an image that cannot be pulled. + +2. **Disabled internal key:** Tribler's only built-in auth is a static `api/key` passed as a `?key=…` URL query parameter or `X-Api-Key` header. That model is incompatible with a clean SSO experience (the secret would have to live in the URL). Instead the app is fronted by the **`nginx-hash-lock` OIDC sidecar**, which gates *all* access through the PCS's built-in Authelia single sign-on — the recommended Yundera auth method. The Tribler backend is therefore **not the auth boundary** and runs with its key emptied so the UI works seamlessly behind SSO. + +3. **socat shim:** Tribler v8.4.2 hard-binds its REST/Web UI to `127.0.0.1` inside the container. This was verified empirically to be **non-overridable**: there is no working environment variable (`CORE_API_BIND_ADDRESS` is ignored), and any `api/http_host` written to `configuration.json` is reset to `127.0.0.1` on every boot. A separate container therefore cannot reach it. The `tribler-fwd` container runs `socat` with `network_mode: service:tribler-backend` (shared network namespace) and republishes `127.0.0.1:8085` on `0.0.0.0:8086`, making the UI reachable as `tribler-backend:8086` on the `pcs` network for the sidecar — without exposing it publicly. + + Relatedly, Tribler resets its default save path to `$HOME/Downloads` on every boot, so rather than fight it, `HOME=/state` and the user folder is bind-mounted at `/DATA/Downloads → /state/Downloads`. Downloads land in the user-visible `/DATA/Downloads` with correct `PUID:PGID` ownership (verified on a live PCS). + +## Security mitigations in place + +- **Authentication is enforced** for every request by the OIDC sidecar (Authelia SSO). Only the sidecar (`container_name: tribler`) carries Caddy labels and is publicly reachable; the backend has no `ports:` and no public labels — it is reachable only on the internal `pcs` network. Verified on a live PCS: the public URL `302`-redirects unauthenticated requests to the Authelia OIDC login. +- The `socat` shim listens only on the backend's internal namespace; it has no Caddy labels and is not published to the host. +- The backend runs as **`$PUID:$PGID`** (not root), so files written to the user-visible `/DATA/Downloads` carry correct ownership. +- Memory limits and `cpu_shares` set on every service. +- Per-service JSON log rotation. + +## Alternatives considered and rejected + +- **Expose Tribler directly with its `?key=` URL auth.** Rejected: weak, leaks the secret in the URL/history, and does not integrate with the PCS SSO that the store mandates. +- **Override the bind host via env var or seeded `configuration.json`.** Rejected: empirically proven non-functional in v8.4.2 (env ignored; config reset every boot). The `socat` shim is the only reliable approach. +- **Run the backend as root to use the image's default `/root/.Tribler`.** Rejected: downloads in `/DATA/Downloads` would be root-owned, violating the dual-permission model. `HOME`/`TSTATEDIR=/state` lets it run as `$PUID:$PGID` instead. + +## Data protection + +- All state (identity keys, databases, settings) lives under `/DATA/AppData/tribler/state`; downloaded files live under `/DATA/Downloads`. Both persist across uninstall/reinstall. +- The `pre-install-cmd` only creates the state directory and fixes ownership; it never writes or overwrites application data, so user preferences and the node identity are preserved on reinstall and version upgrades. diff --git a/Apps/Tribler/screenshot-1.png b/Apps/Tribler/screenshot-1.png new file mode 100644 index 0000000..863b3e4 Binary files /dev/null and b/Apps/Tribler/screenshot-1.png differ diff --git a/Apps/Tribler/screenshot-2.png b/Apps/Tribler/screenshot-2.png new file mode 100644 index 0000000..6e075ae Binary files /dev/null and b/Apps/Tribler/screenshot-2.png differ diff --git a/Apps/Tribler/screenshot-3.png b/Apps/Tribler/screenshot-3.png new file mode 100644 index 0000000..f20d729 Binary files /dev/null and b/Apps/Tribler/screenshot-3.png differ diff --git a/Apps/Tribler/thumbnail.png b/Apps/Tribler/thumbnail.png new file mode 100644 index 0000000..2f446e8 Binary files /dev/null and b/Apps/Tribler/thumbnail.png differ diff --git a/Apps/Vaultwarden/docker-compose.yml b/Apps/Vaultwarden/docker-compose.yml index 29b0602..b4857c7 100644 --- a/Apps/Vaultwarden/docker-compose.yml +++ b/Apps/Vaultwarden/docker-compose.yml @@ -3,6 +3,7 @@ name: vaultwarden services: vaultwarden: image: vaultwarden/server:1.35.3 + container_name: vaultwarden user: $PUID:$PGID deploy: resources: @@ -34,7 +35,7 @@ services: ROCKET_ADDRESS: 0.0.0.0 SIGNUPS_ALLOWED: "true" ADMIN_TOKEN: $APP_DEFAULT_PASSWORD - APP_DOMAIN: https://vaultwarden-$APP_DOMAIN + DOMAIN: https://vaultwarden-$APP_DOMAIN APP_NET: pcs SMTP_HOST: smtp SMTP_PORT: 587 @@ -274,14 +275,14 @@ x-casaos: before_install: en_us: | `If you lose your master password, **NOBODY CAN RECOVER YOUR DATA**! Your vault will be permanently inaccessible. Store it in a safe place.` - + **Default Admin Access** | Setting | Value | |---------|-------| | Admin Panel | [🔗 Admin Panel](https://vaultwarden-$APP_DOMAIN/admin) | | Admin Token | `$APP_DEFAULT_PASSWORD` | - + Note: The admin token will not give you access to user vaults, only the user master password will. **First Steps:** @@ -297,14 +298,14 @@ x-casaos: Download the **Bitwarden app** or use the **Bitwarden browser extension** - works perfectly with Vaultwarden! ko_kr: | `마스터 비밀번호를 분실하면 **아무도 데이터를 복구할 수 없습니다**! 보관함에 영구적으로 접근할 수 없게 됩니다. 안전한 곳에 보관하세요.` - + **기본 관리자 접근** | 설정 | 값 | |------|-----| | 관리자 패널 | [🔗 관리자 패널](https://vaultwarden-$APP_DOMAIN/admin) | | 관리자 토큰 | `$APP_DEFAULT_PASSWORD` | - + 참고: 관리자 토큰으로는 사용자 보관함에 접근할 수 없으며, 오직 사용자 마스터 비밀번호만으로 가능합니다. **📧 이메일 통합** @@ -356,14 +357,14 @@ x-casaos: **重要提示**: Yundera的SMTP服务自动连接 - 密码恢复立即可用! fr_fr: | `Si vous perdez votre mot de passe principal, **PERSONNE NE PEUT RÉCUPÉRER VOS DONNÉES** ! Votre coffre-fort sera définitivement inaccessible. Conservez-le dans un endroit sûr.` - + **Accès Admin par Défaut** | Paramètre | Valeur | |-----------|--------| | Panneau Admin | [🔗 Panneau Admin](https://vaultwarden-$APP_DOMAIN/admin) | | Token Admin | `$APP_DEFAULT_PASSWORD` | - + Note : Le token admin ne vous donnera pas accès aux coffres-forts des utilisateurs, seul le mot de passe principal de l'utilisateur le peut. **📧 Intégration Email** @@ -387,14 +388,14 @@ x-casaos: **Important** : Le service SMTP de Yundera est automatiquement connecté - la récupération de mots de passe fonctionne immédiatement ! de_de: | `Wenn Sie Ihr Master-Passwort verlieren, **KANN NIEMAND IHRE DATEN WIEDERHERSTELLEN**! Ihr Tresor wird dauerhaft unzugänglich. Bewahren Sie es an einem sicheren Ort auf.` - + **Standard-Admin-Zugang** | Einstellung | Wert | |-------------|------| | Admin-Panel | [🔗 Admin-Panel](https://vaultwarden-$APP_DOMAIN/admin) | | Admin-Token | `$APP_DEFAULT_PASSWORD` | - + Hinweis: Das Admin-Token gewährt Ihnen keinen Zugang zu Benutzer-Tresoren, nur das Benutzer-Master-Passwort kann das. **📧 E-Mail-Integration** @@ -418,14 +419,14 @@ x-casaos: **Wichtig**: Der SMTP-Dienst von Yundera ist automatisch verbunden - Passwort-Wiederherstellung funktioniert sofort! es_es: | `Si pierdes tu contraseña maestra, **NADIE PUEDE RECUPERAR TUS DATOS**! Tu bóveda será permanentemente inaccesible. Guárdala en un lugar seguro.` - + **Acceso de Admin por Defecto** | Configuración | Valor | |---------------|-------| | Panel de Admin | [🔗 Panel de Admin](https://vaultwarden-$APP_DOMAIN/admin) | | Token de Admin | `$APP_DEFAULT_PASSWORD` | - + Nota: El token de admin no te dará acceso a las bóvedas de usuarios, solo la contraseña maestra del usuario puede hacerlo. **📧 Integración de Email** diff --git a/Apps/Vaultwarden/thumbnail.png b/Apps/Vaultwarden/thumbnail.png new file mode 100644 index 0000000..63e6cb0 Binary files /dev/null and b/Apps/Vaultwarden/thumbnail.png differ diff --git a/Apps/WireGuardEasy/docker-compose.yml b/Apps/WireGuardEasy/docker-compose.yml new file mode 100644 index 0000000..61adb53 --- /dev/null +++ b/Apps/WireGuardEasy/docker-compose.yml @@ -0,0 +1,189 @@ +name: wgeasy + +services: + wgeasy: + image: ghcr.io/wg-easy/wg-easy:15.3.0 + container_name: wgeasy + restart: unless-stopped + user: "0:0" + labels: + caddy_0: wgeasy-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 80}}" + caddy_1: wgeasy-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 80}}" + caddy_2: wgeasy-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 80}}" + networks: + - pcs + environment: + PORT: '80' + INSECURE: 'true' + DISABLE_IPV6: 'true' + INIT_ENABLED: 'true' + INIT_USERNAME: admin + INIT_PASSWORD: $APP_DEFAULT_PASSWORD + INIT_HOST: $PCS_PUBLIC_IP + INIT_PORT: '51820' + INIT_DNS: '9.9.9.9' + expose: + - 80 + ports: + - target: 51820 + published: 51820 + protocol: udp + volumes: + - /DATA/AppData/$AppID/wireguard:/etc/wireguard + cap_add: + - NET_ADMIN + - SYS_MODULE + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv4.conf.all.src_valid_mark=1 + cpu_shares: 50 + +networks: + pcs: + name: pcs + external: true + +x-casaos: + architectures: + - amd64 + - arm + - arm64 + main: wgeasy + store_app_id: wgeasy + webui_port: 80 + index: / + developer: WeejeWel + author: Yundera Team + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/WireGuardEasy/icon.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/WireGuardEasy/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/WireGuardEasy/screenshot-1.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/WireGuardEasy/screenshot-2.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/WireGuardEasy/screenshot-3.png + category: Network + title: + en_us: WireGuard Easy + tagline: + en_us: Simple web UI to manage WireGuard VPN (v15) + ko_kr: WireGuard VPN을 관리하는 간단한 웹 UI (v15) + zh_cn: 管理 WireGuard VPN 的简单 Web UI (v15) + fr_fr: Interface web simple pour gerer WireGuard VPN (v15) + es_es: Interfaz web simple para gestionar WireGuard VPN (v15) + description: + en_us: | + **The easiest way to install and manage WireGuard on any Linux host** + + WireGuard Easy provides a simple web interface to create and manage WireGuard VPN connections. Add clients with one click, download configurations, and monitor connected devices. + + **What you can do:** + - Create and manage VPN clients with one click + - Download client configs or scan QR codes + - Monitor connected clients and bandwidth + - Enable/disable clients instantly + + **Perfect for:** Anyone who wants a personal VPN server without complex command-line setup. + ko_kr: | + **Linux 호스트에서 WireGuard를 설치하고 관리하는 가장 쉬운 방법** + + WireGuard Easy는 WireGuard VPN 연결을 생성하고 관리하는 간단한 웹 인터페이스를 제공합니다. 한 번의 클릭으로 클라이언트를 추가하고, 설정을 다운로드하고, 연결된 장치를 모니터링할 수 있습니다. + + **할 수 있는 것:** + - 한 번의 클릭으로 VPN 클라이언트 생성 및 관리 + - 클라이언트 설정 다운로드 또는 QR 코드 스캔 + - 연결된 클라이언트 및 대역폭 모니터링 + - 클라이언트 즉시 활성화/비활성화 + + **이런 분께 완벽해요:** 복잡한 명령줄 설정 없이 개인 VPN 서버를 원하는 모든 분. + zh_cn: | + **在任何 Linux 主机上安装和管理 WireGuard 的最简单方法** + + WireGuard Easy 提供简单的 Web 界面来创建和管理 WireGuard VPN 连接。一键添加客户端、下载配置并监控已连接的设备。 + + **您可以做的事:** + - 一键创建和管理 VPN 客户端 + - 下载客户端配置或扫描二维码 + - 监控已连接的客户端和带宽 + - 即时启用/禁用客户端 + + **完美适合:** 任何想要个人 VPN 服务器而无需复杂命令行设置的人。 + fr_fr: | + **Le moyen le plus simple d'installer et de gerer WireGuard sur n'importe quel hote Linux** + + WireGuard Easy fournit une interface web simple pour creer et gerer les connexions VPN WireGuard. Ajoutez des clients en un clic, telechargez les configurations et surveillez les appareils connectes. + + **Ce que vous pouvez faire:** + - Creer et gerer des clients VPN en un clic + - Telecharger les configs ou scanner les QR codes + - Surveiller les clients connectes et la bande passante + - Activer/desactiver les clients instantanement + + **Parfait pour:** Tous ceux qui veulent un serveur VPN personnel sans configuration complexe en ligne de commande. + es_es: | + **La forma mas facil de instalar y gestionar WireGuard en cualquier host Linux** + + WireGuard Easy proporciona una interfaz web simple para crear y gestionar conexiones VPN WireGuard. Agregue clientes con un clic, descargue configuraciones y monitoree dispositivos conectados. + + **Lo que puedes hacer:** + - Crear y gestionar clientes VPN con un clic + - Descargar configs de clientes o escanear codigos QR + - Monitorear clientes conectados y ancho de banda + - Activar/desactivar clientes al instante + + **Perfecto para:** Cualquiera que quiera un servidor VPN personal sin configuracion compleja por linea de comandos. + tips: + before_install: + en_us: | + # Default Account + + | Username | Password | + | -------- | ----------------------- | + | `admin` | `$APP_DEFAULT_PASSWORD` | + + This is **WireGuard Easy v15**. The admin account is auto-configured. VPN settings can be changed from the web UI after login. + + The VPN listens on UDP port 51820. Make sure this port is open on your firewall/router. + ko_kr: | + # 기본 계정 + + | 사용자명 | 비밀번호 | + | -------- | ----------------------- | + | `admin` | `$APP_DEFAULT_PASSWORD` | + + **WireGuard Easy v15** 버전입니다. 관리자 계정이 자동 설정됩니다. VPN 설정은 로그인 후 웹 UI에서 변경할 수 있습니다. + + VPN은 UDP 포트 51820에서 수신합니다. 방화벽/라우터에서 이 포트가 열려있는지 확인하세요. + zh_cn: | + # 默认账户 + + | 用户名 | 密码 | + | -------- | ----------------------- | + | `admin` | `$APP_DEFAULT_PASSWORD` | + + 这是 **WireGuard Easy v15** 版本。管理员账户已自动配置。登录后可在 Web UI 中更改 VPN 设置。 + + VPN 监听 UDP 端口 51820。请确保防火墙/路由器上此端口已开放。 + fr_fr: | + # Compte par defaut + + | Nom d'utilisateur | Mot de passe | + | ----------------- | ----------------------- | + | `admin` | `$APP_DEFAULT_PASSWORD` | + + Ceci est **WireGuard Easy v15**. Le compte administrateur est configure automatiquement. Les parametres VPN peuvent etre modifies depuis l'interface web apres connexion. + + Le VPN ecoute sur le port UDP 51820. Assurez-vous que ce port est ouvert sur votre pare-feu/routeur. + es_es: | + # Cuenta predeterminada + + | Usuario | Contrasena | + | -------- | ----------------------- | + | `admin` | `$APP_DEFAULT_PASSWORD` | + + Esta es la version **WireGuard Easy v15**. La cuenta de administrador se configura automaticamente. La configuracion VPN se puede cambiar desde la interfaz web despues de iniciar sesion. + + La VPN escucha en el puerto UDP 51820. Asegurese de que este puerto este abierto en su firewall/router. diff --git a/Apps/WireGuardEasy/icon.png b/Apps/WireGuardEasy/icon.png new file mode 100644 index 0000000..e1645de Binary files /dev/null and b/Apps/WireGuardEasy/icon.png differ diff --git a/Apps/WireGuardEasy/screenshot-1.png b/Apps/WireGuardEasy/screenshot-1.png new file mode 100644 index 0000000..837a7bd Binary files /dev/null and b/Apps/WireGuardEasy/screenshot-1.png differ diff --git a/Apps/WireGuardEasy/screenshot-2.png b/Apps/WireGuardEasy/screenshot-2.png new file mode 100644 index 0000000..8de36b4 Binary files /dev/null and b/Apps/WireGuardEasy/screenshot-2.png differ diff --git a/Apps/WireGuardEasy/screenshot-3.png b/Apps/WireGuardEasy/screenshot-3.png new file mode 100644 index 0000000..4c60af8 Binary files /dev/null and b/Apps/WireGuardEasy/screenshot-3.png differ diff --git a/Apps/WireGuardEasyHost/docker-compose.yml b/Apps/WireGuardEasyHost/docker-compose.yml new file mode 100644 index 0000000..af9ec3c --- /dev/null +++ b/Apps/WireGuardEasyHost/docker-compose.yml @@ -0,0 +1,189 @@ +name: wgeasyhost + +services: + # WireGuard server, running in the HOST network namespace. + # Because wg0 lives on the host, the VPN server address (10.9.0.1) IS the host, + # so VPN clients can reach host-bound services (Samba :445, Guacamole, ...) at 10.9.0.1. + # A host-networked container cannot be a Caddy upstream, so the web UI is exposed + # through the wgeasyhost-proxy sidecar below instead of Caddy labels here. + wgeasyhost: + image: ghcr.io/wg-easy/wg-easy:15.3.0 + container_name: wgeasyhost + restart: unless-stopped + user: "0:0" + network_mode: host + environment: + PORT: '51821' # web UI port on the host (proxied by the sidecar) + INSECURE: 'true' # plain HTTP; TLS is terminated by the Yundera gateway + DISABLE_IPV6: 'true' + INIT_ENABLED: 'true' + INIT_USERNAME: admin + INIT_PASSWORD: $PCS_DEFAULT_PASSWORD + INIT_HOST: $PCS_PUBLIC_IP # endpoint written into client configs + INIT_PORT: '51820' # WireGuard UDP port on the host + INIT_DNS: '9.9.9.9' + # VPN subnet. Uses 10.9.0.0/24 to avoid colliding with the bridge-mode WireGuardEasy + # app (10.8.0.0/24). NOTE: wg-easy only applies IPV4_CIDR when BOTH v4+v6 are set, + # so INIT_IPV6_CIDR is required even though IPv6 is disabled. + INIT_IPV4_CIDR: '10.9.0.0/24' + INIT_IPV6_CIDR: 'fdcc:ad94:bacf:61a4::cafe:0/112' + # Split-tunnel default for new clients (NOT a 0.0.0.0/0 catch-all / kill switch): + # only the VPN subnet is routed, so clients reach host services at 10.9.0.1 while + # their normal internet stays direct. Comma-separated; seeds on first init only. + INIT_ALLOWED_IPS: '10.9.0.0/24' + volumes: + - /DATA/AppData/wgeasyhost/wireguard/:/etc/wireguard + cap_add: + - NET_ADMIN + - SYS_MODULE + # NOTE: host network mode forbids setting net.ipv4.* sysctls on the container + # (they would mutate the host). They are set on the HOST by pre-install-cmd below. + cpu_shares: 50 + mem_limit: 256m + + # Bridged reverse-proxy on the pcs network. Carries the gateway labels and forwards + # to the host-mode web UI via the docker host-gateway (host.docker.internal:51821). + # nginx.conf is fetched onto the host by pre-install-cmd and bind-mounted read-only. + wgeasyhost-proxy: + image: nginx:alpine + container_name: wgeasyhost-proxy + restart: unless-stopped + # Must be root: the CasaOS installer injects `user: $PUID:$PGID` into any service + # without an explicit user, and nginx:alpine's master can't run as non-root + # (fails to create /var/cache/nginx). Declaring it here prevents that injection. + user: "0:0" + depends_on: + - wgeasyhost + labels: + caddy_0: wgeasyhost-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 80}}" + caddy_1: wgeasyhost-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 80}}" + caddy_2: wgeasyhost-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 80}}" + networks: + - pcs + expose: + - 80 + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - /DATA/AppData/$AppID/nginx.conf:/etc/nginx/nginx.conf:ro + cpu_shares: 10 + mem_limit: 64m + +networks: + pcs: + name: pcs + external: true + +x-casaos: + architectures: + - amd64 + - arm + - arm64 + # Backend+proxy pattern (cf. lystik): main = the backend service name = subdomain prefix; + # the -proxy sidecar carries the caddy labels advertising
-user.nsl.sh and forwards + # to the host-mode wg-easy. The store installer injects `hostname` (user-specific) at install; + # scheme/port_map are static. Health probe follows redirects: / -> /login -> 200. (webui_port + # is deprecated.) + main: wgeasyhost + store_app_id: wgeasyhost + index: / + pre-install-cmd: | + mkdir -p /DATA/AppData/wgeasyhost/wireguard && + wget -q https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/WireGuardEasyHost/nginx.conf -O /DATA/AppData/wgeasyhost/nginx.conf && + chown -R $PUID:$PGID /DATA/AppData/wgeasyhost && + sysctl -w net.ipv4.ip_forward=1 && + sysctl -w net.ipv4.conf.all.src_valid_mark=1 + # New clients inherit the server-wide default PersistentKeepalive from wg-easy's + # userConfig row (factory-seeded to 0 = off). v15 dropped the keepalive env/INIT var, + # so the only way to seed a default is to write it after the container has built its + # SQLite DB. This runs AFTER container startup: it polls until the user_configs_table + # row exists, then sets PersistentKeepalive to 25s (standard NAT-traversal value) — but + # only while still at the factory default (0), so a later manual change in the UI is + # never clobbered. Uses the image's own bundled @libsql driver (no sqlite3 CLI present). + post-install-cmd: | + for i in $(seq 1 60); do + docker exec -w /app/server wgeasyhost node --input-type=module -e 'import {createClient} from "@libsql/client"; const c=createClient({url:"file:/etc/wireguard/wg-easy.db"}); const r=await c.execute("SELECT count(*) AS n FROM user_configs_table"); if(Number(r.rows[0].n)<1) process.exit(3); await c.execute("UPDATE user_configs_table SET default_persistent_keepalive=25 WHERE default_persistent_keepalive=0"); console.log("wgeasyhost: default PersistentKeepalive set to 25s");' && break + sleep 2 + done + developer: WeejeWel + author: Yundera Team + icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/WireGuardEasyHost/icon.png + thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/WireGuardEasyHost/icon.png + screenshot_link: + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/WireGuardEasyHost/screenshot-1.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/WireGuardEasyHost/screenshot-2.png + - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/WireGuardEasyHost/screenshot-3.png + category: Network + title: + en_us: WireGuard Easy - Host + tagline: + en_us: WireGuard VPN with host networking - reach Samba, Guacamole & other host services (v15) + fr_fr: WireGuard VPN en mode hote - accedez a Samba, Guacamole et autres services de l'hote (v15) + description: + en_us: | + **WireGuard VPN that can reach the rest of your PCS** - built on WireGuard Easy v15, but running in **host networking** mode. + + Unlike the standard WireGuard Easy app (which is isolated on a Docker bridge), this version puts the VPN interface directly on the host. That means the VPN server address `10.9.0.1` **is your PCS**, so connected clients can reach host-bound services - Samba file shares, Guacamole, and anything else listening on the host - at `10.9.0.1:`. This is the closest WireGuard equivalent to a Tailscale-style "reach my whole server" setup. + + **What you can do:** + - Reach host services (Samba :445, Guacamole, ...) over the VPN at `10.9.0.1` + - Create and manage VPN clients with one click + - Download client configs or scan QR codes + - Monitor connected clients and bandwidth + + **Requires:** inbound **UDP 51820** reachable on your PCS public IP. + fr_fr: | + **WireGuard VPN capable d'atteindre le reste de votre PCS** - base sur WireGuard Easy v15, mais en mode **reseau hote**. + + Contrairement a l'app WireGuard Easy standard (isolee sur un bridge Docker), cette version place l'interface VPN directement sur l'hote. L'adresse du serveur VPN `10.9.0.1` **est votre PCS**, donc les clients connectes peuvent atteindre les services de l'hote - partages Samba, Guacamole, etc. - a `10.9.0.1:`. C'est l'equivalent WireGuard le plus proche d'une configuration Tailscale. + + **Necessite :** le port **UDP 51820** entrant accessible sur l'IP publique de votre PCS. + tips: + before_install: + en_us: | + # Default Account + + | Username | Password | + | -------- | ----------------------- | + | `admin` | `$PCS_DEFAULT_PASSWORD` | + + This is **WireGuard Easy v15 in host-networking mode**. The admin account is auto-configured; VPN settings can be changed from the web UI after login. + + **Reaching your PCS over the VPN — use `10.9.0.1`:** once a client is connected, the whole server is reachable at the VPN address **`10.9.0.1`** (that address *is* your PCS). Point any tool at that IP: + + - **File shares (Samba), port 445:** + - Windows: open Explorer and type `\\10.9.0.1` + - macOS: Finder -> *Go* -> *Connect to Server* -> `smb://10.9.0.1` + - Linux: in your file manager, `smb://10.9.0.1` + - **Other host apps (e.g. Guacamole):** `http://10.9.0.1:` + - **Ping test:** `ping 10.9.0.1` should reply once connected. + + Notes: the default split-tunnel only routes `10.9.0.0/24`, so your normal internet stays direct (no kill-switch). The target service must listen on all interfaces (`0.0.0.0`), not just one — apps installed from this store already do. + + **Firewall:** the VPN listens on **UDP 51820** - it must be open on your firewall/router. The management UI also binds host port `51821` over plain HTTP; access it via the HTTPS gateway link, and firewall `51821` if you don't want it exposed directly. + fr_fr: | + # Compte par defaut + + | Nom d'utilisateur | Mot de passe | + | ----------------- | ----------------------- | + | `admin` | `$PCS_DEFAULT_PASSWORD` | + + Ceci est **WireGuard Easy v15 en mode reseau hote**. Le compte admin est configure automatiquement ; les parametres VPN sont modifiables depuis l'interface web apres connexion. + + **Acceder a votre PCS via le VPN — utilisez `10.9.0.1` :** une fois un client connecte, tout le serveur est accessible a l'adresse VPN **`10.9.0.1`** (cette adresse *est* votre PCS). Pointez n'importe quel outil vers cette IP : + + - **Partages de fichiers (Samba), port 445 :** + - Windows : ouvrez l'Explorateur et tapez `\\10.9.0.1` + - macOS : Finder -> *Aller* -> *Se connecter au serveur* -> `smb://10.9.0.1` + - Linux : dans votre gestionnaire de fichiers, `smb://10.9.0.1` + - **Autres applis de l'hote (ex. Guacamole) :** `http://10.9.0.1:` + - **Test ping :** `ping 10.9.0.1` doit repondre une fois connecte. + + Notes : le split-tunnel par defaut ne route que `10.9.0.0/24`, votre trafic internet normal reste direct (pas de kill-switch). Le service cible doit ecouter sur toutes les interfaces (`0.0.0.0`). + + **Pare-feu :** le VPN ecoute sur **UDP 51820** - ce port doit etre ouvert. L'interface de gestion ecoute aussi sur le port hote `51821` en HTTP ; utilisez le lien HTTPS de la passerelle. diff --git a/Apps/WireGuardEasyHost/icon.png b/Apps/WireGuardEasyHost/icon.png new file mode 100644 index 0000000..2312ab0 Binary files /dev/null and b/Apps/WireGuardEasyHost/icon.png differ diff --git a/Apps/WireGuardEasyHost/nginx.conf b/Apps/WireGuardEasyHost/nginx.conf new file mode 100644 index 0000000..cbe22d6 --- /dev/null +++ b/Apps/WireGuardEasyHost/nginx.conf @@ -0,0 +1,43 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + keepalive_timeout 65; + + server { + listen 80; + + # wgeasyhost runs with network_mode: host, so its web UI is on the host's + # port 51821 - not on the pcs bridge. Reach it via the docker host-gateway + # (mapped to host.docker.internal by extra_hosts in docker-compose.yml). + location / { + proxy_pass http://host.docker.internal:51821; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support (wg-easy live updates) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + } +} diff --git a/Apps/WireGuardEasyHost/screenshot-1.png b/Apps/WireGuardEasyHost/screenshot-1.png new file mode 100644 index 0000000..837a7bd Binary files /dev/null and b/Apps/WireGuardEasyHost/screenshot-1.png differ diff --git a/Apps/WireGuardEasyHost/screenshot-2.png b/Apps/WireGuardEasyHost/screenshot-2.png new file mode 100644 index 0000000..8de36b4 Binary files /dev/null and b/Apps/WireGuardEasyHost/screenshot-2.png differ diff --git a/Apps/WireGuardEasyHost/screenshot-3.png b/Apps/WireGuardEasyHost/screenshot-3.png new file mode 100644 index 0000000..4c60af8 Binary files /dev/null and b/Apps/WireGuardEasyHost/screenshot-3.png differ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c740ad2..0f6378c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ Before submitting your PR, ensure your app meets these requirements: ### Security Checklist - [ ] An authentication method is enabled and documented - this is **mandatory**. Exceptions must be explained in rationale.md (e.g., public websites). - - **Recommended**: OIDC via the `nginx-hash-lock` sidecar, which auto-registers with the PCS's `auth-registrar` and protects the app with the built-in Authelia SSO. See [OIDC Authentication](#oidc-authentication-recommended) for the minimal setup and [HashLockDemo](https://github.com/Yundera/AppStoreLab/tree/main/Apps/HashLockDemo) for a reference deployment. + - **Recommended**: OIDC via the **AppShield** sidecar (`ghcr.io/yundera/appshield`, formerly `nginx-hash-lock`), which auto-registers with the PCS's `auth-registrar` and protects the app with the built-in Authelia SSO. See [OIDC Authentication](#oidc-authentication-recommended) for the minimal setup, and copy a recently-shipped app (e.g. `Apps/ConvertX`, `Apps/Spliit`, `Apps/BrowserMCP`) as a reference deployment. - Acceptable alternatives: Basic Auth, the app's own built-in auth (e.g. Jellyfin, Immich onboarding), or any other login gate that is enabled by default. - Example of valid exception: - A public website that does not require authentication @@ -660,20 +660,23 @@ x-casaos: #### OIDC Authentication (Recommended) -The recommended way to satisfy the authentication requirement is to front your app with the `nginx-hash-lock` sidecar, which plugs into the PCS's built-in Authelia SSO. The sidecar self-registers as an OIDC client with the PCS's `auth-registrar` on first login — there are **no client IDs, no secrets, and no issuer URL to configure**. +The recommended way to satisfy the authentication requirement is to front your app with the **AppShield** sidecar (`ghcr.io/yundera/appshield`, formerly `nginx-hash-lock`), which plugs into the PCS's built-in Authelia SSO. The sidecar self-registers as an OIDC client with the PCS's `auth-registrar` on first login — there are **no client IDs, no secrets, and no issuer URL to configure**. -Reference deployment: [AppStoreLab/Apps/HashLockDemo](https://github.com/Yundera/AppStoreLab/tree/main/Apps/HashLockDemo). Fork it, swap `whoami` for your backend, ship. +Reference deployments: copy a recently-shipped SSO app such as `Apps/ConvertX`, `Apps/Spliit`, or `Apps/BrowserMCP` (an MCP server). The live store is the source of truth — if this guide and a shipped app disagree, the app wins. -**Pattern:** put the hash-lock container in front of your backend, point Caddy at hash-lock instead of the backend, and keep the backend reachable only on the internal `pcs` network. +**Pattern:** put the AppShield container in front of your backend, point Caddy at AppShield instead of the backend, and keep the backend reachable only on the internal `pcs` network. ```yaml +name: myapp services: - myapp-auth: - image: ghcr.io/yundera/nginx-hash-lock:1.0.7 - container_name: myapp-auth + myapp: # AppShield sidecar (public-facing) + image: ghcr.io/yundera/appshield:2.0.3 + container_name: myapp # must equal top-level name: (load-bearing — see checklist) + hostname: myapp # must equal container_name — OIDC identity (load-bearing — see checklist) restart: unless-stopped + user: "root" expose: - - "80" + - 80 labels: caddy_0: myapp-${APP_DOMAIN} caddy_0.import: gateway_tls @@ -684,21 +687,30 @@ services: caddy_2: myapp-\${APP_PUBLIC_IP_DASH}.sslip.io caddy_2.reverse_proxy: "{{upstreams 80}}" environment: - # Presence of OIDC_REGISTRAR_URL enables OIDC mode. - OIDC_REGISTRAR_URL: "http://auth-registrar:9092" - BACKEND_HOST: "myapp-backend" # internal DNS name of the protected container - BACKEND_PORT: "80" # port the backend listens on - LISTEN_PORT: "80" # port hash-lock listens on (matches `expose` + Caddy) + AUTH_HASH: $AUTH_HASH # token; pair with x-casaos.index: /?hash=$AUTH_HASH + BACKEND_HOST: "myapp-backend" # internal DNS name of the protected container + BACKEND_PORT: "80" # port the backend listens on + LISTEN_PORT: "80" # port AppShield listens on (matches `expose` + Caddy) + OIDC_REGISTRAR_URL: "http://auth-registrar:9092" # presence of this enables OIDC mode + REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},\${APP_PUBLIC_IP_DASH}.nip.io,\${APP_PUBLIC_IP_DASH}.sslip.io" + CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate" + # Optional: + # USER: "ADMIN" # extra basic-auth gate in front of the UI + # PASSWORD: $APP_DEFAULT_PASSWORD + # ALLOWED_PATHS: "mcp" # paths reachable with the hash token only (MCP servers) depends_on: - myapp-backend + cpu_shares: 80 networks: - pcs myapp-backend: image: myapp:1.2.3 - # No Caddy labels — only the hash-lock sidecar is publicly reachable + container_name: myapp-backend + # No Caddy labels — only the AppShield sidecar is publicly reachable expose: - - "80" + - 80 + cpu_shares: 50 networks: - pcs @@ -708,19 +720,36 @@ networks: external: true x-casaos: - main: myapp-auth # Caddy labels live on the sidecar - webui_port: 80 + main: myapp # primary service (the sidecar; some apps point it at the backend) + index: /?hash=$AUTH_HASH # launch URL carries the hash token so the user lands authenticated + webui_port: 80 # optional; keep at 80 if set ``` +**AppShield environment reference (OIDC mode):** + +| Variable | Required | Purpose | +| --- | --- | --- | +| `AUTH_HASH` | yes | Injected token; pair with `x-casaos.index: /?hash=$AUTH_HASH`. | +| `BACKEND_HOST` / `BACKEND_PORT` | yes | Internal DNS name + port of the protected container. | +| `LISTEN_PORT` | yes | Port AppShield listens on (matches `expose` + Caddy `{{upstreams}}`). | +| `OIDC_REGISTRAR_URL` | yes | `http://auth-registrar:9092` — enables OIDC; self-registers (no client id/secret). | +| `REDIRECT_HOST_SUFFIXES` | yes | `${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io` — valid OIDC redirect hosts. | +| `CREDENTIAL_VALIDATE_URL` | yes | `http://casaos-oidc-bridge:8090/validate` — validates the session against the PCS bridge. | +| `USER` / `PASSWORD` | optional | Extra basic-auth gate in front of the UI (e.g. `ADMIN` / `$APP_DEFAULT_PASSWORD`). | +| `ALLOWED_PATHS` | optional | Paths reachable with just the hash token, bypassing basic-auth — e.g. `mcp` for MCP servers. | + **Checklist for OIDC apps:** -- [ ] Caddy labels are attached **only to the hash-lock sidecar**, never to the backend — otherwise the backend is exposed unauthenticated. -- [ ] `x-casaos.main` points at the sidecar service. +- [ ] Caddy labels are attached **only to the AppShield sidecar**, never to the backend — otherwise the backend is exposed unauthenticated. +- [ ] The sidecar carries the full env set: `AUTH_HASH`, `BACKEND_HOST`, `BACKEND_PORT`, `LISTEN_PORT`, `OIDC_REGISTRAR_URL`, `REDIRECT_HOST_SUFFIXES`, `CREDENTIAL_VALIDATE_URL`. +- [ ] `x-casaos.index` is set to `/?hash=$AUTH_HASH` when using `AUTH_HASH`. +- [ ] `x-casaos.main` points at the primary service. - [ ] Backend service has no `ports:` and no public Caddy labels; it is reachable only via the `pcs` network. -- [ ] The top-level `name:`, the sidecar service name, and its `container_name` all match (lowercase alnum + `-`, not starting with a digit). `auth-registrar` derives the OIDC `client_id` from the container name via PTR lookup on the `pcs` network, so the `container_name` is load-bearing — it must be stable across reinstalls. +- [ ] The sidecar's `container_name` equals the top-level `name:` (lowercase alnum + `-`, not starting with a digit). `auth-registrar` derives the OIDC `client_id` from the container name via PTR lookup on the `pcs` network, so the `container_name` is load-bearing — it must be stable across reinstalls. The compose **service name itself may differ** (shipped apps use `myapp`, `myapp-proxy`, `nginxhashlock`, etc.). +- [ ] The sidecar sets `hostname:` to the **same value** as its `container_name`. AppShield's auth-service builds its OIDC redirect URIs from `os.hostname()` (as `-`), and `auth-registrar` independently attests the app name via the container's PTR record and **rejects any redirect URI that doesn't match**. If `hostname:` is omitted, Docker defaults it to the random container ID, the submitted redirect URIs won't match the attested name, and OIDC registration fails at first login. (`container_name` alone does **not** set the in-container hostname.) - [ ] Do not claim `auth-${APP_DOMAIN}` in any Caddy label — it collides with the PCS's Authelia and causes intermittent `invalid_client` errors. -- [ ] Pin `nginx-hash-lock` to a specific version tag (currently `1.0.7`). +- [ ] Pin AppShield to a specific version tag (currently `ghcr.io/yundera/appshield:2.0.3`) — never `:latest` / `:main`. -**Requirements on the host PCS:** the `authelia` and `auth-registrar` containers must be running on the `pcs` network (provisioned automatically by the current `template-root`). If they are missing, the app fails at first login with `ENOTFOUND auth-registrar` in the sidecar logs. +**Requirements on the host PCS:** the `authelia`, `auth-registrar`, and `casaos-oidc-bridge` containers must be running on the `pcs` network (provisioned automatically by the current `template-root`). If they are missing, the app fails at first login with `ENOTFOUND auth-registrar` in the sidecar logs. #### System Variables