关于 appgarden
appgarden 是用于通过 SSH 部署和运维 Web 应用的命令行技能,适合管理远程服务器的开发者。它支持多种部署方式,并可用项目配置管理不同环境。
主要功能
- 01通过 SSH 部署应用
- 02支持静态、命令及 Docker 部署
- 03使用 appgarden.toml 配置环境
- 04查看应用状态与日志
- 05管理应用生命周期
适用场景
- 01将静态站点部署到远程服务器
- 02按 staging 和 production 环境发布
- 03检查应用状态并排查日志
- 04重启、停止或重新部署应用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ lukastk/ appgarden --skill appgarden安装后在 Claude Code 中输入/appgarden确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.env.sample.envrcCLAUDE.md.gitignoreREADME.mdNBLITE_INSTRUCTIONS.mdnblite.toml.github/workflows/ci.ymlrelease.ymldev_scripts/clean-projectpublish-newrun-testspts/appgarden/01_remote.pct.py03_routing.pct.py00_config.pct.py06_apps.pct.py05_deploy.pct.py04_server.pct.py08_environments.pct.py07_auto_docker.pct.py02_ports.pct.py09_tunnel.pct.py_dir_server.pct.py10_cli.pct.pypts/tests/test_cli.pct.pytest_deploy.pct.pytest_apps.pct.pytest_environments.pct.pypts/tests/integration/test_server_init.pct.pyconftest.pct.py详细介绍
AppGarden CLI 技能
使用 appgarden CLI 部署或运维 Web 应用时,请使用此技能。
AppGarden 是一款无需 Agent 的部署工具:本地 CLI 通过 SSH 连接,准备远程服务,上传或克隆应用源码,写入 Caddy/systemd/Docker 文件,并将状态记录在服务器的 AppGarden 根目录下(默认为 /srv/appgarden)。
运行命令前
-
开发或测试 AppGarden 本身时,优先从 AppGarden 仓库检出目录运行:
cd /path/to/appgarden && uv run appgarden ...如果环境中已安装 AppGarden,也可以使用
appgarden ...。 -
以下只读/安全的发现命令无需确认即可运行:
appgarden --help、appgarden version、appgarden config show、appgarden server list、appgarden server ping、appgarden apps list、appgarden apps status、appgarden apps logs、appgarden tunnel list。 -
对会创建、更改、部署、重启、停止、删除或公开服务的命令,请先征求确认:
server add、server remove、server default、server init、deploy、apps start|stop|restart|redeploy|remove、apps meta set|replace|remove、tunnel open|close|cleanup。 -
对以下操作要格外谨慎:
appgarden server init --include ssh:加固 SSH 配置并重新加载 sshd。appgarden server init --include firewall:启用 UFW 并更改防火墙策略。appgarden apps remove NAME --yes:删除应用资源;只有在确实要保留应用的data/目录时,才添加--keep-data。appgarden deploy:可能覆盖已有应用的上传源码,并可能更新 Caddy/systemd/Docker 资源。appgarden tunnel open:通过配置的服务器公开本地端口/文件/目录,直到隧道停止。
配置位置
本地配置存储于:
~/.config/appgarden/config.toml
服务器配置格式:
default_server = "myserver"
[defaults]
method = "dockerfile"
container_port = 3000
[servers.myserver]
host = "203.0.113.10" # or hcloud_name + hcloud_context
ssh_user = "root"
ssh_key = "~/.ssh/id_rsa"
domain = "apps.example.com"
app_root = "/srv/appgarden" # optional; defaults to /srv/appgarden
[servers.myserver.init]
skip = ["upgrades"] # optional persistent init skips
远程状态和资源位于 app_root 下,通常为:
/srv/appgarden/garden.json # app registry
/srv/appgarden/apps/<name>/ # per-app files/source/app.json/.env/data
/srv/appgarden/caddy/apps/*.caddy # deployed app Caddy snippets
/srv/appgarden/caddy/tunnels/*.caddy
/srv/appgarden/tunnels/active.json
appgarden config show 会显示本地配置。对于指定服务器的命令,使用 --server <name> / -s <name> 或设置 APPGARDEN_SERVER;否则 AppGarden 会使用 default_server。
在一台主机上运行多个 garden: 为每个 garden 指定不同的 app_root(例如 /srv/appgarden 和 /srv/appgarden-proto),即可在同一台设备上运行多个 AppGarden garden。每个 garden 的状态(garden.json)、Caddy 片段以及 /etc/caddy/Caddyfile 中由 AppGarden 管理的区块,都会按 app_root 分别标识,因此一个 garden 的 server init 不会影响另一个。端口则是整台设备共享的资源——所有 garden 共用主机的 TCP 端口空间——因此端口分配记录在单个主机级注册表 /var/lib/appgarden/ports.json 中,由所有 garden 共享并通过同一把锁保护。这可以防止两个 garden 分配到相同的主机端口;你无需配置端口范围。注意事项:systemd 单元和 Docker 容器名称(appgarden-<name>)是整台设备共享的,因此同一主机上的不同 garden 必须使用唯一的应用名称。如果要升级的主机早于共享注册表功能,请对每个现有 garden 运行一次 appgarden server init——init 会将每个 garden 中已部署应用的端口同步到共享注册表(可幂等执行)。
服务器设置
服务器必须完成配置、可通过 SSH 访问,并且 DNS 必须指向该服务器。
appgarden server add myserver \
--host <server-ip-or-hostname> \
--ssh-user root \
--ssh-key ~/.ssh/id_rsa \
--domain apps.example.com
# Hetzner alternative: resolve IP through hcloud instead of --host
appgarden server add myserver \
--hcloud-name <hetzner-server-name> \
--hcloud-context <hcloud-context> \
--domain apps.example.com
appgarden server list
appgarden server ping myserver
appgarden server default myserver
对于子域名部署和隧道,请创建通配符 DNS 记录,例如将 *.apps.example.com 指向服务器 IP。显式配置的自定义主机名也需要 DNS 指向服务器。
初始化服务
每个服务只需运行一次:
appgarden server init myserver
当前 server init 的行为:
- 默认启用的可选步骤;使用
--skip跳过:update、docker、caddy、upgrades。 - 默认关闭的可选步骤;使用
--include启用:firewall、ssh、fail2ban、group。 - 始终运行的必要步骤:配置 AppGarden Caddyfile 导入块、创建目录、安装特权辅助程序/ sudoers 条目、设置部署用户的所有权、初始化状态文件,以及在可用时启动 Docker/Caddy。
示例:
appgarden server init myserver --minimal # essential steps only
appgarden server init myserver --skip upgrades # skip unattended-upgrades
appgarden server init myserver --include group # create/use appgarden group for non-root deploy users
appgarden server init myserver --include firewall # configure UFW; confirm first
appgarden server init myserver --include ssh # harden sshd; confirm first
对于非 root SSH 用户,server init 会安装 /usr/local/bin/appgarden-privileged,并添加一条仅允许执行 AppGarden 范围内特权操作的 sudoers 规则。
首选部署流程:appgarden.toml
在项目根目录创建 appgarden.toml,并部署命名环境。
[app]
name = "myapp"
slug = "my-app" # optional; used by {app.slug}; falls back to name
server = "myserver"
method = "dockerfile" # static | command | dockerfile | docker-compose | auto
source = "." # local path or git URL
container_port = 3000
subdomain = "{app.slug}" # or path/url; supports placeholders
exclude = ["node_modules", ".git"]
gitignore = true # local uploads honor .gitignore by default
meta = { team = "frontend" }
[environments.production]
branch = "main"
subdomain = "{app.slug}"
env = { NODE_ENV = "production" }
[environments.staging]
branch = "staging"
subdomain = "{app.slug}-staging"
env_file = ".env.staging"
meta = { visibility = "internal" }
部署:
appgarden deploy production
appgarden deploy staging
appgarden deploy --all-envs
appgarden deploy --project /path/to/project production
如果存在 appgarden.toml,且未提供环境/名称,appgarden deploy 会部署所有环境。如果提供了位置参数环境,该环境必须存在。
appgarden.toml 语义
[app].name为必填项。已部署应用的名称由此派生:production使用基础名称,其他环境使用<name>-<env>。例如:myapp和myapp-staging。- 值按以下顺序逐层覆盖:硬编码默认值 <
~/.config/appgarden/config.toml中的全局[defaults]<[app]默认值 <[environments.<name>]< CLI 标志 /APPGARDEN_*环境变量。 - 字典字段
env和meta按键合并;环境/CLI 值会覆盖较早的键。 - 列表字段
exclude和volumes会跨层拼接并去重。 - 项目/环境配置中的字符串值支持占位符:
{app.name}、{app.slug}、{env.name}。 - 相对本地
source和env_file值相对于项目目录,或相对于显式指定的--project文件所在目录解析。
[app] 或 [environments.<name>] 中的常用字段:
| 字段 | 含义 |
|---|---|
server | 已配置的服务名称。 |
method | static、command、dockerfile、docker-compose 或 auto。默认值为 static。 |
source | 本地目录/路径或 Git URL。static、dockerfile、docker-compose、auto 必填;command 可选。 |
url | 显式指定的公网主机名/路径,例如 example.com 或 example.com/docs。 |
subdomain | 与 domain 或服务域名组合的前缀,例如 docs -> docs.apps.example.com。 |
path | domain 或服务域名下的路径,例如 docs -> apps.example.com/docs。 |
domain | 覆盖用于 subdomain/path 的服务基础域名。 |
port | 主机端口;省略时,AppGarden 会根据其端口状态自动分配。 |
container_port | Dockerfile/auto 容器内部公开的端口。默认值为 3000。 |
cmd | command 和 auto 方法的启动命令。两者均为必填项。 |
setup_cmd | auto 的安装/构建命令;覆盖检测到的运行时默认值。 |
branch | Git 源的分支。 |
env | 内联环境变量。 |
env_file | 本地 dotenv 文件。 |
meta | 与应用一同存储并在状态中显示的任意元数据。 |
exclude | 本地上传的 Rsync 排除模式;apps redeploy 也会复用这些模式。 |
gitignore | 本地上传是否应用 rsync 的 .gitignore 过滤规则。默认值为 true;CLI 可通过 --no-gitignore 覆盖。 |
volumes | dockerfile 和 auto 的 Docker 卷挂载(host:container[:opts])。 |
created_at、updated_at、repo | 可选的元数据覆盖项。日期接受简短日期、ISO 字符串或 TOML 日期;如果可能,repo 会自动从本地 git origin 检测。 |
URL 解析顺序为:显式指定的 url;否则使用 subdomain + 域名;否则使用 path + 域名。对于子目录路由(domain/path),同一域名上的多个应用会共享生成的 Caddy 配置,AppGarden 也会检查 URL 冲突。
不使用 appgarden.toml 进行临时部署
使用 --name 和标志。大多数标志也有对应的 APPGARDEN_* 环境变量(APPGARDEN_NAME、APPGARDEN_SERVER、APPGARDEN_METHOD、APPGARDEN_SOURCE、APPGARDEN_URL、APPGARDEN_SUBDOMAIN、APPGARDEN_PATH、APPGARDEN_DOMAIN、APPGARDEN_PORT、APPGARDEN_CONTAINER_PORT、APPGARDEN_CMD、APPGARDEN_SETUP_CMD、APPGARDEN_BRANCH、APPGARDEN_ENVVAR_FILE、APPGARDEN_PROJECT、APPGARDEN_ALL_ENVS)。
# Static site / SPA
appgarden deploy --name mysite --method static --source ./dist --subdomain mysite
# Dockerfile app
appgarden deploy --name myapp --method dockerfile --source . --container-port 3000 --subdomain myapp
# Auto-generated Dockerfile; runtime is detected from package.json, requirements.txt,
# pyproject.toml, Gemfile, go.mod, or Cargo.toml. --cmd is required.
appgarden deploy --name myapp --method auto --source . --cmd "npm start" --subdomain myapp
# Bare process managed by systemd. --source is optional; --cmd is required.
appgarden deploy --name myapi --method command --source ./api --cmd "python app.py" --subdomain myapi
# Docker Compose stack; your compose file is used directly.
appgarden deploy --name mystack --method docker-compose --source ./project --subdomain mystack
# Explicit path routing instead of subdomain routing
appgarden deploy --name docs --method static --source ./docs --url apps.example.com/docs
# Env, metadata, excludes, and volumes
appgarden deploy --name myapp --method dockerfile --source . --subdomain myapp \
--envvar NODE_ENV=production \
--envvar-file .env.production \
--meta team=backend --meta visibility=internal \
--exclude node_modules --exclude .git \
--volume ./data:/app/data \
--no-gitignore
部署方式说明:
| 方式 | 适用场景 | 必需输入 | 运行时行为 |
|---|---|---|---|
static | HTML/CSS/JS/SPA | source | 上传/克隆源代码,并使用 Caddy 直接提供服务。 |
command | 不使用 Docker 的本地进程 | cmd(source 可选) | 创建一个设置了已分配/显式端口 PORT 的 systemd 服务。 |
dockerfile | 含有 Dockerfile 的项目 | source | 在远程构建镜像、写入 AppGarden compose 文件,并通过 systemd 运行。 |
docker-compose | 现有的 compose 项目 | source | 通过 systemd 运行你的 docker compose up/down;不会注入 volumes 配置。 |
auto | 简单的 Node/Python/Ruby/Go/Rust 项目 | source、cmd | 检测运行时、写入 Dockerfile、构建镜像并写入 compose 文件。 |
对于本地上传,AppGarden 使用带 --delete 的 rsync,默认遵循 .gitignore,并应用任何 --exclude/exclude 模式。对于 git 源,它会在远程克隆仓库,并可选检出指定分支。
部署应用 .env 文件的环境变量优先级为:appgarden.toml env < env_file 内容 < CLI --envvar 值。
管理已部署的应用
appgarden apps list [-s server] [--short]
appgarden apps status <name> [-s server]
appgarden apps logs <name> [-s server] [-n 100]
appgarden apps restart <name> [-s server]
appgarden apps redeploy <name> [-s server]
appgarden apps stop <name> [-s server]
appgarden apps start <name> [-s server]
appgarden apps remove <name> [-s server] [--keep-data] [--yes]
运维指南:
-
不确定确切的应用名称时,先使用
apps list。 -
使用
apps status查看 URL、路由、方式、端口、仓库/源、时间戳和元数据。 -
调试时,重启前使用
apps logs NAME -n 200。 -
apps redeploy会更新源:git 应用运行git pull(如果已存储分支,则使用该分支);本地源应用会使用已存储的exclude/gitignore重新上传;Dockerfile/auto 应用会重建镜像并保留已存储的卷;static 应用会重新加载 Caddy。 -
如果在
appgarden.toml中更改应用名称,请先移除旧应用,以避免 URL/Caddy 冲突:appgarden apps remove old-name --yes appgarden deploy production
元数据
元数据是与应用一同存储的任意 JSON 兼容键值数据。
appgarden apps meta get myapp [-s server]
appgarden apps meta set myapp --meta team=backend --meta tier=premium [-s server]
appgarden apps meta replace myapp --json '{"team":"frontend"}' [-s server]
appgarden apps meta remove myapp tier visibility [-s server]
在 appgarden.toml 中,meta 字典的合并方式与 env 字典相同:
[app]
name = "myapp"
meta = { team = "backend", visibility = "internal" }
[environments.production]
meta = { visibility = "public" }
# result: { team = "backend", visibility = "public" }
本地主机隧道
使用隧道通过远程服务,以 HTTPS 暴露本地开发服务、文件、目录或命令。除非在 --cmd/--serve 中使用 --close-on-cmd-exit,否则隧道会一直运行,直到按下 Ctrl+C。
# Expose an existing local server. URL omitted => random three-word subdomain.
appgarden tunnel open 3000
# Use server domain subdomain shorthand or an explicit URL
appgarden tunnel open 3000 --subdomain preview
appgarden tunnel open 3000 --url preview.apps.example.com
# Run a local command while the tunnel is open
appgarden tunnel open 3000 --cmd "npm run dev"
appgarden tunnel open 3000 --cmd "npm run dev" --close-on-cmd-exit
# Serve a local file or directory. LOCAL_PORT is optional with --serve.
appgarden tunnel open --serve ./dist --subdomain docs-preview
appgarden tunnel open --serve ./dist --include "*.html" --exclude "node_modules"
# Reclaim a URL a dead run still holds, then open (idempotent re-open)
appgarden tunnel open 3000 --url preview.apps.example.com --replace
appgarden tunnel list [-s server]
appgarden tunnel list --json [-s server] # machine-readable, for scripting
appgarden tunnel close <tunnel-id> [-s server]
appgarden tunnel cleanup [-s server]
--cmd 和 --serve 不能同时使用。只有当 --serve 指向目录时,--include/--exclude 才适用。隧道会分配一个远程端口,在 caddy/tunnels 下写入 Caddy 配置片段,并在 tunnels/active.json 中注册状态;清理时会移除这些资源。
--replace 是实现无人值守重新打开的关键。 隧道的 Caddy 配置片段以其自身的隧道 ID 为键,因此,如果某次运行被强制终止且未清理(例如重启或 SIGKILL),配置片段仍会占用该主机名;随后对相同 --url 执行 tunnel open 时,Caddy 重新加载会因站点定义不明确而失败,并回滚操作。--replace 会先关闭已注册到该 URL 的任何隧道。它要求明确指定 --url/--subdomain——自动生成的子域名无法匹配,因此会拒绝执行,而不会静默地什么也不做。请注意,tunnel cleanup 不能替代此操作:它只会回收远程端口上没有进程监听的隧道。
隧道在 SIGTERM 和 Ctrl+C 下都会正常关闭,因此通过监控程序执行 stop(supervisord、systemd、docker stop)时,会释放注册信息、Caddy 配置片段和远程端口,而不会遗留这些资源。
tunnel list --json 会输出 JSON 数组(没有隧道时为空数组,绝不会输出人类可读的 “No active tunnels.” 行)——请解析该数组,而不是表格。
故障排除工作流
-
确认配置和服务器目标:
appgarden config show appgarden server list appgarden server ping <server> -
检查应用状态和日志:
appgarden apps list --short appgarden apps status <app> appgarden apps logs <app> -n 200 -
阅读错误信息后再重新部署或重启:
appgarden apps redeploy <app> appgarden apps restart <app> -
本地来源上传的常见问题:
- 本地必须安装
rsync。 - 加密的 SSH 密钥需要 SSH Agent:
eval $(ssh-agent) && ssh-add ~/.ssh/id_rsa。 - 非 root 部署时出现权限错误,通常意味着需要重新运行
appgarden server init --include group或修正远程所有权。
- 本地必须安装
本仓库中的参考文件
生成的 src/appgarden/*.py 文件标有 “AUTOGENERATED”;通常应在对应的 pts/appgarden/*.pct.py 源文件中进行实现修改。有关使用技能的问题,请按需阅读以下文件:
README.md— 面向用户的指南和 CLI 参考。src/appgarden/cli.py/pts/appgarden/10_cli.pct.py— 命令定义和选项。src/appgarden/config.py/pts/appgarden/00_config.pct.py— 本地配置模型和默认配置路径。src/appgarden/environments.py/pts/appgarden/08_environments.pct.py—appgarden.toml解析、合并语义和占位符。src/appgarden/server.py/pts/appgarden/04_server.pct.py— 服务器初始化步骤。src/appgarden/deploy.py/pts/appgarden/05_deploy.pct.py— 部署行为和远程状态。src/appgarden/apps.py/pts/appgarden/06_apps.pct.py— 应用生命周期和重新部署行为。src/appgarden/routing.py/pts/appgarden/03_routing.pct.py— Caddy 路由和 URL 冲突行为。src/appgarden/tunnel.py/pts/appgarden/09_tunnel.pct.py— 隧道行为。