-
Notifications
You must be signed in to change notification settings - Fork 4
feat(db): 自建 PostgreSQL + pgAdmin GUI + 自动备份,替代 Neon #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ea6f649
b803ca9
e22ed6a
eb8910a
167fbe0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,3 +36,6 @@ build/ | |
|
|
||
| ga4-sa-key.json | ||
| .env | ||
|
|
||
| # pgAdmin 密码文件(含明文,不入库) | ||
| docker/pgadmin/pgpass | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,9 +77,84 @@ services: | |
| networks: | ||
| - InvolutionHell-net | ||
|
|
||
| # pgAdmin:PostgreSQL 的 Web GUI,带完整的备份/恢复按钮。 | ||
| # 登录 http://<host>:8082,预注册服务器见 docker/pgadmin/servers.json, | ||
| # 密码通过 /pgpass 自动填充,不需要每次手输。 | ||
| # 备份卷挂到 /var/lib/pgadmin/storage/.../backups,在 Restore 对话框里直接看得到。 | ||
| pgadmin: | ||
| image: dpage/pgadmin4:latest | ||
| container_name: involution-pgadmin | ||
| restart: unless-stopped | ||
| environment: | ||
| PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL:-admin@involutionhell.com} | ||
| PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:-change_me} | ||
| # pgAdmin 保持 SERVER_MODE=False(desktop / single-user,无登录页)—— | ||
| # 关键前提:**外层 Caddy 做 forward_auth**,只有带合法 admin satoken | ||
| # cookie 的请求才被代理到这里。直接对公网暴露 8082 绝对不行。 | ||
| PGADMIN_CONFIG_SERVER_MODE: "False" | ||
| PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False" | ||
| # pgAdmin 被 iframe 嵌入在 involutionhell.com 主站的 /admin/database 页里, | ||
| # 流量路径:involutionhell.com(Vercel 前端) → iframe → api.involutionhell.com/admin/pgadmin/* | ||
| # SCRIPT_NAME 让 pgAdmin 生成的所有 URL 自带 /admin/pgadmin 前缀, | ||
| # X_FRAME_OPTIONS 清空让 Caddy 层自己控制 CSP frame-ancestors。 | ||
| SCRIPT_NAME: "/admin/pgadmin" | ||
| PGADMIN_CONFIG_X_FRAME_OPTIONS: "''" | ||
| PGADMIN_CONFIG_WTF_CSRF_SSL_STRICT: "False" | ||
| ports: | ||
| - "127.0.0.1:8082:80" | ||
| volumes: | ||
| - pgadmin-data:/var/lib/pgadmin | ||
| - ./docker/pgadmin/servers.json:/pgadmin4/servers.json:ro | ||
| # pgpass 主机文件必须是 UID 5050 所有且 0600,否则 pgAdmin 拒绝加载。 | ||
| # 用 `sudo chown 5050:5050 docker/pgadmin/pgpass && sudo chmod 600 …` 设好。 | ||
| - ./docker/pgadmin/pgpass:/tmp/pgpass | ||
| # 备份卷挂到容器内 /backups(只读)。SERVER_MODE=True 下 /var/lib/pgadmin/ | ||
| # storage/<email>/ 目录由 pgAdmin 运行时自建并校验 5050 所有权,把 root | ||
| # 所有的 pg-backups 挂进去会触发 "user does not have permission" 启动失败。 | ||
| # 在 pgAdmin Restore 对话框里手填路径 /backups/daily/xxx.dump 即可。 | ||
| - pg-backups:/backups:ro | ||
| depends_on: | ||
| postgres: | ||
| condition: service_healthy | ||
| networks: | ||
| - InvolutionHell-net | ||
|
|
||
| # 自动定时备份:每天 03:00 对 postgres 容器做 pg_dump(custom format), | ||
| # 输出到共享卷 pg-backups,pgAdmin 能直接在 Restore 对话框里看到这些文件。 | ||
| # 保留最近 30 天日备 / 8 周周备 / 12 个月月备。 | ||
| pg-backup: | ||
| image: prodrigestivill/postgres-backup-local:18-alpine | ||
| container_name: involution-pg-backup | ||
| restart: unless-stopped | ||
| environment: | ||
| POSTGRES_HOST: postgres | ||
| POSTGRES_DB: ${POSTGRES_DB:-involution_hell} | ||
| POSTGRES_USER: ${POSTGRES_USER:-involution} | ||
| POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change_me} | ||
| # -Fc 产出 custom 格式(.dump),pgAdmin 右键 Restore 直接一键恢复; | ||
| # 镜像包装脚本默认会 gzip,这里必须显式设 BACKUP_SUFFIX=.dump 且不加 gzip | ||
| # 选项,让镜像按 custom 格式直接落盘。 | ||
| POSTGRES_EXTRA_OPTS: "-Fc --blobs" | ||
| BACKUP_SUFFIX: ".dump" | ||
| BACKUP_COMPRESS: "none" | ||
| SCHEDULE: "@daily" | ||
|
Comment on lines
+122
to
+140
|
||
| BACKUP_KEEP_DAYS: 30 | ||
| BACKUP_KEEP_WEEKS: 8 | ||
| BACKUP_KEEP_MONTHS: 12 | ||
| HEALTHCHECK_PORT: 8080 | ||
| volumes: | ||
| - pg-backups:/backups | ||
| depends_on: | ||
| postgres: | ||
| condition: service_healthy | ||
| networks: | ||
| - InvolutionHell-net | ||
|
|
||
| networks: | ||
| InvolutionHell-net: | ||
| driver: bridge | ||
|
|
||
| volumes: | ||
| involution-postgres-data: | ||
| pgadmin-data: | ||
| pg-backups: | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1 @@ | ||||||
| postgres:5432:*:involution:change_me | ||||||
|
||||||
| postgres:5432:*:involution:change_me | |
| postgres:5432:*:neondb_owner:change_me |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,16 @@ | ||||||||||
| { | ||||||||||
| "Servers": { | ||||||||||
| "1": { | ||||||||||
| "Name": "InvolutionHell (local)", | ||||||||||
| "Group": "Servers", | ||||||||||
| "Host": "postgres", | ||||||||||
| "Port": 5432, | ||||||||||
| "MaintenanceDB": "involution_hell", | ||||||||||
| "Username": "neondb_owner", | ||||||||||
|
Comment on lines
+8
to
+9
|
||||||||||
| "MaintenanceDB": "involution_hell", | |
| "Username": "neondb_owner", | |
| "MaintenanceDB": "postgres", | |
| "Username": "involution", |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,232 @@ | ||||||||||||||||||
| # 数据库运维手册(自建 Docker PostgreSQL) | ||||||||||||||||||
|
|
||||||||||||||||||
| > 2026-04-17 起,生产/开发均从 Neon 迁到本机 compose 起的 `postgres:18-alpine`。 | ||||||||||||||||||
| > 迁移动因:Neon 免费月度额度(100 CU-h)耗尽后计算节点被暂停, | ||||||||||||||||||
| > 所有业务请求报错。自建后无配额限制,数据和延迟都本地可控。 | ||||||||||||||||||
|
|
||||||||||||||||||
| ## 架构总览 | ||||||||||||||||||
|
|
||||||||||||||||||
| ``` | ||||||||||||||||||
| docker-compose.yml 里四个相关服务: | ||||||||||||||||||
| postgres — PostgreSQL 18 主库,数据卷 involution-postgres-data(命名卷,持久化) | ||||||||||||||||||
| backend — Spring Boot,通过内网 jdbc:postgresql://postgres:5432/involution_hell 访问 | ||||||||||||||||||
| pg-backup — prodrigestivill/postgres-backup-local,每天 03:00 跑 pg_dump,保留 30 天日备/8 周周备/12 月月备 | ||||||||||||||||||
|
||||||||||||||||||
| pg-backup — prodrigestivill/postgres-backup-local,每天 03:00 跑 pg_dump,保留 30 天日备/8 周周备/12 月月备 | |
| pg-backup — prodrigestivill/postgres-backup-local,按 `@daily` 调度运行 pg_dump,保留 30 天日备/8 周周备/12 月月备 |
Copilot
AI
Apr 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里的 pgAdmin storage 路径同样硬编码了 admin_involutionhell.com,并依赖 PGADMIN_EMAIL 的具体值(@ 替换为 _)。如果部署时改了 PGADMIN_EMAIL,文档中给的路径将不再成立,可能导致“pgAdmin 看不到备份文件”的困惑。建议在文档里改为描述规则/动态路径,或要求固定 PGADMIN_EMAIL 并在 compose/文档中显式写出该约束。
| 备份文件写入 `pg-backups` 命名卷,pgAdmin 也挂载同一个卷到 | |
| `/var/lib/pgadmin/storage/admin_involutionhell.com/backups/`, | |
| 在 pgAdmin 的 Restore 对话框里直接选得到。 | |
| 备份文件写入 `pg-backups` 命名卷,pgAdmin 也挂载同一个卷。 | |
| 在容器内,pgAdmin 可见的目录通常位于 | |
| `/var/lib/pgadmin/storage/<由 PGADMIN_EMAIL 派生的目录名>/backups/`, | |
| 其中目录名依赖 `.env` 里的 `PGADMIN_EMAIL`(通常可理解为将 `@` 替换为 `_`)。 | |
| 如果部署时修改了 `PGADMIN_EMAIL`,请按实际派生后的目录查看;在 pgAdmin 的 Restore 对话框里可直接选到该目录中的备份文件。 |
Copilot
AI
Apr 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
手动备份这一段把产物描述为 .sql.gz(plain SQL + gzip),但 compose 里 pg-backup 已配置 POSTGRES_EXTRA_OPTS: "-Fc --blobs"(custom format),通常需要配合 pg_restore,且文件后缀/压缩方式可能不是 .sql.gz。建议统一:要么把 compose 改回 plain SQL + gzip(并在 env 设置对应 suffix),要么把本文这里的产物示例和后续恢复命令改为 custom format 的写法。
| # 文件名示例:involution_hell-20260417-201149.sql.gz(plain SQL + gzip) | |
| # 文件为 pg_dump -Fc --blobs 生成的 custom format,文件名/后缀以容器实际产物为准 | |
| # 恢复这类备份时通常使用 pg_restore |
Copilot
AI
Apr 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
恢复命令示例默认用 gunzip | psql 并且 FILE 也写成 .sql.gz,但当前 compose 已开启 -Fc(custom format)。在 custom format 下应使用 pg_restore(并匹配实际备份文件后缀/路径),否则按文档操作会直接失败。建议把示例拆成两套:plain SQL.gz 的恢复流程和 custom -Fc 的恢复流程,并确保默认配置对应其中一套。
Copilot
AI
Apr 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这一节说“切换备份格式到 -Fc(可选)”,但当前 docker-compose.yml 里 pg-backup 已经默认配置了 POSTGRES_EXTRA_OPTS: "-Fc --blobs"。这会让读者误以为默认是 .sql.gz,与前文/恢复命令示例产生冲突。建议要么把 compose 默认改回 plain SQL.gz,并保留这里作为可选切换;要么把这里改成“当前已使用 -Fc”并补全 BACKUP_SUFFIX/恢复命令的对应说明。
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package com.involutionhell.backend.admin.controller; | ||
|
|
||
| import cn.dev33.satoken.annotation.SaCheckRole; | ||
| import com.involutionhell.backend.common.api.ApiResponse; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| /** | ||
| * 管理员基础设施(非业务)辅助接口。 | ||
| * | ||
| * 目前只有一个:/api/admin/pgadmin-check —— 专门给 Caddy `forward_auth` 调用, | ||
| * 用来判断当前请求是否是 admin。通过就 200,否则 sa-token 自动抛 NotLogin / | ||
| * NotPermission 异常,全局异常处理器转成 401 / 403,Caddy 据此拒绝代理到 pgAdmin。 | ||
| * | ||
| * 设计要点: | ||
| * - sa-token 默认从 header / cookie 两边读 token(sa-token.is-read-cookie=true 默认开) | ||
| * 配合前端在登录时把 satoken 同步写一份到 .involutionhell.com 域名 cookie, | ||
| * 浏览器直接访问 api 子域时也能带上,forward_auth 校验链才能成立 | ||
| * - 响应体故意空壳,Caddy 只看状态码不看 body;保持最小负载 | ||
| * - 单独放在 admin/controller 包下而不是塞进 events/controller:这是 | ||
| * "基础设施级"鉴权桩,不属于任何业务域,放一起语义会误导 | ||
| */ | ||
| @RestController | ||
| @RequestMapping("/api/admin") | ||
| public class AdminInfraController { | ||
|
|
||
| /** | ||
| * Caddy 的 forward_auth 目标。只要通过 @SaCheckRole("admin") 就返回 200。 | ||
| * | ||
| * superadmin 的 roles 集合也包含 "admin"(由 sa-token 角色体系保证), | ||
| * 所以超管也能直接过,不用单独处理。 | ||
| */ | ||
| @GetMapping("/pgadmin-check") | ||
| @SaCheckRole("admin") | ||
| public ApiResponse<Void> pgadminCheck() { | ||
| return ApiResponse.okMessage("authorized"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pgpass在 PR 描述里提到“只读挂载”,但这里的 bind mount 没有加:ro。如果容器内进程(或被入侵后)能改写该文件,会影响后续连接行为;也不符合上面注释的“只读”意图。建议把该挂载改成只读,或更新注释/文档与实际保持一致。