diff --git a/.woodpecker/dev-image-api.yml b/.woodpecker/dev-image-api.yml new file mode 100644 index 0000000..a83978f --- /dev/null +++ b/.woodpecker/dev-image-api.yml @@ -0,0 +1,51 @@ +# 主后端(含内嵌前端)的开发镜像:提交信息带 [CI],并且这次推送改到了主后端用到的代码时才构建。 +# 推送 webdrop-api:dev(开发机器的 podman auto-update 跟踪它)和 webdrop-api:dev-<提交> +when: + - event: push + branch: [master, main] + evaluate: 'CI_COMMIT_MESSAGE contains "[CI]"' + path: + include: + - 'web/**' + - 'packages/core/**' + - 'server/Containerfile' + - 'server/Directory.Build.props' + - 'server/src/WebDrop.Api/**' + - 'server/src/WebDrop.Core/**' + - 'server/src/WebDrop.Storage/**' + - 'server/src/WebDrop.Frontend/**' + - 'server/src/WebDrop.FileProtocol/**' + - 'server/src/WebDrop.Sync/**' + - 'scripts/ci-publish-image.sh' + - '.woodpecker/dev-image-api.yml' + - event: manual + +labels: + ci: domain-linux-native + platform: linux/amd64 + +clone: + - name: git + image: woodpeckerci/plugin-git:2.9.3 + settings: + remote: + from_secret: gitea_clone_webdrop + lfs: false + +depends_on: + - ci + - web-build + +concurrency: 1 + +steps: + publish-dev-image: + image: bash + environment: + ZOT_USERNAME: justme + ZOT_SECRET: + from_secret: zot_secret + ZOT_REGISTRY: registry.oeo.one + commands: + - sha="${CI_COMMIT_SHA:0:7}" + - bash scripts/ci-publish-image.sh webdrop-api server/Containerfile "dev-$sha" dev diff --git a/.woodpecker/dev-image-files.yml b/.woodpecker/dev-image-files.yml new file mode 100644 index 0000000..d791dac --- /dev/null +++ b/.woodpecker/dev-image-files.yml @@ -0,0 +1,46 @@ +# 文件服务(fileAPI)的开发镜像:提交信息带 [CI],并且这次推送改到了文件服务用到的代码时才构建。 +# 推送 webdrop-files:dev(放文件的机器上的 podman auto-update 跟踪它)和 webdrop-files:dev-<提交> +when: + - event: push + branch: [master, main] + evaluate: 'CI_COMMIT_MESSAGE contains "[CI]"' + path: + include: + - 'server/Containerfile.files' + - 'server/Directory.Build.props' + - 'server/src/WebDrop.FileApi/**' + - 'server/src/WebDrop.FileProtocol/**' + - 'server/src/WebDrop.Sync/**' + - 'scripts/ci-publish-image.sh' + - '.woodpecker/dev-image-files.yml' + - event: manual + +labels: + ci: domain-linux-native + platform: linux/amd64 + +clone: + - name: git + image: woodpeckerci/plugin-git:2.9.3 + settings: + remote: + from_secret: gitea_clone_webdrop + lfs: false + +# 文件服务不带前端,不用等前端构建 +depends_on: + - ci + +concurrency: 1 + +steps: + publish-dev-image: + image: bash + environment: + ZOT_USERNAME: justme + ZOT_SECRET: + from_secret: zot_secret + ZOT_REGISTRY: registry.oeo.one + commands: + - sha="${CI_COMMIT_SHA:0:7}" + - bash scripts/ci-publish-image.sh webdrop-files server/Containerfile.files "dev-$sha" dev diff --git a/.woodpecker/dev-image.yml b/.woodpecker/dev-image.yml deleted file mode 100644 index 826affd..0000000 --- a/.woodpecker/dev-image.yml +++ /dev/null @@ -1,51 +0,0 @@ -when: - - event: push - branch: master - evaluate: 'CI_COMMIT_MESSAGE contains "[CI]"' - - event: push - branch: main - evaluate: 'CI_COMMIT_MESSAGE contains "[CI]"' - -labels: - ci: domain-linux-native - platform: linux/amd64 - -clone: - - name: git - image: woodpeckerci/plugin-git:2.9.3 - settings: - remote: - from_secret: gitea_clone_webdrop - lfs: false - -depends_on: - - ci - - web-build - -concurrency: 1 - -steps: - publish-dev-image: - image: bash - environment: - ZOT_USERNAME: justme - ZOT_SECRET: - from_secret: zot_secret - ZOT_REGISTRY: registry.oeo.one - commands: - - set -euo pipefail - - ': "${WOODPECKER_BACKEND_LOCAL_TEMP_DIR:=$HOME/woodpecker/work}"' - - buildah_store="$(dirname "$WOODPECKER_BACKEND_LOCAL_TEMP_DIR")/buildah-cache/webdrop" - - mkdir -p "$buildah_store/storage" "$buildah_store/run" - - 'buildah() { command buildah --root "$buildah_store/storage" --runroot "$buildah_store/run" "$@"; }' - - printf '%s' "$ZOT_SECRET" | buildah login --tls-verify=true --username "$ZOT_USERNAME" --password-stdin "$ZOT_REGISTRY" - - trap 'buildah logout "$ZOT_REGISTRY" >/dev/null 2>&1 || true' EXIT - - sha="${CI_COMMIT_SHA:0:7}" - - build_tag="dev-$sha" - - image="$ZOT_REGISTRY/justme/webdrop-api:dev-$sha" - - dev_image="$ZOT_REGISTRY/justme/webdrop-api:dev" - - build_time="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - - buildah bud --layers --network host --format oci --platform linux/amd64 --build-arg "WEBDROP_FRONTEND_COMMIT=$CI_COMMIT_SHA" --build-arg "WEBDROP_FRONTEND_TAG=$build_tag" --build-arg "WEBDROP_BACKEND_COMMIT=$CI_COMMIT_SHA" --build-arg "WEBDROP_BACKEND_TAG=$build_tag" --build-arg "WEBDROP_BUILD_TIME=$build_time" --tag "$image" --file server/Containerfile . - - buildah tag "$image" "$dev_image" - - buildah push "$image" "docker://$image" - - buildah push "$dev_image" "docker://$dev_image" diff --git a/.woodpecker/release-backend.yml b/.woodpecker/release.yml similarity index 50% rename from .woodpecker/release-backend.yml rename to .woodpecker/release.yml index 1c707b1..97a4f64 100644 --- a/.woodpecker/release-backend.yml +++ b/.woodpecker/release.yml @@ -1,3 +1,6 @@ +# 发布:打 vMAJOR.MINOR.PATCH tag(或手动运行并传 RELEASE_TAG)。 +# 一个 tag 同时发布两个镜像:webdrop-api: / :latest 和 webdrop-files: / :latest。 +# 两边共用票据格式和文件协议,版本号一致最不容易配错;各机器只会拉自己用的那个镜像。 when: - event: tag ref: refs/tags/v* @@ -37,29 +40,26 @@ steps: - NUGET_PACKAGES="$HOME/.cache/woodpecker/webdrop/nuget" dotnet restore server/WebDrop.slnx - NUGET_PACKAGES="$HOME/.cache/woodpecker/webdrop/nuget" dotnet test server/WebDrop.slnx --configuration Release --verbosity minimal --no-restore - publish-backend: + publish-api: image: bash + depends_on: [release-checks] environment: ZOT_USERNAME: justme ZOT_SECRET: from_secret: zot_secret ZOT_REGISTRY: registry.oeo.one commands: - - set -euo pipefail - release_tag="${CI_COMMIT_TAG:-${RELEASE_TAG:-}}" - - test -n "$release_tag" - - ': "${WOODPECKER_BACKEND_LOCAL_TEMP_DIR:=$HOME/woodpecker/work}"' - - buildah_store="$(dirname "$WOODPECKER_BACKEND_LOCAL_TEMP_DIR")/buildah-cache/webdrop" - - mkdir -p "$buildah_store/storage" "$buildah_store/run" - - 'buildah() { command buildah --root "$buildah_store/storage" --runroot "$buildah_store/run" "$@"; }' - - printf '%s' "$ZOT_SECRET" | buildah login --tls-verify=true --username "$ZOT_USERNAME" --password-stdin "$ZOT_REGISTRY" - - trap 'buildah logout "$ZOT_REGISTRY" >/dev/null 2>&1 || true' EXIT - - image="$ZOT_REGISTRY/justme/webdrop-api:$release_tag" - - latest_image="$ZOT_REGISTRY/justme/webdrop-api:latest" - - build_commit="$(git rev-parse HEAD)" - - build_time="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - - buildah bud --layers --network host --format oci --platform linux/amd64 --build-arg "WEBDROP_FRONTEND_COMMIT=$build_commit" --build-arg "WEBDROP_FRONTEND_TAG=$release_tag" --build-arg "WEBDROP_BACKEND_COMMIT=$build_commit" --build-arg "WEBDROP_BACKEND_TAG=$release_tag" --build-arg "WEBDROP_BUILD_TIME=$build_time" --tag "$image" --file server/Containerfile . - - buildah tag "$image" "$latest_image" - - buildah push "$image" "docker://$image" - - buildah push "$latest_image" "docker://$latest_image" + - bash scripts/ci-publish-image.sh webdrop-api server/Containerfile "$release_tag" latest + publish-files: + image: bash + depends_on: [release-checks] + environment: + ZOT_USERNAME: justme + ZOT_SECRET: + from_secret: zot_secret + ZOT_REGISTRY: registry.oeo.one + commands: + - release_tag="${CI_COMMIT_TAG:-${RELEASE_TAG:-}}" + - bash scripts/ci-publish-image.sh webdrop-files server/Containerfile.files "$release_tag" latest diff --git a/deploy/webdrop-dev.container b/deploy/webdrop-dev.container index 4251117..9fb9a4d 100644 --- a/deploy/webdrop-dev.container +++ b/deploy/webdrop-dev.container @@ -10,6 +10,7 @@ PublishPort=127.0.0.1:5001:5000 Environment=ASPNETCORE_URLS=http://+:5000 Environment=WebDrop__DataDir=/data Environment=DOTNET_EnableDiagnostics=0 +EnvironmentFile=-%h/podman/webdrop-dev/webdrop.env Volume=%h/podman/webdrop-dev/data:/data:Z,U ReadOnly=true ReadOnlyTmpfs=false diff --git a/deploy/webdrop-files-dev.container b/deploy/webdrop-files-dev.container new file mode 100644 index 0000000..fc4d181 --- /dev/null +++ b/deploy/webdrop-files-dev.container @@ -0,0 +1,29 @@ +[Unit] +Description=WebDrop file service (fileAPI) development environment from registry +Wants=network-online.target +After=network-online.target + +[Container] +ContainerName=webdrop-files-dev +Image=registry.oeo.one/justme/webdrop-files:dev +Pull=missing +AutoUpdate=registry +PublishPort=127.0.0.1:5101:5100 +Environment=ASPNETCORE_URLS=http://+:5100 +Environment=FileApi__DataDir=/data +Environment=DOTNET_EnableDiagnostics=0 +EnvironmentFile=%h/podman/webdrop-files-dev/webdrop-files.env +Volume=%h/podman/webdrop-files-dev/data:/data:Z,U +ReadOnly=true +ReadOnlyTmpfs=false +Tmpfs=/tmp:rw,noexec,nosuid,size=16m +DropCapability=ALL +NoNewPrivileges=true + +[Service] +Restart=always +TimeoutStartSec=120 +TimeoutStopSec=60 + +[Install] +WantedBy=default.target diff --git a/deploy/webdrop-files.container b/deploy/webdrop-files.container new file mode 100644 index 0000000..ce1ff5f --- /dev/null +++ b/deploy/webdrop-files.container @@ -0,0 +1,31 @@ +[Unit] +Description=WebDrop file service (fileAPI) +Wants=network-online.target +After=network-online.target + +[Container] +ContainerName=webdrop-files +# 正式环境:跟踪打 v* tag 发布的 latest。放文件的机器(例如家里的 NAS)只拉这个镜像,不需要主后端的 +Image=registry.oeo.one/justme/webdrop-files:latest +Pull=missing +AutoUpdate=registry +PublishPort=127.0.0.1:5100:5100 +Environment=ASPNETCORE_URLS=http://+:5100 +Environment=FileApi__DataDir=/data +Environment=DOTNET_EnableDiagnostics=0 +# 和主后端共用的密钥、允许的页面来源、备份设置等,不进仓库。例子见 webdrop-files.env.example +EnvironmentFile=%h/podman/webdrop-files/webdrop-files.env +Volume=%h/podman/webdrop-files/data:/data:Z,U +ReadOnly=true +ReadOnlyTmpfs=false +Tmpfs=/tmp:rw,noexec,nosuid,size=16m +DropCapability=ALL +NoNewPrivileges=true + +[Service] +Restart=always +TimeoutStartSec=120 +TimeoutStopSec=60 + +[Install] +WantedBy=default.target diff --git a/deploy/webdrop-files.env.example b/deploy/webdrop-files.env.example new file mode 100644 index 0000000..c72bb59 --- /dev/null +++ b/deploy/webdrop-files.env.example @@ -0,0 +1,18 @@ +# 文件服务(家里的 NAS)的机器配置:复制到 ~/podman/webdrop-files/webdrop-files.env 并按实际情况修改。不要提交真实值 +# 和主后端共用的密钥,至少 32 个字符。两边必须一致 +FileApi__Secret= +# 页面所在的源:浏览器直接把文件传到这里,必须列出,否则浏览器会拦下跨域请求 +FileApi__CorsOrigins__0=https://app.example.com +# 创建频道时可选的文件空间档位(字节)和默认值;不配用内置的 1/5/10/50/100 GiB,默认 10 GiB +# FileApi__QuotaChoices__0=1073741824 +# FileApi__QuotaChoices__1=10737418240 +# FileApi__DefaultQuota=10737418240 +# 文件服务最多存多少字节,0 为不限 +# FileApi__MaxStoreBytes=0 +# 每天把新增的文件备份到 S3 协议的存储 +# FileApi__Backup__Enabled=true +# FileApi__Backup__Time=04:30 +# FileApi__Backup__S3__Endpoint=https://s3.example.com +# FileApi__Backup__S3__Bucket=webdrop +# FileApi__Backup__S3__AccessKey= +# FileApi__Backup__S3__SecretKey= diff --git a/deploy/webdrop.container b/deploy/webdrop.container index c083f60..d1abe7e 100644 --- a/deploy/webdrop.container +++ b/deploy/webdrop.container @@ -1,15 +1,21 @@ [Unit] Description=WebDrop API with embedded frontend +Wants=network-online.target +After=network-online.target [Container] ContainerName=webdrop -Image=localhost/webdrop:local -Pull=never +# 正式环境:跟踪打 v* tag 发布的 latest。podman-auto-update.timer 每隔几分钟检查 registry,有新镜像就拉取并重启这个单元 +Image=registry.oeo.one/justme/webdrop-api:latest +Pull=missing +AutoUpdate=registry PublishPort=127.0.0.1:5000:5000 Environment=ASPNETCORE_URLS=http://+:5000 Environment=WebDrop__DataDir=/data Environment=DOTNET_EnableDiagnostics=0 -Volume=/home/justme/podman/webdrop/data:/data:Z,U +# 机器上的配置(文件服务的地址和共用密钥等),不进仓库。例子见 webdrop.env.example +EnvironmentFile=-%h/podman/webdrop/webdrop.env +Volume=%h/podman/webdrop/data:/data:Z,U ReadOnly=true ReadOnlyTmpfs=false Tmpfs=/tmp:rw,noexec,nosuid,size=16m diff --git a/deploy/webdrop.env.example b/deploy/webdrop.env.example new file mode 100644 index 0000000..2f96088 --- /dev/null +++ b/deploy/webdrop.env.example @@ -0,0 +1,7 @@ +# 主后端(VPS)的机器配置:复制到 ~/podman/webdrop/webdrop.env 并按实际情况修改。不要提交真实值 +# 打开文件服务:文件内容不放主后端,由 fileAPI 保存;主后端只签发访问票据 +WebDrop__Files__Enabled=true +# 浏览器访问文件服务的地址(必须 https,和文件服务机器上配的对外地址一致) +WebDrop__Files__PublicUrl=https://files.example.com +# 和文件服务共用的密钥,至少 32 个字符:openssl rand -base64 48。两边必须一致 +WebDrop__Files__Secret= diff --git a/scripts/ci-publish-image.sh b/scripts/ci-publish-image.sh new file mode 100644 index 0000000..ddf9df9 --- /dev/null +++ b/scripts/ci-publish-image.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# 构建并推送一个镜像到 registry。开发镜像和发布镜像、主后端和文件服务都用它,构建和推送的细节只写在这里。 +# 用法: ci-publish-image.sh <镜像名> <版本标签> [额外标签...] +# 例: ci-publish-image.sh webdrop-api server/Containerfile dev-abc1234 dev +# 需要环境变量: ZOT_REGISTRY、ZOT_USERNAME、ZOT_SECRET(Woodpecker 的密钥) +set -euo pipefail + +name="$1"; containerfile="$2"; version="$3"; shift 3 +: "${ZOT_REGISTRY:?}" "${ZOT_USERNAME:?}" "${ZOT_SECRET:?}" +: "${WOODPECKER_BACKEND_LOCAL_TEMP_DIR:=$HOME/woodpecker/work}" + +# 每个镜像一个构建缓存目录,两个镜像并行构建时互不干扰 +store="$(dirname "$WOODPECKER_BACKEND_LOCAL_TEMP_DIR")/buildah-cache/$name" +mkdir -p "$store/storage" "$store/run" +buildah() { command buildah --root "$store/storage" --runroot "$store/run" "$@"; } + +printf '%s' "$ZOT_SECRET" | buildah login --tls-verify=true --username "$ZOT_USERNAME" --password-stdin "$ZOT_REGISTRY" +trap 'buildah logout "$ZOT_REGISTRY" >/dev/null 2>&1 || true' EXIT + +commit="${CI_COMMIT_SHA:-$(git rev-parse HEAD)}" +image="$ZOT_REGISTRY/justme/$name:$version" +buildah bud --layers --network host --format oci --platform linux/amd64 \ + --build-arg "WEBDROP_FRONTEND_COMMIT=$commit" --build-arg "WEBDROP_FRONTEND_TAG=$version" \ + --build-arg "WEBDROP_BACKEND_COMMIT=$commit" --build-arg "WEBDROP_BACKEND_TAG=$version" \ + --build-arg "WEBDROP_BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --tag "$image" --file "$containerfile" . + +buildah push "$image" "docker://$image" +for extra in "$@"; do + buildah tag "$image" "$ZOT_REGISTRY/justme/$name:$extra" + buildah push "$ZOT_REGISTRY/justme/$name:$extra" "docker://$ZOT_REGISTRY/justme/$name:$extra" +done diff --git a/server/Containerfile b/server/Containerfile index dc0a0c7..0b9f182 100644 --- a/server/Containerfile +++ b/server/Containerfile @@ -25,6 +25,7 @@ COPY server/src/WebDrop.Core/WebDrop.Core.csproj ./server/src/WebDrop.Core/ COPY server/src/WebDrop.FileProtocol/WebDrop.FileProtocol.csproj ./server/src/WebDrop.FileProtocol/ COPY server/src/WebDrop.Frontend/WebDrop.Frontend.csproj ./server/src/WebDrop.Frontend/ COPY server/src/WebDrop.Storage/WebDrop.Storage.csproj ./server/src/WebDrop.Storage/ +COPY server/src/WebDrop.Sync/WebDrop.Sync.csproj ./server/src/WebDrop.Sync/ RUN dotnet restore server/src/WebDrop.Api/WebDrop.Api.csproj \ --runtime linux-x64 COPY server/ ./server/ diff --git a/server/Containerfile.files b/server/Containerfile.files new file mode 100644 index 0000000..f0114f8 --- /dev/null +++ b/server/Containerfile.files @@ -0,0 +1,37 @@ +# 文件服务(fileAPI):单独部署,放在存储所在的机器(例如家里的 NAS)。只依赖 FileProtocol 和 Sync,不带前端和数据库 +FROM mcr.microsoft.com/dotnet/sdk:10.0-noble-aot AS build +WORKDIR /src +COPY server/Directory.Build.props ./server/ +COPY server/src/WebDrop.FileApi/WebDrop.FileApi.csproj ./server/src/WebDrop.FileApi/ +COPY server/src/WebDrop.FileProtocol/WebDrop.FileProtocol.csproj ./server/src/WebDrop.FileProtocol/ +COPY server/src/WebDrop.Sync/WebDrop.Sync.csproj ./server/src/WebDrop.Sync/ +RUN dotnet restore server/src/WebDrop.FileApi/WebDrop.FileApi.csproj \ + --runtime linux-x64 +COPY server/src/WebDrop.FileApi/ ./server/src/WebDrop.FileApi/ +COPY server/src/WebDrop.FileProtocol/ ./server/src/WebDrop.FileProtocol/ +COPY server/src/WebDrop.Sync/ ./server/src/WebDrop.Sync/ +RUN dotnet publish server/src/WebDrop.FileApi/WebDrop.FileApi.csproj \ + --configuration Release \ + --runtime linux-x64 \ + --self-contained true \ + --no-restore \ + --output /out + +FROM mcr.microsoft.com/dotnet/runtime-deps:10.0 AS runtime +ARG WEBDROP_BACKEND_COMMIT=unknown +ARG WEBDROP_BACKEND_TAG=unknown +ARG WEBDROP_BUILD_TIME=unknown +LABEL org.opencontainers.image.revision=$WEBDROP_BACKEND_COMMIT \ + org.opencontainers.image.version=$WEBDROP_BACKEND_TAG \ + org.opencontainers.image.created=$WEBDROP_BUILD_TIME +WORKDIR /app +COPY --from=build /out/webdrop-files /app/webdrop-files +RUN mkdir -p /data \ + && chown 10001:10001 /data \ + && chmod 0750 /data +ENV ASPNETCORE_URLS=http://+:5100 \ + FileApi__DataDir=/data \ + DOTNET_EnableDiagnostics=0 +USER 10001:10001 +EXPOSE 5100 +ENTRYPOINT ["/app/webdrop-files"]