diff --git a/.dockerignore b/.dockerignore index d776ca4..c8fedbd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ * !Dockerfile !requirements.txt +!LICENSE !index.html !style.css !app.js diff --git a/.env.example b/.env.example index 7da9f7d..0c0603c 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,8 @@ SIMKEEP_SECURE_COOKIES=0 SIMKEEP_DB=data/simkeep.db # Docker Compose 公网端口;改端口时同步修改 SIMKEEP_PUBLIC_URL。 SIMKEEP_PORT=5180 +# Docker Compose 镜像;使用预构建镜像时改为 ghcr.io/rest-rain/simkeep:latest。 +SIMKEEP_IMAGE=simkeep:local # 可选的公共默认通知服务;用户也可在网页单独配置自己的服务。 # 从 BotFather 获取 Token。可选用户名不包含 @;留空会自动查询。 diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..32be096 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,73 @@ +name: Docker image + +on: + push: + branches: [main] + tags: ["v[0-9]*"] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build and publish + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + packages: write + steps: + - name: Check out source + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + + - name: Set up QEMU + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 + with: + platforms: arm64 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate image tags and labels + id: metadata + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 + with: + images: ghcr.io/${{ github.repository }} + flavor: latest=false + tags: | + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}},enable=${{ !contains(github.ref_name, '-') }} + type=sha + labels: | + org.opencontainers.image.licenses=MIT + + - name: Build and publish image + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index 36e7ccd..185520b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN python scripts/build.py FROM python:3.12-slim AS runtime WORKDIR /app ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 SIMKEEP_DB=/app/data/simkeep.db SIMKEEP_STATIC_DIR=/app/dist -COPY requirements.txt . +COPY requirements.txt LICENSE ./ RUN pip install --no-cache-dir -r requirements.txt COPY server ./server COPY scripts/backup.py scripts/restore.py ./scripts/ diff --git a/README.md b/README.md index 9a65777..c855c01 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ SIMKEEP 是一个可自行部署的 SIM / eSIM 管理平台。把开通日期、 ## Docker 部署 -需要 Docker Engine 和 Docker Compose 插件。镜像会自动构建前端,服务器无需安装 Node.js 或 Python。 +需要 Docker Engine 和 Docker Compose 插件。可直接使用 GHCR 镜像,支持 `amd64` / `arm64`,服务器无需安装 Node.js 或 Python。 下载仓库并进入项目目录后执行: @@ -67,17 +67,27 @@ cp .env.example .env chmod 600 .env ``` -编辑 `.env`,把 `SIMKEEP_PUBLIC_URL` 改成自己的访问地址,例如 `http://your-server:5180`。本机试用可保留 `http://localhost:5180`。 +编辑 `.env`,把 `SIMKEEP_PUBLIC_URL` 改成自己的访问地址,例如 `http://your-server:5180`,并设置镜像: + +```dotenv +SIMKEEP_IMAGE=ghcr.io/rest-rain/simkeep:latest +``` + +本机试用可保留 `http://localhost:5180`。`latest` 对应 `main` 的最新构建;需要固定版本时使用对应版本标签。 ```bash -docker compose up -d --build --wait +docker compose pull web +docker compose up -d --no-build --wait web docker compose ps ``` 打开 `http://localhost:5180` 或设置的服务器地址,点击“创建账号”。没有预置管理员、初始密码或演示资料。 +也可从源码构建:在 `.env` 中设置 `SIMKEEP_IMAGE=simkeep:local`,然后执行 `docker compose up -d --build --wait`。 + | 配置项 | 默认值 | 用途 | | --- | --- | --- | +| `SIMKEEP_IMAGE` | `simkeep:local` | 镜像名称;使用 GHCR 镜像时填写上面的地址 | | `SIMKEEP_PUBLIC_URL` | `http://localhost:5180` | 网页访问地址和提醒中的链接 | | `SIMKEEP_PORT` | `5180` | 宿主机端口;修改时同步调整访问地址 | | `SIMKEEP_BIND_ADDRESS` | `0.0.0.0` | 宿主机监听地址 | @@ -85,7 +95,7 @@ docker compose ps 数据保存于命名卷 `simkeep_simkeep-data` 中的 `/app/data/simkeep.db`。容器重启和镜像更新会保留数据。使用单个 Web worker 和一个提醒调度进程。 -详见 [Docker 部署与恢复说明](docs/DOCKER.md)。 +GitHub Actions 在推送 `main` 或 `v1.0.0` 这样的版本标签时自动构建并发布镜像,也支持手动触发。首次发布后,仓库维护者需将 GHCR 包设为 Public,其他人才能匿名拉取。详见 [Docker 部署、自动构建与恢复说明](docs/DOCKER.md)。 ## 通知配置 @@ -126,16 +136,19 @@ docker compose cp web:/app/data/backups ./docker-backups 备份包含账号、卡片、历史和个人通知密钥,应放在受保护的位置。不要只复制正在使用的主数据库文件而遗漏 WAL。 -更新代码和镜像: +使用 GHCR 镜像时,拉取更新并重建容器: ```bash -docker compose up -d --build --wait +docker compose pull web +docker compose up -d --no-build --wait web ``` +使用源码构建时,更新代码后执行 `docker compose up -d --build --wait`。 + 修改 `.env` 后,需要重建容器加载新环境变量: ```bash -docker compose up -d --force-recreate --wait web +docker compose up -d --no-build --force-recreate --wait web ``` `docker compose restart` 不会加载修改后的 `.env`。不要执行 `docker compose down -v`,它会删除数据卷。 diff --git a/docs/DOCKER.md b/docs/DOCKER.md index 51d3017..f92274f 100644 --- a/docs/DOCKER.md +++ b/docs/DOCKER.md @@ -1,10 +1,10 @@ # Docker Compose 部署 -需要 Docker Engine 与 Docker Compose 插件。镜像构建会自动打包前端,不需要宿主机安装 Node.js、npm 或 Python。应用使用单个 Uvicorn worker,后台续期提醒与 Web 同时启动。 +需要 Docker Engine 与 Docker Compose 插件。预构建镜像包含前端与后端,支持 `linux/amd64` 和 `linux/arm64`,不需要宿主机安装 Node.js、npm 或 Python。应用使用单个 Uvicorn worker,后台续期提醒与 Web 同时启动。 ## 在新服务器部署 -进入包含 Dockerfile 和 compose.yml 的 simkeep 目录,准备配置: +进入仓库目录,准备配置;使用预构建镜像时只需 `compose.yml`、`.env.example` 和自己的 `.env`: ```bash test -f .env || cp .env.example .env @@ -13,8 +13,15 @@ chmod 600 .env 编辑 `.env`,将 `SIMKEEP_PUBLIC_URL` 改为实际访问地址,例如 `http://服务器IP:5180`。`SIMKEEP_PORT` 控制宿主机端口,默认 5180;改端口时同步修改公网地址。保持 HTTP 时使用 `SIMKEEP_SECURE_COOKIES=0`。每个用户登录网页的“通知设置”,点击“配置 Telegram / 配置邮件”保存自己的凭据;不需要修改 `.env` 或重启。`.env` 中的通知凭据仅作为可选公共默认服务。 +在 `.env` 中将镜像改为: + +```dotenv +SIMKEEP_IMAGE=ghcr.io/rest-rain/simkeep:latest +``` + ```bash -docker compose up -d --build --wait +docker compose pull web +docker compose up -d --no-build --wait web docker compose ps ``` @@ -22,18 +29,51 @@ docker compose ps 默认 Compose 项目名为 `simkeep`,持久化卷为 `simkeep_simkeep-data`,数据库位于容器 `/app/data/simkeep.db`。保持项目名即可复用同一个卷。容器重启、镜像更新及普通 `docker compose down` 都会保留卷;`docker compose down -v` 会删除数据卷。 -## 更新、配置与日志 +### 从源码构建 -更新代码并重新构建镜像: +需要完整仓库。在 `.env` 中设置 `SIMKEEP_IMAGE=simkeep:local`,然后执行: ```bash docker compose up -d --build --wait ``` +构建时自动打包前端,宿主机同样无需安装应用依赖。 + +## GitHub 自动构建镜像 + +工作流位于 [docker-image.yml](../.github/workflows/docker-image.yml),镜像发布到 `ghcr.io/rest-rain/simkeep`,同时构建 `amd64` 与 `arm64`。发布使用 GitHub 自动提供的 `GITHUB_TOKEN`,无需配置 Docker Hub 账号或额外的访问令牌。 + +| 触发方式 | 发布标签 | +| --- | --- | +| 推送 `main` | `latest`、`main`、`sha-提交短编号` | +| 推送版本标签,例如 `v1.2.3` | `1.2.3`、`1.2`、`sha-提交短编号` | +| 推送预发布标签,例如 `v1.2.3-rc.1` | `1.2.3-rc.1`、`sha-提交短编号` | +| 在 Actions 中手动运行 | 所选分支或版本对应的标签 | +| 向 `main` 提交 Pull Request | 仅验证构建,不发布 | + +`latest` 只随 `main` 更新。使用正式版本标签部署时,例如设置 `SIMKEEP_IMAGE=ghcr.io/rest-rain/simkeep:1.2.3`,可避免跟随开发分支更新。 + +在仓库的 **Actions → Docker image** 查看构建结果,或点击 **Run workflow** 手动运行。仓库需启用 GitHub Actions;工作流已经声明 `contents: read` 和 `packages: write` 权限。 + +**首次发布后**,GHCR 包默认为 Private,即使源码仓库是公开的。维护者打开 [SIMKEEP 的包设置](https://github.com/users/rest-rain/packages/container/simkeep/settings),在 **Change visibility** 中改为 **Public**,即可匿名拉取。构建成功前包设置页面可能尚不存在。拉取提示 `denied` 时,先确认工作流已成功及包的可见性。 + +构建只读取 `.dockerignore` 允许的应用文件,不传入服务器 `.env`、数据库或通知密钥。镜像内附带 MIT LICENSE。 + +## 更新、配置与日志 + +使用预构建镜像时: + +```bash +docker compose pull web +docker compose up -d --no-build --wait web +``` + +使用源码镜像时,更新代码后执行 `docker compose up -d --build --wait`。更新镜像会重建容器,数据仍保存在原命名卷中。 + 修改 `.env` 后重建容器加载新配置: ```bash -docker compose up -d --force-recreate --wait web +docker compose up -d --no-build --force-recreate --wait web ``` 仅执行 `docker compose restart` 不会加载修改后的环境配置。日常查看或重启: @@ -64,7 +104,7 @@ docker compose cp web:/app/data/backups ./docker-backups ```bash docker compose stop web docker compose run --rm --no-deps -T --entrypoint python web scripts/restore.py --replace < restore.db -docker compose up -d --wait +docker compose up -d --no-build --wait web ``` 恢复容器以应用用户读取标准输入,不需要 root 或修改宿主机备份权限。它检查数据库完整性与 SIMKEEP 表结构,临时生成恢复库后替换;未传 --replace 时会拒绝覆盖现有库。恢复容器只执行数据库操作,不启动提醒进程。使用 `docker compose -p 其他项目名` 时,以上所有命令也须带相同项目名。