commit 93bb867c3fc197668bbfb145bb9ce147877e2470 Author: orion Date: Sat Mar 21 07:13:57 2026 +0000 Initial sanitized deployment snapshot diff --git a/README.md b/README.md new file mode 100644 index 0000000..b5860cc --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# HangmanLab.Server.T0 + +Sanitized deployment snapshot for the Dockerized `vps.git` stack. + +Included: +- `git-kc/compose.yaml` +- `git-kc/env.template` +- `git-kc/mysql/init/01-init-databases.sh` +- `git-kc/gitea/app.ini.redacted` +- `git-kc/docs/` +- `logs/VPS_GIT_WORKLOG_2026-03-20.md` + +Excluded on purpose: +- `.env` +- database dumps / backups +- Keycloak realm export files +- raw secrets / tokens / passwords diff --git a/git-kc/compose.yaml b/git-kc/compose.yaml new file mode 100644 index 0000000..238b641 --- /dev/null +++ b/git-kc/compose.yaml @@ -0,0 +1,112 @@ +# Draft target path on vps.git: ~/git-kc/compose.yaml +# Notes: +# - Keep nginx on the host. +# - Keep MySQL private inside the Docker network. +# - First migration run: Keycloak uses --import-realm. +# After successful import, you may remove --import-realm for steady-state. +# - This draft assumes Gitea SSH is disabled for phase 1. + +services: + mysql: + image: ${MYSQL_IMAGE} + container_name: git-kc-mysql + restart: unless-stopped + env_file: + - .env + environment: + TZ: ${TZ} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_0900_ai_ci + - --default-authentication-plugin=caching_sha2_password + - --skip-name-resolve + volumes: + - ./mysql/data:/var/lib/mysql + - ./mysql/init:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD-SHELL", "mysqladmin ping -uroot -p$$MYSQL_ROOT_PASSWORD --silent"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 20s + networks: + - git-kc-net + + gitea: + image: ${GITEA_IMAGE} + container_name: git-kc-gitea + restart: unless-stopped + depends_on: + mysql: + condition: service_healthy + env_file: + - .env + user: "${GITEA_UID}:${GITEA_GID}" + environment: + TZ: ${TZ} + USER_UID: ${GITEA_UID} + USER_GID: ${GITEA_GID} + HOME: /home/git + GITEA_WORK_DIR: /var/lib/gitea + GITEA_CUSTOM: /var/lib/gitea/custom + working_dir: /var/lib/gitea + command: ["gitea", "web", "--config", "/etc/gitea/app.ini"] + ports: + - "${GITEA_HOST_BIND}:${GITEA_HTTP_PORT}:3000" + volumes: + - /var/lib/gitea:/var/lib/gitea + - /home/git:/home/git + - ./gitea/app.ini:/etc/gitea/app.ini:ro + healthcheck: + test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost:3000/ || exit 1"] + interval: 15s + timeout: 5s + retries: 10 + start_period: 30s + networks: + - git-kc-net + + keycloak: + image: ${KEYCLOAK_IMAGE} + container_name: git-kc-keycloak + restart: unless-stopped + depends_on: + mysql: + condition: service_healthy + env_file: + - .env + environment: + TZ: ${TZ} + KC_DB: mysql + KC_DB_URL_HOST: ${KC_DB_URL_HOST} + KC_DB_URL_PORT: ${KC_DB_URL_PORT} + KC_DB_URL_DATABASE: ${KC_DB_URL_DATABASE} + KC_DB_USERNAME: ${KC_DB_USERNAME} + KC_DB_PASSWORD: ${KC_DB_PASSWORD} + KC_HOSTNAME: ${KC_HOSTNAME} + KC_HTTP_ENABLED: "true" + KC_PROXY_HEADERS: xforwarded + KC_HEALTH_ENABLED: "true" + KC_METRICS_ENABLED: "true" + KC_BOOTSTRAP_ADMIN_USERNAME: ${KC_BOOTSTRAP_ADMIN_USERNAME} + KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_BOOTSTRAP_ADMIN_PASSWORD} + command: + - start + ports: + - "${KEYCLOAK_HOST_BIND}:${KEYCLOAK_HTTP_PORT}:8080" + volumes: + - ./keycloak/import:/opt/keycloak/data/import:ro + healthcheck: + test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/localhost/8080' && exit 0 || exit 1"] + interval: 15s + timeout: 5s + retries: 20 + start_period: 45s + networks: + - git-kc-net + +networks: + git-kc-net: + name: ${DOCKER_NETWORK_NAME} + driver: bridge diff --git a/git-kc/docs/CHECKLIST.md b/git-kc/docs/CHECKLIST.md new file mode 100644 index 0000000..8ddcb54 --- /dev/null +++ b/git-kc/docs/CHECKLIST.md @@ -0,0 +1,447 @@ +# vps.git Docker 迁移 Checklist + +> 目标:将 `vps.git` 上的 Gitea、Keycloak、MySQL 迁移到 Docker;宿主机 nginx 保持不动。 +> +> 当前约束: +> - Gitea / Keycloak / MySQL 进入同一个 Docker 网络 +> - MySQL 为单实例,内部拆分 `` / `` +> - Gitea 直接挂载旧目录 +> - Keycloak 从 H2 迁移到 MySQL +> - 本次先不启用 Git over SSH + +--- + +## 0. 迁移前确认 + +- [ ] 确认维护窗口可开始 +- [ ] 确认当前整机备份已存在 +- [ ] 确认当前无人依赖 Git over SSH +- [ ] 确认本次不做大版本升级 +- [ ] 确认宿主机 nginx 保持不动 + +--- + +## 1. 安装 Docker Engine(vps.git) + +推荐使用 Docker 官方仓库安装。 + +```bash +apt-get update +apt-get install -y ca-certificates curl gnupg +install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg +chmod a+r /etc/apt/keyrings/docker.gpg + +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo \"$VERSION_CODENAME\") stable" \ + | tee /etc/apt/sources.list.d/docker.list > /dev/null + +apt-get update +apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +docker --version +docker compose version +``` + +验收: +- [ ] `docker --version` 正常 +- [ ] `docker compose version` 正常 + +--- + +## 2. 准备部署目录 + +```bash +mkdir -p ~/git-kc/{backups,docs,gitea,keycloak/import,mysql/data,mysql/init} +chmod 700 ~/git-kc +``` + +验收: +- [ ] `~/git-kc` 已创建 +- [ ] 子目录结构完整 + +--- + +## 3. 准备 compose 与 .env + +将以下草案文件放到目标目录: + +- `compose.yaml` +- `.env` + +来源建议: +- 使用 workspace 内草案: + - `VPS_GIT_DOCKER_COMPOSE_DRAFT.yaml` + - `VPS_GIT_DOCKER_ENV_TEMPLATE.env` + +```bash +cp VPS_GIT_DOCKER_COMPOSE_DRAFT.yaml ~/git-kc/compose.yaml +cp VPS_GIT_DOCKER_ENV_TEMPLATE.env ~/git-kc/.env +chmod 600 ~/git-kc/.env +``` + +然后填写真实密码,并把 `GITEA_UID/GITEA_GID` 改成宿主机 `git` 用户实际数值: + +```bash +id -u git +id -g git +``` + +验收: +- [ ] `~/git-kc/compose.yaml` 已就位 +- [ ] `~/git-kc/.env` 已填写真实密码 +- [ ] `GITEA_UID/GITEA_GID` 已按实际值修正 + +--- + +## 4. 准备 Gitea 配置副本 + +复制现有配置,不直接原地改 `/etc/gitea/app.ini`。 + +```bash +cp /etc/gitea/app.ini ~/git-kc/gitea/app.ini +``` + +需要修改的核心项: + +- [ ] `[database] HOST = mysql:3306` +- [ ] 保持 `DB_TYPE = mysql` +- [ ] `NAME = ` +- [ ] `USER = gitea` +- [ ] `PASSWD = <新的 Docker MySQL 中 gitea 用户密码>` +- [ ] 保持 `ROOT_URL = https://git.hangman-lab.top/` +- [ ] 本次先不启用 Git over SSH +- [ ] 在 `app.ini` 中显式设置 `DISABLE_SSH = true` +- [ ] 如存在 `START_SSH_SERVER`,显式设置为 `false` +- [ ] 检查页面不再误导用户展示不可用的 SSH clone/push 路径 + +建议先备份副本再改: + +```bash +cp ~/git-kc/gitea/app.ini ~/git-kc/gitea/app.ini.bak +``` + +验收: +- [ ] `~/git-kc/gitea/app.ini` 已生成 +- [ ] DB HOST 已改为 `mysql:3306` +- [ ] 其他核心业务配置未误改 + +--- + +## 5. 准备 MySQL 初始化逻辑 + +目标:Docker MySQL 首次启动时自动创建: + +- `` +- `` +- `gitea@'%'` +- `keycloak@'%'` + +建议在 `~/git-kc/mysql/init/` 中放一个首次初始化脚本,例如: + +- `01-init-databases.sh` + +workspace 内已提供草案文件: + +- `VPS_GIT_DOCKER_MYSQL_INIT_DRAFT.sh` + +可直接放到目标位置: + +```bash +cp VPS_GIT_DOCKER_MYSQL_INIT_DRAFT.sh ~/git-kc/mysql/init/01-init-databases.sh +chmod 755 ~/git-kc/mysql/init/01-init-databases.sh +``` + +> 如果实施环境不是当前 workspace 所在机器,也需要先把该草案文件同步到 `vps.git`。 + +脚本职责: +- [ ] 读取 `.env` 中的变量 +- [ ] 创建两个 database +- [ ] 创建两个用户 +- [ ] 分配最小权限 + +> 注意:`docker-entrypoint-initdb.d` 只会在 **空 datadir 第一次启动** 时执行。 + +验收: +- [ ] 已准备初始化脚本或确认采用手动 SQL 初始化 + +--- + +## 6. 准备 Keycloak 导出目录 + +```bash +mkdir -p ~/git-kc/keycloak/import +``` + +说明: +- 旧 Keycloak 导出的 realm 文件,后续放到这里 +- Docker Keycloak 首次启动时通过 `--import-realm` 导入 + +验收: +- [ ] `~/git-kc/keycloak/import` 已存在 + +--- + +## 7. 预拉取镜像(切换前) + +```bash +cd ~/git-kc +docker compose --env-file .env pull +``` + +验收: +- [ ] MySQL 镜像拉取成功 +- [ ] Gitea 镜像拉取成功 +- [ ] Keycloak 镜像拉取成功 + +--- + +## 8. 迁移前备份 + +### 8.1 备份 Gitea 配置 + +```bash +cp /etc/gitea/app.ini ~/git-kc/backups/app.ini.$(date +%F-%H%M%S).bak +``` + +- [ ] 已备份 `/etc/gitea/app.ini` + +### 8.2 导出 Gitea 数据库 + +```bash +mysqldump \ + --single-transaction \ + --routines \ + --triggers \ + --events \ + > ~/git-kc/backups/.$(date +%F-%H%M%S).sql +``` + +- [ ] 已导出 `` + +### 8.3 导出 Keycloak(H2) + +先停止旧 Keycloak,再执行离线导出: + +```bash +systemctl stop keycloak +sudo -u keycloak /opt/keycloak/bin/kc.sh export \ + --dir ~/git-kc/keycloak/import \ + --users realm_file +``` + +> 如果导出目录权限有问题,可先导出到 `/tmp` 或 keycloak 用户可写目录,再移动到 `~/git-kc/keycloak/import/`。 + +- [ ] 已导出 Keycloak realm / users + +### 8.4 备份 nginx 相关配置 + +```bash +cp /etc/nginx/sites-enabled/git.hangman-lab.top ~/git-kc/backups/git.hangman-lab.top.$(date +%F-%H%M%S).bak +cp /etc/nginx/sites-enabled/login.hangman-lab.top ~/git-kc/backups/login.hangman-lab.top.$(date +%F-%H%M%S).bak +``` + +- [ ] 已备份 nginx 站点配置 + +--- + +## 9. 切换阶段 + +### 9.1 停止旧服务 + +```bash +systemctl stop gitea +systemctl stop keycloak +systemctl stop mysql +``` + +- [ ] 旧 Gitea 已停 +- [ ] 旧 Keycloak 已停 +- [ ] 旧 MySQL 已停 + +### 9.2 启动 Docker MySQL + +```bash +cd ~/git-kc +docker compose --env-file .env up -d mysql +docker compose ps +``` + +- [ ] MySQL 容器已启动 +- [ ] MySQL healthcheck 正常 + +### 9.3 初始化数据库 + +如果已准备 `mysql/init` 初始化脚本: +- [ ] 确认第一次启动已执行成功 + +如果采用手动方式,执行 SQL 初始化: + +```bash +docker exec -it git-kc-mysql mysql -uroot -p +``` + +手动创建: +- [ ] `` +- [ ] `` +- [ ] `gitea` 用户及权限 +- [ ] `keycloak` 用户及权限 + +### 9.4 导入 Gitea 数据库 + +```bash +cat ~/git-kc/backups/..sql | docker exec -i git-kc-mysql mysql -uroot -p${MYSQL_ROOT_PASSWORD} +``` + +- [ ] `` 已导入 + +### 9.5 启动 Docker Keycloak + +```bash +cd ~/git-kc +docker compose --env-file .env up -d keycloak +docker compose logs --tail=200 keycloak +``` + +- [ ] Keycloak 容器已启动 +- [ ] import 完成 +- [ ] 无明显启动报错 + +### 9.6 启动 Docker Gitea + +```bash +cd ~/git-kc +docker compose --env-file .env up -d gitea +docker compose logs --tail=200 gitea +``` + +- [ ] Gitea 容器已启动 +- [ ] 无明显启动报错 + +--- + +## 10. 修改 nginx(仅 Keycloak upstream) + +目标变更: + +- 从 `proxy_pass https://localhost:8443;` +- 改为 `proxy_pass http://localhost:8080;` + +同时确认保留: + +- `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;` + +验证并重载: + +```bash +nginx -t +systemctl reload nginx +``` + +- [ ] nginx 配置校验通过 +- [ ] nginx 已 reload + +--- + +## 11. 验证阶段 + +### 11.1 验证 Gitea + +- [ ] `https://git.hangman-lab.top/` 可访问 +- [ ] 首页正常 +- [ ] 用户可登录 +- [ ] 仓库列表正常 +- [ ] 现有仓库可见 +- [ ] HTTPS clone 正常 +- [ ] HTTPS push 正常 +- [ ] OIDC 跳转到 Keycloak 正常 +- [ ] 从 Keycloak 回跳 Gitea 正常 + +### 11.2 验证 Keycloak + +- [ ] `https://login.hangman-lab.top/` 可访问 +- [ ] `/.well-known/openid-configuration` 正常 +- [ ] `Hangman-Lab` realm 正常 +- [ ] 管理后台可登录 +- [ ] 普通用户可登录 +- [ ] 原 client 保留 +- [ ] issuer 与原来一致 + +### 11.3 验证 MySQL + +- [ ] `` 表完整 +- [ ] `` 表完整 +- [ ] Gitea 能正常连接 MySQL +- [ ] Keycloak 能正常连接 MySQL + +### 11.4 验证 Docker 重启持久化 + +```bash +docker compose --env-file ~/git-kc/.env -f ~/git-kc/compose.yaml restart +``` + +- [ ] 三个容器重启后仍正常 +- [ ] 数据未丢失 + +--- + +## 12. 回滚 Checklist + +如果验证失败,执行回滚: + +### 12.1 停止 Docker 服务 + +```bash +cd ~/git-kc +docker compose --env-file .env down +``` + +- [ ] Docker 容器已停 + +### 12.2 恢复 nginx Keycloak upstream + +- [ ] 将 `http://localhost:8080` 改回 `https://localhost:8443` + +```bash +nginx -t +systemctl reload nginx +``` + +- [ ] nginx 已恢复 + +### 12.3 启动旧宿主机服务 + +```bash +systemctl start mysql +systemctl start keycloak +systemctl start gitea +``` + +- [ ] 旧 MySQL 已恢复 +- [ ] 旧 Keycloak 已恢复 +- [ ] 旧 Gitea 已恢复 + +### 12.4 回滚验证 + +- [ ] Gitea 页面恢复 +- [ ] Keycloak 页面恢复 +- [ ] Gitea 登录恢复 +- [ ] OIDC 恢复 + +--- + +## 13. 迁移后待办(非本次切换阻塞项) + +- [ ] 清理 Keycloak 首次导入相关临时文件 +- [ ] 视情况去掉 Keycloak `--import-realm` +- [ ] 评估是否恢复 Git over SSH +- [ ] 如需恢复 Git over SSH,单独设计第二阶段方案 +- [ ] 更新与 Docker 化相关的运维脚本 +��第二阶段方案 +- [ ] 更新与 Docker 化相关的运维脚本 +�案 +- [ ] 更新与 Docker 化相关的运维脚本 diff --git a/git-kc/docs/PLAN.md b/git-kc/docs/PLAN.md new file mode 100644 index 0000000..3580977 --- /dev/null +++ b/git-kc/docs/PLAN.md @@ -0,0 +1,526 @@ +# vps.git Docker 迁移方案 v1 + +## 目标 + +把 `vps.git` 上当前裸机部署的: + +- Gitea +- Keycloak +- MySQL + +迁移为 Docker 版本,并满足以下约束: + +- `gitea`、`keycloak`、`mysql` 位于同一个 Docker 网络 +- MySQL 只保留一个容器实例,但实例内分两个独立库: + - `` + - `` +- 宿主机 `nginx` 架构不变,继续保留在宿主机上 +- Gitea 直接挂载现有数据目录 +- Keycloak 从 H2 迁移到 MySQL +- 敏感信息统一放在 `.env` +- Docker 部署根目录放在 `~/git-kc` +- 本次迁移不顺手升级大版本,优先保持现有版本基线 +- 本次迁移默认先不启用 Git over SSH,仅保证 HTTPS Git 正常 + +--- + +## 当前现状摘要 + +### 服务现状 + +宿主机当前运行: + +- `nginx` +- `gitea.service` +- `keycloak.service` +- `mysql.service` + +### 监听关系 + +- `nginx`:对外 `80/443` +- `gitea`:宿主机监听 `3000` +- `keycloak`:宿主机监听 `8443` +- `mysql`:宿主机监听 `localhost:3306` + +### 当前数据位置 + +- Gitea 配置:`/etc/gitea/app.ini` +- Gitea 数据:`/var/lib/gitea` +- Keycloak 配置:`/opt/keycloak/conf/keycloak.conf` +- Keycloak H2 数据:`/opt/keycloak/data/h2` +- MySQL 数据:`/var/lib/mysql` + +### 当前版本基线 + +- Gitea:`1.22.4` +- Keycloak:`26.0.6` +- MySQL:`8.0.x` + +迁移时保持同代版本,不使用 `latest`。 + +--- + +## 目标架构 + +### 宿主机保留 + +- `nginx` +- 当前证书与域名 +- 宿主机 SSH 运维入口 + +### Docker 内运行 + +- `mysql` +- `gitea` +- `keycloak` + +三者放在同一个 Docker 网络中,例如: + +- `git-kc-net` + +### 端口策略 + +- `mysql` + - 不对宿主机发布端口 + - 仅允许 Docker 内部网络访问 +- `gitea` + - 发布到 `localhost:3000` +- `keycloak` + - 发布到 `localhost:8080` + +### nginx 反代目标 + +- `git.hangman-lab.top` → `http://localhost:3000` +- `login.hangman-lab.top` → `http://localhost:8080` + +说明: + +- Gitea 迁移后,对外域名不变 +- Keycloak 迁移后,对外域名不变 +- Keycloak 容器内不再自行持有 HTTPS,TLS 由宿主机 nginx 负责终止 + +--- + +## 部署目录设计 + +Docker 部署根目录: + +- `~/git-kc` + +建议结构: + +- `~/git-kc/compose.yaml` +- `~/git-kc/.env` +- `~/git-kc/backups/` +- `~/git-kc/mysql/` +- `~/git-kc/gitea/` +- `~/git-kc/keycloak/` +- `~/git-kc/keycloak/import/` +- `~/git-kc/docs/` + +用途说明: + +- `compose.yaml`:Compose 主文件 +- `.env`:敏感配置 +- `backups/`:迁移专用备份 +- `gitea/`:Gitea 容器使用的配置副本 +- `keycloak/import/`:Keycloak realm 导入文件 +- `docs/`:迁移记录与回滚说明 + +--- + +## 数据与配置保留策略 + +### Gitea + +Gitea 采用“保留原数据目录,容器挂载使用”的方式: + +- 直接挂载现有数据目录:`/var/lib/gitea` + +配置文件策略: + +- 不直接原地修改宿主机当前使用的 `/etc/gitea/app.ini` +- 先复制一份到:`~/git-kc/gitea/app.ini` +- Docker Gitea 使用这份配置副本 +- 原始 `/etc/gitea/app.ini` 保留不动,用于快速回滚 + +### Keycloak + +Keycloak 不直接复用当前运行目录作为 Docker 运行目录,而是: + +1. 从旧 Keycloak 执行 realm export +2. 在 Docker Keycloak 中导入 realm +3. 新容器改用 MySQL 存储 + +旧目录保留: + +- `/opt/keycloak/conf` +- `/opt/keycloak/data/h2` + +### MySQL + +MySQL 不直接接管旧宿主机 datadir,而采用逻辑导出 / 导入迁移: + +- 导出旧 `` +- 在 Docker MySQL 内创建新实例环境 +- 导入 `` +- 新建 `` + +--- + +## 数据库设计 + +Docker MySQL 容器内使用一个实例,分为两个独立数据库: + +- `` +- `` + +分别创建独立账户: + +- `gitea` +- `keycloak` + +权限原则: + +- `gitea` 仅访问 `` +- `keycloak` 仅访问 `` + +字符集与排序规则保持与当前环境一致: + +- `utf8mb4` +- `utf8mb4_0900_ai_ci` + +说明: + +- 是“共用一个 MySQL 容器实例” +- 不是“共用同一个 database/schema” + +--- + +## Gitea 容器设计 + +### 运行目标 + +迁移后保持: + +- 域名不变 +- 数据目录不变 +- 仓库、用户、OIDC、package 等数据保留 +- 本次先不启用 Git over SSH + +### 配置原则 + +基于现有 `app.ini` 的副本进行调整,重点改动: + +- `[database] HOST` + - 从 `localhost:3306` + - 改为 `mysql:3306` + +其余核心配置尽量保持不变,例如: + +- `ROOT_URL` +- `APP_DATA_PATH` +- `repository ROOT` +- `oauth2/openid` +- `security` +- `service` +- `package/lfs/queue` + +### 端口 + +- 宿主机:`localhost:3000` +- 容器内:`3000` + +### SSH 策略 + +本次迁移默认: + +- 不启用 Git over SSH +- 不抢占宿主机 `22` +- 所有 Git 操作先走 HTTPS +- Gitea 配置中应显式关闭 SSH(例如 `DISABLE_SSH = true`;若存在 `START_SSH_SERVER`,则设为 `false`) + +后续如需恢复 Git over SSH,再单独设计第二阶段方案。 + +--- + +## Keycloak 容器设计 + +### 迁移原则 + +Keycloak 从: + +- 裸机 +- H2 +- 自身提供 HTTPS 8443 + +迁移为: + +- Docker +- MySQL +- 容器内部 HTTP +- 宿主机 nginx 负责 TLS 终止 + +### 数据库连接 + +Keycloak 容器连接: + +- Host:`mysql` +- Port:`3306` +- Database:`` + +### 对外行为保持不变 + +- 域名:`login.hangman-lab.top` +- Realm:`Hangman-Lab` +- OIDC issuer 保持不变 +- 尽量保持原有 client 与集成关系不变 + +### 端口 + +- 宿主机:`localhost:8080` +- 容器内:`8080` + +### 容器配置原则 + +通过 `.env` 注入: + +- `KC_DB=mysql` +- `KC_DB_URL_HOST=mysql` +- `KC_DB_URL_DATABASE=` +- `KC_DB_USERNAME` +- `KC_DB_PASSWORD` +- `KC_HOSTNAME=login.hangman-lab.top` +- `KC_HTTP_ENABLED=true` +- `KC_PROXY_HEADERS=xforwarded` + +说明: + +- Docker Keycloak 不再自行持有 TLS 证书 +- nginx 负责 HTTPS + +--- + +## nginx 变更原则 + +宿主机 nginx 架构保持不变。 + +### Gitea + +继续反代到: + +- `http://localhost:3000` + +如容器沿用相同宿主端口,Gitea 对应 nginx 配置可能无需变更。 + +### Keycloak + +需要把 upstream 从: + +- `https://localhost:8443` + +改为: + +- `http://localhost:8080` + +并保留以下头: + +- `Host` +- `X-Real-IP` +- `X-Forwarded-For` +- `X-Forwarded-Proto` + +--- + +## 迁移实施步骤 + +### Phase A:准备阶段 + +1. 在 `vps.git` 上安装 Docker Engine 与 Docker Compose Plugin +2. 创建目录: + - `~/git-kc` + - `~/git-kc/backups` + - `~/git-kc/mysql` + - `~/git-kc/gitea` + - `~/git-kc/keycloak/import` + - `~/git-kc/docs` +3. 编写 `compose.yaml` 与 `.env` +4. 复制 Gitea 当前配置: + - `/etc/gitea/app.ini` → `~/git-kc/gitea/app.ini` +5. 调整 Gitea 容器配置副本中的数据库地址 +6. 准备 Keycloak 容器参数 +7. 预拉取固定版本镜像 + +### Phase B:备份阶段 + +正式切换前执行迁移专用备份: + +1. 备份 `/etc/gitea/app.ini` +2. 记录并确认 `/var/lib/gitea` 当前状态 +3. 导出 Gitea 数据库:`mysqldump ` +4. 从旧 Keycloak 导出 realm / users +5. 备份 nginx 相关站点配置 + +说明: + +- 即使整机已有备份,仍建议保留迁移专用备份,以便快速回滚 + +### Phase C:切换阶段 + +建议顺序: + +1. 进入维护窗口 +2. 停止旧 Gitea:`systemctl stop gitea` +3. 停止旧 Keycloak:`systemctl stop keycloak` +4. 执行 Gitea 最终 SQL 导出 +5. 执行 Keycloak 最终 export +6. 停止旧 MySQL:`systemctl stop mysql` +7. 启动 Docker MySQL +8. 初始化 Docker MySQL: + - 创建 `` + - 创建 `` + - 创建 `gitea` 用户 + - 创建 `keycloak` 用户 +9. 导入 Gitea SQL 到 Docker MySQL +10. 启动 Docker Keycloak,并导入 realm +11. 启动 Docker Gitea +12. 修改 nginx 的 Keycloak upstream: + - 从 `https://localhost:8443` + - 改为 `http://localhost:8080` +13. 执行 `nginx -t` +14. 执行 `systemctl reload nginx` +15. 执行完整验证 + +--- + +## 验证清单 + +### Gitea + +- `https://git.hangman-lab.top/` 可访问 +- 首页正常 +- 用户可登录 +- 仓库列表正常 +- 既有仓库可见 +- Package 功能正常 +- HTTPS clone 正常 +- HTTPS push 正常 +- OIDC 登录可正常跳转到 Keycloak 并返回 + +### Keycloak + +- `https://login.hangman-lab.top/` 可访问 +- Realm `Hangman-Lab` 正常 +- OIDC discovery 正常 +- 管理后台可登录 +- 普通用户可登录 +- 原有 client 保留 +- issuer 与原环境一致 + +### MySQL + +- `` 表完整 +- `` 表完整 +- Gitea 可正常连接数据库 +- Keycloak 可正常连接数据库 +- 容器重启后数据不丢失 + +### nginx + +- HTTPS 证书正常 +- 无重定向循环 +- 无 mixed content +- 代理头工作正常 + +--- + +## 回滚方案 + +如果迁移后验证失败,按以下顺序回滚: + +1. 停止 Docker 容器: + - `gitea` + - `keycloak` + - `mysql` +2. 恢复 nginx 的 Keycloak upstream: + - 改回 `https://localhost:8443` +3. 启动旧宿主机服务: + - `systemctl start mysql` + - `systemctl start keycloak` + - `systemctl start gitea` +4. 执行 `nginx -t` +5. 执行 `systemctl reload nginx` + +回滚前提: + +- 不删除旧数据 +- 不覆盖旧宿主配置 +- Gitea 使用配置副本而不是原地覆盖 +- MySQL 使用逻辑迁移而不是直接接管 datadir + +--- + +## 主要风险点 + +### 1. Keycloak H2 → MySQL + +这是整个迁移中最高风险点。 + +控制策略: + +- 必须走 export / import +- 不采用“直接改 DB 配置试运行”的方式 + +### 2. Gitea 与 Keycloak 的 OIDC 一致性 + +控制策略: + +- 不改域名 +- 不改 realm 名称 +- 不改 issuer +- 尽量不重建 client + +### 3. Gitea 直接挂载旧目录 + +控制策略: + +- 切换时必须先彻底停止旧 Gitea +- 避免新旧环境同时访问同一数据目录 + +### 4. Keycloak 容器改为 HTTP + +控制策略: + +- 确保 nginx 传递正确代理头 +- 确保 Keycloak hostname 与 proxy 配置正确 +- 仅绑定到 `localhost` + +--- + +## 已确定的关键决策 + +- 宿主机 nginx 架构不动 +- Docker 部署根目录使用 `~/git-kc` +- Gitea 直接挂载现有数据目录 +- MySQL 采用逻辑迁移,不直接接管旧 datadir +- Keycloak 改为 MySQL,走 export / import 迁移 +- 敏感信息统一放 `.env` +- 版本先保持现状,不升级大版本 +- Git over SSH 本次先不启用;必要时后续单独设计第二阶段方案 + +--- + +## 结论 + +本方案的核心原则是: + +- 先完成 Docker 化迁移 +- 保持外部域名与功能语义不变 +- 优先控制风险与保留回滚能力 +- 不把“迁移”与“升级”混在同一轮进行 + +在此基础上,下一步可进一步补充: + +- `compose.yaml` 草案 +- `.env` 模板 +- 精确到命令级的实施 checklist diff --git a/git-kc/env.template b/git-kc/env.template new file mode 100644 index 0000000..f613978 --- /dev/null +++ b/git-kc/env.template @@ -0,0 +1,52 @@ +# Draft target path on vps.git: ~/git-kc/.env +# Fill all placeholders before first docker compose up. + +COMPOSE_PROJECT_NAME=git-kc +DOCKER_NETWORK_NAME=git-kc-net +TZ=UTC + +# Images (pin versions; do not use latest) +MYSQL_IMAGE=mysql:8.0.45 +GITEA_IMAGE=gitea/gitea:1.22.4 +KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:26.0.6 + +# Host bindings +GITEA_HOST_BIND=localhost +GITEA_HTTP_PORT=3000 +KEYCLOAK_HOST_BIND=localhost +KEYCLOAK_HTTP_PORT=8080 + +# Gitea container runtime user +# Replace with: id -u git / id -g git on vps.git +GITEA_UID=1001 +GITEA_GID=1001 + +# MySQL root +MYSQL_ROOT_PASSWORD=REPLACE_WITH_STRONG_ROOT_PASSWORD + +# Planned logical DB layout inside the single MySQL instance +MYSQL_GITEA_DATABASE= +MYSQL_GITEA_USER=gitea +MYSQL_GITEA_PASSWORD=REPLACE_WITH_STRONG_GITEA_DB_PASSWORD +MYSQL_KEYCLOAK_DATABASE= +MYSQL_KEYCLOAK_USER=keycloak +MYSQL_KEYCLOAK_PASSWORD=REPLACE_WITH_STRONG_KEYCLOAK_DB_PASSWORD + +# Keycloak DB connection +KC_DB_URL_HOST=mysql +KC_DB_URL_PORT=3306 +KC_DB_URL_DATABASE= +KC_DB_USERNAME=keycloak +KC_DB_PASSWORD=REPLACE_WITH_STRONG_KEYCLOAK_DB_PASSWORD + +# Keycloak external hostname +KC_HOSTNAME=login.hangman-lab.top + +# Bootstrap admin for first start on empty +# Keep these temporary and rotate/remove after cutover verification if desired. +KC_BOOTSTRAP_ADMIN_USERNAME=admin +KC_BOOTSTRAP_ADMIN_PASSWORD=REPLACE_WITH_TEMP_BOOTSTRAP_ADMIN_PASSWORD + +# Helpful references (not consumed directly by compose, but useful in ops docs) +GITEA_EXTERNAL_URL=https://git.hangman-lab.top/ +KEYCLOAK_EXTERNAL_URL=https://login.hangman-lab.top/ diff --git a/git-kc/gitea/app.ini.redacted b/git-kc/gitea/app.ini.redacted new file mode 100644 index 0000000..db1180c --- /dev/null +++ b/git-kc/gitea/app.ini.redacted @@ -0,0 +1,89 @@ +APP_NAME = Hangman-Lab +RUN_USER = git +WORK_PATH = /var/lib/gitea/ +RUN_MODE = prod + +[database] +DB_TYPE = mysql +HOST = mysql:3306 +NAME = +USER = gitea +PASSWD = +SCHEMA = +SSL_MODE = disable +PATH = /var/lib/gitea/data/gitea.db +LOG_SQL = false + +[repository] +ROOT = /var/lib/gitea/data/gitea-repositories +ENABLE_PUSH_CREATE_USER = true +ENABLE_PUSH_CREATE_ORG = true + +[server] +#PROTOCOL = https +#CERT_FILE = /etc/letsencrypt/live/git.hangman-lab.top/fullchain.pem +#KEY_FILE = /etc/letsencrypt/live/git.hangman-lab.top/privkey.pem +SSH_DOMAIN = git.hangman-lab.top +DOMAIN = git.hangman-lab.top +HTTP_PORT = 3000 +ROOT_URL = https://git.hangman-lab.top/ +APP_DATA_PATH = /var/lib/gitea/data +DISABLE_SSH = true +SSH_PORT = 22 +LFS_START_SERVER = true +LFS_JWT_SECRET = +OFFLINE_MODE = true + +START_SSH_SERVER = false +[lfs] +PATH = /var/lib/gitea/data/lfs + +[mailer] +ENABLED = false + +[service] +REGISTER_EMAIL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DISABLE_REGISTRATION = true +ALLOW_ONLY_EXTERNAL_REGISTRATION = false +ENABLE_CAPTCHA = false +REQUIRE_SIGNIN_VIEW = false +DEFAULT_KEEP_EMAIL_PRIVATE = false +DEFAULT_ALLOW_CREATE_ORGANIZATION = true +DEFAULT_ENABLE_TIMETRACKING = true +NO_REPLY_ADDRESS = noreply.localhost + +[openid] +ENABLE_OPENID_SIGNIN = false +ENABLE_OPENID_SIGNUP = false + +[cron.update_checker] +ENABLED = false + +[session] +PROVIDER = file + +[log] +MODE = file +LEVEL = info +ROOT_PATH = /var/lib/gitea/log + +[repository.pull-request] +DEFAULT_MERGE_STYLE = merge + +[repository.signing] +DEFAULT_TRUST_MODEL = committer + +[security] +INSTALL_LOCK = true +INTERNAL_TOKEN = +PASSWORD_HASH_ALGO = pbkdf2 + +[oauth2] +JWT_SECRET = +ENABLE_OPENID_CONNECT = true +ENABLED = true +[openid] +ENABLE = true +[auth] +REQUIRE_EXTERNAL_LOGIN = true diff --git a/git-kc/mysql/init/01-init-databases.sh b/git-kc/mysql/init/01-init-databases.sh new file mode 100755 index 0000000..ab0f4c9 --- /dev/null +++ b/git-kc/mysql/init/01-init-databases.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Draft target path on vps.git: ~/git-kc/mysql/init/01-init-databases.sh +# Runs inside the official MySQL container on first initialization only +# (i.e. only when /var/lib/mysql is empty). + +set -Eeuo pipefail + +required_vars=( + MYSQL_ROOT_PASSWORD + MYSQL_GITEA_DATABASE + MYSQL_GITEA_USER + MYSQL_GITEA_PASSWORD + MYSQL_KEYCLOAK_DATABASE + MYSQL_KEYCLOAK_USER + MYSQL_KEYCLOAK_PASSWORD +) + +for var in "${required_vars[@]}"; do + if [[ -z "${!var:-}" ]]; then + echo "[ERROR] Required environment variable is missing: $var" >&2 + exit 1 + fi +done + +sql_escape() { + printf '%s' "$1" | sed "s/'/''/g" +} + +MYSQL_ROOT_PASSWORD_SQL=$(sql_escape "${MYSQL_ROOT_PASSWORD}") +MYSQL_GITEA_PASSWORD_SQL=$(sql_escape "${MYSQL_GITEA_PASSWORD}") +MYSQL_KEYCLOAK_PASSWORD_SQL=$(sql_escape "${MYSQL_KEYCLOAK_PASSWORD}") + +cat < 记录人:晨曦 / Orion +> 范围:今天在 `vps.git` 上执行的排查、迁移、升级与清理操作 +> 说明:本日志按实际执行顺序整理,便于后续审计、回顾与回滚分析。 + +--- + +## 1. 迁移前探测与现状确认 + +对 `vps.git` 做了只读探测,确认了原始部署情况: + +- 宿主机为 Ubuntu 24.04.1 LTS +- 原先运行的裸机服务: + - `nginx` + - `gitea.service` + - `keycloak.service` + - `mysql.service` +- 原始端口/流量结构: + - `nginx` 对外提供 80/443 + - `gitea` 裸机监听 3000,由 nginx 反代 + - `keycloak` 裸机监听 8443,由 nginx 反代 + - `mysql` 裸机监听 localhost:3306 +- 原始数据位置: + - Gitea 配置:`/etc/gitea/app.ini` + - Gitea 数据:`/var/lib/gitea` + - Keycloak 配置:`/opt/keycloak/conf/keycloak.conf` + - Keycloak H2 数据:`/opt/keycloak/data/h2` + - MySQL 数据:`/var/lib/mysql` +- 原始版本: + - Gitea `1.22.4` + - Keycloak `26.0.6` + - MySQL `8.0.45` + +同时确认: + +- Git over SSH 当前无人使用 +- Gitea 与 Keycloak 均通过域名正常对外暴露 +- Keycloak 的 `Hangman-Lab` realm 为后续重点保留对象 + +--- + +## 2. 迁移方案设计与落盘 + +在 workspace 中编写并落盘了以下迁移材料: + +- `VPS_GIT_DOCKER_MIGRATION_PLAN.md` +- `VPS_GIT_DOCKER_COMPOSE_DRAFT.yaml` +- `VPS_GIT_DOCKER_ENV_TEMPLATE.env` +- `VPS_GIT_DOCKER_MIGRATION_CHECKLIST.md` +- `VPS_GIT_DOCKER_MYSQL_INIT_DRAFT.sh` + +这些材料覆盖了: + +- Docker 化目标架构 +- compose 草案 +- `.env` 模板 +- MySQL 初始化逻辑 +- 实施顺序与回滚顺序 +- 验证点与风险点 + +--- + +## 3. 在 vps.git 上安装 Docker 环境 + +执行了 Docker 环境准备: + +- 安装 Docker Engine +- 安装 Docker Compose Plugin +- 验证 `docker --version` 与 `docker compose version` + +然后创建了部署目录: + +- `/root/git-kc` +- `/root/git-kc/backups` +- `/root/git-kc/docs` +- `/root/git-kc/gitea` +- `/root/git-kc/keycloak/import` +- `/root/git-kc/mysql/data` +- `/root/git-kc/mysql/init` + +并把 workspace 里的草案文件同步到 `vps.git`。 + +--- + +## 4. 生成部署配置与准备 Gitea 容器配置 + +在 `vps.git` 上: + +- 根据模板生成了 `/root/git-kc/.env` +- 为 MySQL root、Gitea DB、Keycloak DB、Keycloak bootstrap admin 生成了随机密码 +- 将宿主机 `git` 用户的真实 UID/GID 写入 `.env` + +复制并调整了 Gitea 配置: + +- `/etc/gitea/app.ini` → `/root/git-kc/gitea/app.ini` +- 将 Gitea 的数据库地址改为:`mysql:3306` +- 将 Gitea DB 密码改为 Docker MySQL 内的新密码 +- 明确关闭了第一阶段的 Git over SSH: + - `DISABLE_SSH = true` + - `START_SSH_SERVER = false` + +同时对 compose 做了兼容修正,使 Gitea 尽量贴近旧裸机布局: + +- `HOME=/home/git` +- `GITEA_WORK_DIR=/var/lib/gitea` +- `GITEA_CUSTOM=/var/lib/gitea/custom` +- 额外挂载 `/home/git:/home/git` +- 显式使用宿主机 `git` 用户 UID/GID 运行容器 + +--- + +## 5. 迁移前备份 + +执行了迁移专用备份: + +- 备份了 Gitea 配置 +- 备份了 nginx 相关站点配置 +- 导出了 Gitea MySQL 数据库 `` +- 停止旧 Keycloak 后,导出了 Keycloak realm / users 到目录文件 + +Keycloak 导出文件位于: + +- `/root/git-kc/keycloak/import/` + +同时保留了升级前的 SQL dump 与 compose / env / app.ini 备份。 + +--- + +## 6. 启动 Docker MySQL 并迁移 Gitea 数据库 + +执行了 MySQL Docker 化: + +- 启动 Docker MySQL 容器 +- 首次启动时通过 `01-init-databases.sh` 自动创建: + - `` + - `` + - `gitea@'%'` + - `keycloak@'%'` +- 将原始 Gitea 数据库导入 Docker MySQL + +导入后核对了核心数据: + +- users = 8 +- repos = 72 +- - external_login_user = 7 + +确认 Gitea 业务主数据迁移成功。 + +--- + +## 7. Keycloak H2 → MySQL 迁移与导入问题修复 + +### 7.1 初次导入失败原因 + +Keycloak 首次导入失败,定位到: + +- `Hangman-Lab` realm 中存在 JS policy +- 报错:`Script upload is disabled` + +进一步确认: + +- 不是整个 `authorizationSettings` 无法导入 +- 而是其中 `type = "js"` 的 policy 被新版本 Keycloak 导入路径拒绝 + +具体涉及的 client: + +- `gitea` +- `iredmail` +- `youtrack-dev` + +脚本内容实际都是同一段默认放行模板: + +```js +// by default, grants any permission associated with this policy +$evaluation.grant(); +``` + +### 7.2 最小修补 + +经确认后,执行了最小修补方案: + +- 移除 `gitea` client 的 `authorizationSettings` +- 彻底移除已废弃 client: + - `iredmail` + - `youtrack-dev` +- 同时移除它们的残留引用: + - client role definitions + - service-account 用户 + - 相关 client role 映射 + +修补仅发生在迁移使用的导出文件中: + +- `/root/git-kc/keycloak/import/Hangman-Lab-realm.json` + +### 7.3 导入成功 + +随后: + +- 重建 `` +- 重新导入 `master` / `Hangman-Lab` / `Dialectic` +- 启动 Docker Keycloak 成功 +- 确认 discovery 文档正常,issuer 正常 + +并在导入完成后: + +- 去掉了 compose 中的一次性 `--import-realm` +- 去掉了临时加入的 `KC_FEATURES=scripts` + +确保后续 Keycloak 重启不会重复导入。 + +--- + +## 8. Gitea Docker 切换与启动修复 + +Gitea 初次容器启动失败,原因是: + +- 容器以 root 身份运行 +- Gitea 拒绝以 root 启动 + +修复措施: + +- 在 compose 中显式设置: + - `user: "${GITEA_UID}:${GITEA_GID}"` + +调整后: + +- Gitea 容器成功启动 +- 健康检查正常 +- 通过 nginx 暴露的 Gitea 页面可访问 +- Gitea 的 OIDC 登录入口可正确跳转至 Keycloak + +--- + +## 9. nginx 切换 + +保留宿主机 nginx 架构不变,仅修改 Keycloak upstream: + +- 从:`https://localhost:8443` +- 改为:`http://localhost:8080` + +操作后: + +- 执行 `nginx -t` +- reload nginx +- 验证 `login.hangman-lab.top` discovery 正常 +- 验证 `git.hangman-lab.top` 仍正常 + +--- + +## 10. 裸机部署清理 + +在确认 Docker 版跑通后,删除了旧裸机部署: + +### 已删除 + +- Gitea 裸机: + - `gitea.service` + - `/usr/local/bin/gitea` + - `/etc/gitea` +- Keycloak 裸机: + - `keycloak.service` + - `/etc/default/keycloak` + - `/opt/keycloak` +- MySQL 裸机: + - `mysql.service` + - 旧 MySQL 包 + - `/var/lib/mysql` + - `/etc/mysql` + +### 保留 + +保留了以下正在被 Docker 使用的宿主机路径: + +- `/var/lib/gitea` +- `/home/git` + +原因: + +- 这两者属于 Docker Gitea 当前的 live 挂载目录,不是旧垃圾。 + +--- + +## 11. 升级到最新版本 + +在 Docker 迁移稳定后,继续升级到了当时的最新版本,并保持固定版本写死: + +### Gitea + +- 从:`1.22.4` +- 升级到:`1.25.5` + +验证: + +- 容器健康正常 +- 页面访问正常 +- OIDC 跳转正常 + +### Keycloak + +- 从:`26.0.6` +- 升级到:`26.5.6` + +Keycloak 升级期间自动执行了数据库 model migration,日志中可见: + +- `Hangman-Lab` migrated to 26.1.0 / 26.2.0 / 26.3.0 / 26.4.0 / 26.4.3 + +升级完成后验证: + +- 容器健康正常 +- discovery 正常 +- issuer 正常 +- Gitea OIDC 跳转正常 + +升级前备份保存在: + +- `/root/git-kc/backups/upgrade-2026-03-20-151958/` + +包含: + +- `.sql` +- `.sql` +- `compose.yaml.bak` +- `.env.bak` +- `app.ini.bak` + +--- + +## 12. 磁盘清理与空间回收 + +后续又做了宿主机空间清理: + +### journald + +- 将 `/var/log/journal` 限制到 `300MB` +- 实际回收后 journald 占用约 `252.9MB` + +### Docker 旧镜像 + +删除了未使用的旧镜像: + +- `gitea/gitea:1.22.4` +- `quay.io/keycloak/keycloak:26.0.6` + +### APT + +- 按要求保留了 apt 元数据和缓存 +- 同时检查了 `apt-get -s autoremove` +- 当前无明显可自动移除的孤儿包 + +--- + +## 13. 当前最终状态(截至日志记录时) + +Docker 正在运行的服务: + +- `mysql:8.0.45` +- `gitea/gitea:1.25.5` +- `quay.io/keycloak/keycloak:26.5.6` + +验证通过项: + +- `https://git.hangman-lab.top/` 可访问 +- `https://login.hangman-lab.top/` 可访问 +- `https://login.hangman-lab.top/realms/Hangman-Lab/.well-known/openid-configuration` 可访问 +- Gitea 的 OIDC 登录入口会正确跳转至 Keycloak + +当时磁盘空间大致为: + +- 总盘:`23G` +- 已用:`12G` +- 可用:`11G` +- 使用率:约 `53%` + +--- + +## 14. 本次迁移中对业务配置做过的显式变更 + +这是后续最需要知道的一部分: + +1. **Gitea 第一阶段明确关闭了 Git over SSH** + - `DISABLE_SSH = true` + - `START_SSH_SERVER = false` + +2. **Keycloak 导入时对 `Hangman-Lab` realm 做了最小修补** + - 移除了 `gitea` 的 `authorizationSettings` + - 删除了已废弃 client: + - `iredmail` + - `youtrack-dev` + - 同时删除了它们关联的 service-account / client role 引用 + +3. **nginx Keycloak upstream 已改为宿主机本地 HTTP 反代** + - `http://localhost:8080` + +--- + +## 15. 后续建议 + +- 用浏览器人工完成一次 Gitea → Keycloak SSO 登录验收 +- 用实际仓库完成一次 HTTPS clone / push 验收 +- 如未来要恢复 Git over SSH,再单独设计第二阶段方案 +- 如要继续精简磁盘,可再评估 apt 元数据清理,但本次按要求保留 + +--- + +## 结论 + +今天在 `vps.git` 上完成了: + +- 裸机 Gitea / Keycloak / MySQL → Docker 迁移 +- Keycloak H2 → MySQL 迁移 +- Gitea / Keycloak 升级到当时最新版本 +- 旧裸机部署清理 +- journald 限额与旧镜像清理 + +当前服务已切换为 Docker 运行,并完成了基础联通验证。 \ No newline at end of file