AcidDemon/ansible-nextcloud-debian
GitHub: AcidDemon/ansible-nextcloud-debian
一套通过 Ansible 在 Proxmox VE 的 Debian 13 虚拟机上自动化部署安全隔离架构 Nextcloud 私有云的 playbook。
Stars: 0 | Forks: 0
# ansible-nextcloud-debian







[](LICENSE)
[](https://github.com/AcidDemon/ansible-nextcloud-debian/commits/main)
[](https://github.com/AcidDemon/ansible-nextcloud-debian)
[](https://git.inviziblenet.work/AcidDemon/ansible-nextcloud-debian)
在运行于 Proxmox VE 的 Debian 13 虚拟机上部署 **Nextcloud** 的 Ansible playbook。
**应用层采用 rootless Podman + Quadlet**(systemd *用户* 单元),以 lingering
`ncstack` 用户身份运行:Postgres (`nc-db`)、Valkey (`nc-redis`)、Nextcloud
php-fpm (`nc-app`)、内部 mTLS nginx (`nc-web`),以及可选的 Collabora Online
(rootless)、ClamAV、监控 exporter 和 restic 备份。
**边缘层是基于 rootful 且由 gVisor 沙箱保护的 Caddy + Coraza WAF**(`edge`,一个
在 `runsc` 下运行的 *系统* Quadlet),在 80/443 端口终止 TLS。它必须以 rootful 运行:
gVisor 需要 rootful cgroups,而且 rootless `runsc` 在 cgroup v2 上无法正常工作(它会写入
根 cgroup 的 subtree_control / 驱动系统 systemd 管理器 → 触发 polkit)。边缘层
运行在 podman bridge 上 —— **而不是** `--network host`,因为 `runsc` 在 host networking 下没有可用的
loopback —— 并且通过主机网关(`host.containers.internal`)跨越 podman 作用域访问 rootless 应用层。
所有跨层流量都经过内部 CA (mTLS);应用层通过名称在
`nc-internal` Quadlet 网络上解析对等节点。
## 前置条件
- **Proxmox VE** 及 Debian 13 cloud-init 模板(由兄弟仓库
`proxmox-debian13-template` 构建,VMID 为 `9999`);将 `pve_template_vmid` 设置为该值。
- **部署 SSH 密钥对**位于 `files/ssh/nextcloud-deploy`(私钥,已添加至 git-ignore)和
`files/ssh/nextcloud-deploy.pub`(公钥,已提交)。使用以下命令生成:
`ssh-keygen -t ed25519 -f files/ssh/nextcloud-deploy`。
- **direnv**(执行 `direnv allow`)或手动执行 `export ANSIBLE_CONFIG=$PWD/ansible.cfg`。
`.envrc` 还会取消设置 `ANSIBLE_COLLECTIONS_PATH`、`ANSIBLE_COLLECTIONS_PATHS`
和 `ANSIBLE_ROLES_PATH` —— 如果在你的 shell profile 中设置了这些变量,它们会覆盖
项目配置并导致 collection/role 解析失败。
- **Collections:** `ansible-galaxy collection install -r requirements.yml`
- **VM 配置:**至少 **8 GB 内存和 4 个 vCPU**(`pve_vm_memory: 8192`,
`pve_vm_cores: 4`)。请参阅下方的配置说明 —— 此架构无法在 4 GB 内存中运行。
## 机密信息
编辑 `inventory/group_vars/all/vault.yml` —— 它以**纯文本存根**的形式提供,包含
`change-me-*` 值。为每个字段设置真实值,然后加密:
```
ansible-vault encrypt inventory/group_vars/all/vault.yml
```
`nextcloud` role 中的预检 `assert.yml` 会拒绝运行,前提是仍然存在任何
`change-me-*` 占位符。Vagrant 实验环境(lab)是例外,因为
`inventory/vagrant.yml` 将所有 `vault_*` 键覆盖为真实的实验环境密码作为
host vars(其优先级高于 `group_vars/all`),因此在实验环境中没有 `change-me-*` 占位符
会到达预检关卡。
加密后,**每一次**运行都需要 `--ask-vault-pass` —— 包括 Vagrant 实验环境的
运行 —— 因为 `group_vars/all/vault.yml` 会为所有 inventory 加载。实验环境仍然
使用来自 `inventory/vagrant.yml` 的临时值。配置中**没有 `.vault_pass`
文件和 `vault_password_file`**;密码仅在运行时提供。(为了避免在开发期间反复输入,可以保持存根为纯文本,仅在投入生产前加密,
或者自行传递 `--vault-password-file`。)
| Vault 键 | 用途 |
|---|---|
| `vault_pve_api_token_secret` | Proxmox API token secret(仅用于置备) |
| `vault_nc_db_password` | Postgres 密码(Podman secret `nc-db-password`) |
| `vault_nc_redis_password` | Valkey 密码(Podman secret `nc-redis-password`) |
| `vault_nc_admin_password` | Nextcloud 管理员密码(Podman secret `nc-admin-password`) |
| `vault_oidc_client_secret` | OIDC client secret(Podman secret `oidc-client-secret`) |
| `vault_ldap_bind_password` | LDAP bind 密码(Podman secret `ldap-bind-password`) |
| `vault_tls_cert` / `vault_tls_key` | 自带 TLS 材料(仅限 `tls_mode: byo`) |
## 运行矩阵
### 本地 Vagrant (libvirt)
```
vagrant up
ansible-playbook -i inventory/vagrant.yml site.yml --ask-vault-pass
```
### 远程测试 VM
```
ansible-playbook -i inventory/test.yml site.yml --ask-vault-pass
```
### 生产环境(包括在 Proxmox 上创建 VM)
```
ansible-playbook -i inventory/hosts.yml site.yml \
-e provision_vm_enabled=true --ask-vault-pass
```
### 按角色测试
请参阅 `tests/README.md` 获取完整列表以及每个测试断言的内容。示例:
```
ansible-playbook -i inventory/vagrant.yml tests/test-podman_host.yml --ask-vault-pass
ansible-playbook -i inventory/vagrant.yml tests/test-nextcloud.yml --ask-vault-pass
ansible-playbook -i inventory/vagrant.yml tests/test-collabora.yml --ask-vault-pass
ansible-playbook -i inventory/vagrant.yml tests/test-euro_office.yml --ask-vault-pass
```
按依赖顺序运行所有测试:
```
for t in os_base podman_host internal_pki caddy_edge \
nextcloud collabora clamav fail2ban monitoring nextcloud_backup \
firewall_coexistence; do
ansible-playbook -i inventory/vagrant.yml tests/test-$t.yml --ask-vault-pass || break
done
```
## 关键变量
所有变量都位于 `inventory/group_vars/all/main.yml`(单一事实来源);
role 内部的可调参数位于 `roles//defaults/main.yml`。
| 变量 | 默认值 | 说明 |
|---|---|---|
| `nc_domain` | `cloud.example.com` | 公共 FQDN;用于证书、边缘 vhost、Nextcloud trusted domains |
| `collabora_domain` | `office.example.com` | Collabora 公共 FQDN(边缘 vhost) |
| `tls_mode` | `acme` | `acme` = Let's Encrypt(公共 FQDN + 80/443 可达);`byo` = 通过 vault 提供的运维人员证书/密钥;`selfsigned` = 实验环境/Vagrant |
| `edge_tls_profile` | `intermediate` | `intermediate`(TLS 1.2+)或 `modern`(仅限 1.3;需要 `edge_tls_modern_attested: true`) |
| `waf_mode` | `detection` | Coraza/CRS 模式;**在 `enforce` 之前先在 `detection` 中观察** |
| `gvisor_platform` | `systrap` | gVisor 平台 —— **绝不要使用 `kvm`**(克隆的 VM 上没有嵌套虚拟化) |
| `ldap_enabled` / `oidc_enabled` | `true` / `true` | LDAP(s) 身份验证和 OIDC SSO |
| `sse_master_key_enabled` | `true` | 主密钥服务端加密(与 SSO 登录配合使用) |
| `collabora_enabled` / `collabora_mode` | `true` / `rootless` | 强制使用 rootless;`rootful` 仅限明确主动开启 |
| `clamav_enabled` | `false` | ClamAV 杀毒;`clamav_max_scan_bytes` 必须等于 NC 最大上传大小且 <= `php_memory_limit` |
| `fail2ban_enabled` | `true` | `jail.d/` 中的防暴力破解 jail(与 base-debian 的 `inet base_filter` 共存,见下文) |
| `monitoring_enabled` / `monitoring_local_dashboard` | `false` / `false` | loopback 上的 exporter;可选的本机 Prometheus+Grafana |
| `nextcloud_backup_enabled` | `false` | 通过 restic 备份到 `nextcloud_backup_repo` |
| `management_cidrs` | `["10.0.0.0/8"]` | 允许访问 SSH (22) 的 CIDR;也是 fail2ban 的 `ignoreip`。由 base-debian 的防火墙使用 |
| `firewall_allow_tcp` | `[80, 443]` | base-debian 为边缘层开放的公共 TCP 端口 |
| `firewall_trust_iifnames` | `["podman*"]` | base-debian 在 INPUT 上信任的接口(podman bridge) |
| `firewall_forward_policy` | `accept` | base-debian 的 FORWARD 策略(容器出站流量) |
| `provision_vm_enabled` | `false` | 从 Proxmox 模板克隆并配置新 VM |
| `pve_vm_cores` / `pve_vm_memory` / `pve_vm_disk_size` | `4` / `8192` / `100G` | VM 配置 —— 见说明 |
| `pve_validate_certs` | `false` | 容忍 Proxmox 自签名的 API 证书;对于 CA 签名/远程 PVE 设置为 `true` |
### VM 配置 (>=8 GB)
默认的 `pve_vm_memory: 8192`(8 GB)和 `pve_vm_cores: 4` 是**下限,而不是
建议**。并发运行 Postgres + Valkey + Nextcloud php-fpm + nginx +
受 gVisor 沙箱保护的 Caddy/Coraza 边缘层无法容纳在 4 GB 内存中。如果启用了 ClamAV,
请额外预留约 2.5 GB 的峰值内存(clamd 会保存完整的特征库以及大小设为
`clamav_max_scan_bytes` 的扫描缓冲区);如果开启了 `monitoring_local_dashboard`,
请为本机 Prometheus + Grafana 预留足够空间。不要将内存缩减到 8 GB 以下。
**磁盘:**仅容器镜像就总计达数 GB —— Collabora CODE 约 1.5 GB,
Nextcloud-fpm 约 300 MB,Postgres/Valkey/nginx 约 250 MB,加上边缘镜像构建
(xcaddy Go 工具链 + 模块缓存需要约 1 GB *瞬时空间*)。在计算用户数据之前,至少为
OS 磁盘预留 **20 GB** 空间(`pve_vm_disk_size: 100G` 是默认值,专为存放数据而设定)。较小的根磁盘(例如 3 GB)甚至无法构建
边缘镜像 —— `podman build` 会因 `no space left on device` 而失败。
## 操作系统加固(外部)—— 关键集成限制
完整的操作系统加固由兄弟仓库
[`ansible-hardening-debian`](https://github.com/acidnetworks/ansible-hardening-debian)
负责,并作为一个**独立的编排层**运行。此仓库仅提供
浅层的 `os_base`。按以下顺序运行:
1. `provision-vm.yml`(或使用现有的 Debian 13 主机)。
2. `ansible-hardening-debian`:先执行 `bootstrap.yml`,再执行其 `site.yml`。
3. 此仓库的 `site.yml`,通过密钥以加固仓库的 deploy user 身份进行连接。
`playbooks/harden.yml.disabled` 包含了一个被注释掉的 `import_playbook` 钩子;
将其重命名为 `harden.yml`,并在加固仓库公开稳定的
`harden.yml` 后将其接入。
## 隔离与 rootless 说明
- **应用层(rootless):**`nc-db`、`nc-redis`、`nc-app`、`nc-web`、Colla、
`euro-office` 以及 exporter 以 lingering 的 `ncstack` 用户(UID 1500)身份运行。
它们的 Quadlet 位于 `/home/ncstack/.config/containers/systemd/`;请以 `ncstack` 身份使用
`systemctl --user` 进行管理。`podman-auto-update.timer` 在用户作用域内运行。
- **边缘层(rootful):**`edge` 单元是
`/etc/containers/systemd/` 中的**系统** Quadlet;请以 root 身份使用 `systemctl` 进行管理。它直接绑定 80/443 端口(rootful —— 无需 unprivileged-port sysctl),并在 `runsc` 下运行。
- gVisor `runsc` 安装在 `/usr/local/bin/runsc` 并注册为名为 `runsc` 的 Podman
OCI runtime,适用于 root podman(边缘层)**以及** rootless ncstack
层(euro_office Document Server)。平台为 `systrap`,绝不要使用 `kvm`。crun 仍然是
rootless 的默认选项。
- **边缘层 ↔ 应用网络:**rootful 边缘层无法共享 rootless 应用层的 podman
网络,因此它通过主机网关跨作用域访问应用层。因此 `nc-web` 和
Collabora 发布在 `0.0.0.0`(`:8443` / `:9980`)上,而不是
loopback —— 但是 base-debian 的 `base_filter` INPUT 链**会丢弃来自
公共接口的这些端口**(只有内部的 podman bridge,即 `iifname "podman*"`,可以
通过 `firewall_trust_iifnames` 访问它们),并且边缘层→`nc-web` 的跳转是 mTLS。
**DB 和 Redis 绝不会被发布。**
- base-debian 在 INPUT(`firewall_trust_iifnames: ["podman*"]`)中信任 podman bridge,
以便 rootful 容器可以通过 aardvark-dns(监听在
bridge 网关上)解析名称,并且边缘层可以访问应用层。公共接口绝不能是
podman bridge,因此仅暴露 80/443/22 的限制仍然有效。
- 容器间的名称解析使用 netavark + aardvark-dns(明确安装了 ——
在 Debian 上 netavark 只是一个 `Recommends` 依赖)。
- `occ` 以 `podman exec -u www-data nc-app php occ `
(作为 `become_user: ncstack`)的方式运行。
标签:Ansible, gVisor, Nextcloud, Podman, Proxmox, 测试用例, 特权提升, 系统提示词, 系统运维, 自动化部署