diff --git a/.github/workflows/cf-edit-native.yml b/.github/workflows/cf-edit-native.yml index 1b752c11..cedc92b8 100644 --- a/.github/workflows/cf-edit-native.yml +++ b/.github/workflows/cf-edit-native.yml @@ -1,16 +1,7 @@ name: CF-EDIT pinned native compile (feature) on: - push: - branches: [feature/v2-rust-guard-only, dev] - paths: - - "build/v2/source-set.json" - - ".github/workflows/cf-edit-native.yml" - pull_request: - branches: [dev] - paths: - - "build/v2/source-set.json" - - ".github/workflows/cf-edit-native.yml" workflow_dispatch: + permissions: contents: read jobs: diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index d558d2cb..c6a3f02a 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -1,53 +1,62 @@ -name: CloudFile development checks +name: CloudFile quick source checks +# Routine dev and PR changes must never trigger a cross-repository build, +# native compilation, DB integration or deployment. Run those separately via +# reviewed workflow_dispatch jobs when the relevant change needs them. on: push: branches: [dev] + paths-ignore: + - '**/*.md' + - '**/*.markdown' + - 'README*' + - 'AGENTS.md' + - 'docs/**' + pull_request: + branches: [dev] + paths-ignore: + - '**/*.md' + - '**/*.markdown' + - 'README*' + - 'AGENTS.md' + - 'docs/**' + workflow_dispatch: -concurrency: - group: dev-checks-${{ github.ref }} - cancel-in-progress: true +permissions: + contents: read jobs: - checks: + static: runs-on: ubuntu-24.04 + timeout-minutes: 5 steps: - uses: actions/checkout@v4 with: - path: cloudfile-docker - fetch-depth: 0 - - - uses: actions/checkout@v4 - with: - repository: eaopen/cloudfile-server - ref: dev - path: cloudfile-server - fetch-depth: 0 - - - uses: actions/checkout@v4 - with: - repository: eaopen/cloudfile-hub - ref: dev - path: cloudfile-hub - fetch-depth: 0 - - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - uses: actions/setup-go@v5 - with: - go-version-file: cloudfile-server/fileserver/go.mod - cache-dependency-path: cloudfile-server/fileserver/go.sum - - - name: Install C test dependencies + fetch-depth: 2 + - name: Check changed files + shell: bash run: | - sudo apt-get update -qq - # Native directory paging tests compile the real Vala objects and JSON RPC. - sudo apt-get install -y -qq libglib2.0-dev libjansson-dev valac pkg-config - - - name: Install isolated check dependencies - run: pip install --quiet -r cloudfile-docker/tools/check-requirements.txt - - - name: Run development checks - run: ./cloudfile-docker/tools/run-checks.sh + set -euo pipefail + git diff --check HEAD^ HEAD + python3 - <<'PY' + from pathlib import Path + import subprocess + + changed = subprocess.check_output( + ['git', 'diff', '--name-only', '-z', 'HEAD^', 'HEAD'] + ).split(b'\0') + for raw in changed: + if not raw: + continue + path = Path(raw.decode('utf-8')) + if path.is_file() and path.suffix == '.py': + compile(path.read_bytes(), str(path), 'exec') + PY + while IFS= read -r path; do + [[ ! -f "$path" ]] || bash -n "$path" + done < <(git diff --name-only HEAD^ HEAD -- '*.sh') + + - name: Test source selection rules without building images + run: | + python3 -m unittest discover -s tests -p test_v2_source_assembly.py -q + python3 -m unittest discover -s tests -p test_v2_build_policy.py -q diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index 66d73425..e4367b04 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -112,18 +112,28 @@ jobs: path: cloudfile-docker fetch-depth: 1 + - name: Restrict the historical release recipe to explicit maintenance refs + shell: bash + run: | + if [[ "$GITHUB_REF_NAME" != maintenance/v0.9/* ]]; then + echo "::error::Historical packaging is allowed only from an explicitly approved maintenance/v0.9/* ref; never from CloudFile dev or a detached feature snapshot." + exit 2 + fi + - name: Read release refs id: refs working-directory: cloudfile-docker run: | - server_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml server_commit || true) - hub_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml hub_commit || true) - if [[ -z $server_ref ]]; then - server_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml forks.cloudfile_server.ref) - fi - if [[ -z $hub_ref ]]; then - hub_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml forks.cloudfile_hub.ref) - fi + server_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml server_commit) + hub_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml hub_commit) + # Never fall back to floating dev; the Docker commit is recorded + # from the checked-out workflow SHA, not self-pinned in this file. + for pin in "$server_ref" "$hub_ref"; do + if [[ ! $pin =~ ^[0-9a-f]{40}$ ]]; then + echo "::error::Historical release requires immutable server_commit and hub_commit pins" + exit 2 + fi + done echo "server_ref=$server_ref" >> "$GITHUB_OUTPUT" echo "hub_ref=$hub_ref" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/v2-clean.yml b/.github/workflows/v2-clean.yml index ea680497..9e9d19eb 100644 --- a/.github/workflows/v2-clean.yml +++ b/.github/workflows/v2-clean.yml @@ -1,10 +1,7 @@ -name: CloudFile V2 clean CE14 isolated checks +name: CloudFile frozen V2 source-set (historical manual check) +# Explicitly frozen 2026-10-16 source snapshot; this does NOT prove current dev. on: - push: - branches: [dev, dev2-clean, feature/v2-rust-guard-only, feature/v2-checkout-default-on] - pull_request: - branches: [dev] workflow_dispatch: permissions: @@ -84,7 +81,7 @@ jobs: CF_TEST_REDIS_PORT: '6379' run: | python3 -m unittest discover -s cloudfile-docker/tests -p test_v2_source_assembly.py -v - python3 cloudfile-docker/tools/prepare-v2-source.py --workspace . --output /tmp/cfv2-clean + python3 cloudfile-docker/tools/prepare-v2-source.py --workspace . --source-set cloudfile-docker/build/v2/source-set.json --output /tmp/cfv2-clean python3 cloudfile-docker/tools/check-v2-fresh.py --source /tmp/cfv2-clean native-ce14-runtime: @@ -147,7 +144,7 @@ jobs: python3 -m pip install --quiet 'pymysql>=1.1,<2' 'redis>=5,<8' 'PyJWT>=2.8,<3' 'requests>=2.31,<3' 'Django>=5.2,<5.3' 'requests>=2.31,<3' 'PyJWT>=2,<3' - name: Assemble exact selected native source run: | - python3 cloudfile-docker/tools/prepare-v2-source.py --workspace . --output /tmp/cfv2-clean + python3 cloudfile-docker/tools/prepare-v2-source.py --workspace . --source-set cloudfile-docker/build/v2/source-set.json --output /tmp/cfv2-clean test ! -e /tmp/cfv2-clean/seahub/cloudfile_extensions test ! -e /tmp/cfv2-clean/seafile-server/server/cloudfile-policy.c - name: Check routed and native Go storage diff --git a/.github/workflows/v2-dev-bundle.yml b/.github/workflows/v2-dev-bundle.yml new file mode 100644 index 00000000..6cc05411 --- /dev/null +++ b/.github/workflows/v2-dev-bundle.yml @@ -0,0 +1,124 @@ +name: CloudFile V2 current dev source bundle + +# Run this integration snapshot only after the selected changes are ready. +# Ordinary commits and documentation edits must not trigger compilation or +# runtime test infrastructure; native builds remain separately approved. +on: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: v2-current-source-${{ github.ref }} + cancel-in-progress: true + +jobs: + current-v2-source: + runs-on: ubuntu-24.04 + services: + mariadb: + image: mariadb:11.4 + env: + MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: '1' + ports: ['3306:3306'] + options: >- + --health-cmd="healthcheck.sh --connect --innodb_initialized" + --health-interval=5s --health-timeout=5s --health-retries=20 + redis: + image: redis:7-alpine + ports: ['6379:6379'] + options: >- + --health-cmd="redis-cli ping" + --health-interval=5s --health-timeout=5s --health-retries=10 + steps: + - uses: actions/checkout@v4 + with: + path: cloudfile-docker + fetch-depth: 0 + + - name: Freeze current peer commits once + shell: bash + id: peers + run: | + set -euo pipefail + server=$(git ls-remote https://github.com/eaopen/cloudfile-server.git refs/heads/dev | awk '{print $1}') + hub=$(git ls-remote https://github.com/eaopen/cloudfile-hub.git refs/heads/dev | awk '{print $1}') + [[ $server =~ ^[a-f0-9]{40}$ && $hub =~ ^[a-f0-9]{40}$ ]] + echo "server_sha=$server" >> "$GITHUB_OUTPUT" + echo "hub_sha=$hub" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v4 + with: + repository: eaopen/cloudfile-server + ref: ${{ steps.peers.outputs.server_sha }} + path: cloudfile-server + fetch-depth: 0 + + - uses: actions/checkout@v4 + with: + repository: eaopen/cloudfile-hub + ref: ${{ steps.peers.outputs.hub_sha }} + path: cloudfile-hub + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install isolated V2 contract dependencies + run: pip install --quiet 'pymysql>=1.1,<2' 'redis>=5,<8' 'PyJWT>=2.8,<3' 'requests>=2.31,<3' 'Django>=5.2,<5.3' + + - name: Prepare and test exact current V2 source snapshot + env: + SERVER_SHA: ${{ steps.peers.outputs.server_sha }} + HUB_SHA: ${{ steps.peers.outputs.hub_sha }} + CF_TEST_DB_PORT: '3306' + CF_TEST_REDIS_PORT: '6379' + shell: bash + run: | + set -euo pipefail + python3 - <<'PY' + import json + import os + from pathlib import Path + + manifest = json.loads(Path('cloudfile-docker/build/v2/source-set.json').read_text()) + manifest['components']['cloudfile-server']['overlay_commit'] = os.environ['SERVER_SHA'] + manifest['components']['cloudfile-hub']['overlay_commit'] = os.environ['HUB_SHA'] + # The Go filesystem backend is an execution seam, not an additive + # V2 module. Without it a current-dev image can silently retain the + # upstream tempfiles EXDEV behavior even after Server's fix merges. + native = manifest['components']['cloudfile-server']['native_patches'] + if 'fileserver/objstore/backend_fs.go' not in native: + native.append('fileserver/objstore/backend_fs.go') + Path('/tmp/current-v2-source-set.json').write_text(json.dumps(manifest, indent=2) + '\n') + PY + python3 cloudfile-docker/tools/prepare-v2-source.py \ + --workspace . --source-set /tmp/current-v2-source-set.json \ + --output /tmp/cloudfile-v2-current + python3 cloudfile-docker/tools/check-v2-fresh.py --source /tmp/cloudfile-v2-current + python3 -m compileall -q /tmp/cloudfile-v2-current/seahub/cloudfile_ext/v2 + + - name: Record independently checkable V2 bundle + if: always() + shell: bash + env: + SERVER_SHA: ${{ steps.peers.outputs.server_sha }} + HUB_SHA: ${{ steps.peers.outputs.hub_sha }} + run: | + { + echo '### V2 current DEV source snapshot' + echo "- Docker: `$(git -C cloudfile-docker rev-parse HEAD)`" + echo "- Server: `$SERVER_SHA`" + echo "- Hub: `$HUB_SHA`" + echo 'V2 source assembly, schema and isolated contracts only; not a live Etech or native CE runtime acceptance.' + } >> "$GITHUB_STEP_SUMMARY" + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: v2-source-receipt-${{ github.run_id }} + path: /tmp/cloudfile-v2-current/v2-source-receipt.json + if-no-files-found: ignore + retention-days: 14 diff --git a/build/v2/Dockerfile.dev b/build/v2/Dockerfile.dev index a2e764c2..d0406a97 100644 --- a/build/v2/Dockerfile.dev +++ b/build/v2/Dockerfile.dev @@ -23,7 +23,9 @@ RUN --mount=type=cache,id=cfv2-gomod,from=cf-cache,source=gomodcache,target=/go/ go mod download FROM go-dependencies AS native -COPY v2-source-receipt.json /source/v2-source-receipt.json +# The combined receipt also changes on Hub-only commits; do not invalidate the +# expensive native C/Go layer with unrelated Hub source. The exact receipt is +# checked before the build and copied into the final runtime image below. COPY seafile-server /source/seafile-server RUN --mount=type=cache,id=cfv2-gomod,from=cf-cache,source=gomodcache,target=/go/pkg/mod \ --mount=type=cache,id=cfv2-gobuild-${TARGETARCH},from=cf-cache,source=gocache,target=/root/.cache/go-build \ diff --git a/build/v2/README.md b/build/v2/README.md index ab5fa472..a126bac6 100644 --- a/build/v2/README.md +++ b/build/v2/README.md @@ -1,7 +1,44 @@ # Native V2 DEV Runtime -Prepare the pinned CE14 source with `tools/prepare-v2-source.py`, then use the -existing CE14 dependency images and cache directory: +> Server/Hub/Docker `dev` is the only default development source. Source assembly +> resolves the local Server/Hub `dev` refs to exact SHAs before reading files; +> update the local `dev` refs first. The pinned `source-set.json` is retained +> only as an explicitly selected historical snapshot with `--source-set`. +> Cross-repository integration checks run on manual `workflow_dispatch`, not +> on every code or documentation change. Neither source check publishes an image. + +### Current CI and incremental-build policy (2026-10-20) + +- Ordinary Server/Hub/Docker pull requests and `dev` pushes perform cheap + changed-source syntax/format checks only; pure Markdown changes skip CI. + Search/OIDC probes retain narrow path filters. +- Full Seahub, C/Go/native, database, cross-repository and image builds are + **manual**. Reuse previous test evidence if its source SHA, prerequisites and + runner environment are unchanged. Neither CI nor release performs a deployment + or an automatic rollback. +- `prepare-v2-source.py` selects the local Server/Hub `dev` refs by default, + resolves the SHAs once, and records the exact reviewed overlay in its receipt. + The CE14 native anchor and allowlisted native file seams do not float. +- The combined source receipt is validated before any Docker build and retained + in the final image, **not** fed into the C/Go compiler layer. A Hub-only SHA + change should reuse that existing layer; this is a cache-key guarantee, + **not** a measured cache-hit claim. BuildKit, ccache, Go, npm and pip caches + may still be absent on a particular builder. +- Legacy `release.yaml`/CE14 packaging is restricted to deliberately selected + maintenance refs and fixed Server/Hub SHA inputs, never the default V2 path. + Missing runtime launchers or invalid input hashes must fail explicitly rather + than copying or silently falling back to historical application files. + +**Approval boundary:** a manual integration or release check is not authorization +to compile all targets, publish an image, change DEV/PROD containers, or roll +back a deployment. Each operation requires its own approved scope. + +Prepare the CE14 source plus reviewed current CloudFile `dev` overlays with +`python3 tools/prepare-v2-source.py --workspace . --output /path/to/prepared-source` +from a three-repository workspace. To reproduce a historical source snapshot, +supply `--source-set build/v2/source-set.json` explicitly instead. + +Then use the existing dependency images and cache directory: ```sh CF_BASE_IMAGE=cloudfile-build-base:ce14-v2 \ @@ -206,7 +243,10 @@ reproduces RawPostDataException on the prior image; no schema, tag migration or native binary rebuild is involved. -## 内部文件读取:复用 Seafile 原生票据(2026-10-19) +## 内部文件读取:复用 Seafile 原生票据(2026-10-19,历史构建快照) + +> 以下源码 SHA 与当时的构建限制用于复现 10 月 19 日的特定候选; +> **不是当前 `dev` 的源码选择规则**。当前构建规则以上方说明为准。 本候选版本固定 Server `89ae96652389024730ac9d3927ed3be908534ce9` 与 Hub `bfc7651f188b8a537a466e7ee0bc143bc6acb85e`。 diff --git a/build/v2/build-image.sh b/build/v2/build-image.sh index 5df93296..14396e79 100755 --- a/build/v2/build-image.sh +++ b/build/v2/build-image.sh @@ -4,6 +4,38 @@ set -euo pipefail here=$(cd "$(dirname "$0")" && pwd) repo=$(cd "$here/../.." && pwd) source_root=$(cd "${1:?prepared V2 source directory required}" && pwd) +# Read-only receipt check BEFORE any tagging, fetching or building. The +# combined receipt stays with the final image, not the Server C/Go build layer. +python3 - "$source_root" <<'PY' +import hashlib +import json +import re +import sys +from pathlib import Path + +root = Path(sys.argv[1]) +path = root / 'v2-source-receipt.json' +if not path.is_file() or path.is_symlink(): + raise SystemExit('Missing immutable V2 source receipt') +receipt = json.loads(path.read_text(encoding='utf-8')) +for component, directory in (('cloudfile-server', 'seafile-server'), + ('cloudfile-hub', 'seahub')): + item = receipt.get('components', {}).get(component, {}) + commits = (item.get('native_commit'), item.get('overlay_commit')) + if not all(isinstance(x, str) and re.fullmatch('[0-9a-f]{40}', x) for x in commits): + raise SystemExit('Incomplete source commits: ' + component) + checksums = item.get('overlay_sha256') + if not isinstance(checksums, dict) or not checksums: + raise SystemExit('Missing source checksums: ' + component) + for name, expected in checksums.items(): + relative = Path(name) + source = root / directory / relative + if (relative.is_absolute() or '..' in relative.parts or source.is_symlink() + or not source.is_file() or not isinstance(expected, str) + or hashlib.sha256(source.read_bytes()).hexdigest() != expected): + raise SystemExit('Source receipt mismatch: ' + component + '/' + name) +print('Prepared source receipt verified; no compilation performed') +PY image=${2:-cloudfile:v2-dev} platform=${CF_PLATFORM:-$(docker version --format '{{.Server.Os}}/{{.Server.Arch}}')} arch=${platform##*/} diff --git a/build/v2/build-native.sh b/build/v2/build-native.sh index a535ce8d..846f2dbe 100644 --- a/build/v2/build-native.sh +++ b/build/v2/build-native.sh @@ -8,7 +8,11 @@ case "$target" in all|sources|dependencies|server) ;; *) exit 2 ;; esac jobs=${CF_V2_BUILD_JOBS:-4} [[ $jobs =~ ^[1-9][0-9]*$ ]] || exit 2 if [[ $target != dependencies ]]; then - test -f "$source_root/v2-source-receipt.json" + # The host validates the full source receipt before Docker packaging. + # This server-only stage intentionally does not COPY the combined receipt: + # a Hub-only SHA update must not recompile native C/Go. + test -f "$source_root/seafile-server/scripts/sql/mysql/cloudfile-v2.sql" + test -f "$source_root/seafile-server/common/cf-v2-storage.c" test ! -d "$source_root/seahub/cloudfile_extensions" test ! -f "$source_root/seafile-server/common/cf-ext.c" fi diff --git a/release.yaml b/release.yaml index 58943d52..214dd0e2 100644 --- a/release.yaml +++ b/release.yaml @@ -1,3 +1,4 @@ +# Historical CE14 extension release recipe. V2 dev uses build/v2/source-set.json and the V2 current-bundle CI; do not release V2 images from this manifest. # CloudFile release manifest. # # Upstream CE 14 is released now: haiwen/seafile-server and haiwen/seahub both diff --git a/tests/test_v2_build_policy.py b/tests/test_v2_build_policy.py new file mode 100644 index 00000000..b1a66b74 --- /dev/null +++ b/tests/test_v2_build_policy.py @@ -0,0 +1,61 @@ +"""Cheap source and CI policy regressions (no Docker, compiler or network).""" +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def read(path): + return (ROOT / path).read_text(encoding="utf-8") + + +class DevSourcePolicy(unittest.TestCase): + def test_current_dev_is_default_but_historical_snapshot_is_explicit(self): + assembler = read("tools/prepare-v2-source.py") + self.assertIn("if args.source_set else 'dev'", assembler) + self.assertIn("sources['components'][component]['overlay_commit'] = commit", assembler) + self.assertIn("go_seam = 'fileserver/objstore/backend_fs.go'", assembler) + self.assertIn("native_patches.append(go_seam)", assembler) + frozen = read(".github/workflows/v2-clean.yml") + self.assertEqual( + frozen.count("--source-set cloudfile-docker/build/v2/source-set.json"), 2 + ) + + def test_hub_only_revision_does_not_invalidate_native_compiler_layer(self): + dockerfile = read("build/v2/Dockerfile.dev") + server_layer = dockerfile.split("FROM go-dependencies AS native", 1)[1].split( + "FROM ${CF_BASE_IMAGE} AS frontend-dependencies", 1 + )[0] + self.assertNotIn("COPY v2-source-receipt.json", server_layer) + self.assertIn("COPY seafile-server /source/seafile-server", server_layer) + self.assertIn( + "COPY v2-source-receipt.json /opt/cloudfile-v2/source/v2-source-receipt.json", + dockerfile, + ) + preflight = read("build/v2/build-image.sh") + self.assertLess( + preflight.index("Prepared source receipt verified"), + preflight.index('docker tag "$build_id"'), + ) + + def test_current_bundle_includes_the_go_storage_seam(self): + current = read(".github/workflows/v2-dev-bundle.yml") + self.assertIn("native.append('fileserver/objstore/backend_fs.go')", current) + + def test_native_and_release_workflows_never_auto_run(self): + paths = ( + ".github/workflows/v2-clean.yml", + ".github/workflows/v2-dev-bundle.yml", + ".github/workflows/cf-edit-native.yml", + ".github/workflows/prod.yml", + ) + for path in paths: + with self.subTest(workflow=path): + trigger = read(path).split("permissions:", 1)[0] + self.assertIn("workflow_dispatch:", trigger) + self.assertNotIn("\n push:", trigger) + self.assertNotIn("\n pull_request:", trigger) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/prepare-v2-source.py b/tools/prepare-v2-source.py index ebdfabfd..93febba3 100644 --- a/tools/prepare-v2-source.py +++ b/tools/prepare-v2-source.py @@ -116,13 +116,41 @@ def main(): parser.add_argument('--output', type=Path, required=True) parser.add_argument('--hub-ref') parser.add_argument('--server-ref') + parser.add_argument('--source-set', type=Path, + help='Explicit frozen source-set; without this, select the local dev refs') args = parser.parse_args() - config = Path(__file__).resolve().parents[1] / 'build/v2/source-set.json' + config = args.source_set or (Path(__file__).resolve().parents[1] / 'build/v2/source-set.json') sources = json.loads(config.read_text()) - refs = { - 'cloudfile-hub': args.hub_ref or sources['components']['cloudfile-hub']['overlay_commit'], - 'cloudfile-server': args.server_ref or sources['components']['cloudfile-server']['overlay_commit'], + if not args.source_set: + # Preserve the historical allowlist only for explicit frozen builds. + # The current Server dev filesystem backend writes each block beside + # its final path; omitting this seam silently restores cross-mount EXDEV. + native_patches = sources['components']['cloudfile-server']['native_patches'] + go_seam = 'fileserver/objstore/backend_fs.go' + if go_seam not in native_patches: + native_patches.append(go_seam) + selected = { + 'cloudfile-hub': args.hub_ref, + 'cloudfile-server': args.server_ref, } + refs = {} + for component, requested in selected.items(): + # CE native commits remain immutable. Review the CloudFile overlay + # plus its minimal native seams from the local dev ref and freeze the + # chosen commit before extraction. Explicit --source-set reproduces + # the exact historical/release allowlist. + ref = requested or (sources['components'][component]['overlay_commit'] + if args.source_set else 'dev') + try: + commit = git(args.workspace / component, 'rev-parse', '--verify', + ref + '^{commit}').decode().strip() + except subprocess.CalledProcessError as error: + raise ValueError('missing source ref for %s: %s' % (component, ref)) from error + if not re.fullmatch('[0-9a-f]{40}', commit): + raise ValueError('invalid resolved source commit for ' + component) + if not args.source_set: + sources['components'][component]['overlay_commit'] = commit + refs[component] = commit receipt = assemble(args.workspace.resolve(), args.output.resolve(), sources, refs) print(json.dumps(receipt, indent=2, sort_keys=True)) diff --git a/tools/release-dev162.sh b/tools/release-dev162.sh index fa8b74df..a970f546 100755 --- a/tools/release-dev162.sh +++ b/tools/release-dev162.sh @@ -30,11 +30,29 @@ esac here=$(cd "$(dirname "$0")" && pwd) repo_root=$(cd "$here/.." && pwd) +# This command intentionally packages the historical recipe only. Require +# an explicit maintenance branch even for push-only mode or detached HEAD; +# never let an old recipe package current dev just because the checkout moved. +docker_branch=$(git -C "$repo_root" symbolic-ref --quiet --short HEAD 2>/dev/null || true) +if [[ "$docker_branch" != maintenance/v0.9/* ]]; then + echo "Historical release requires an approved maintenance/v0.9/* Docker branch; current dev and detached refs are refused." >&2 + exit 2 +fi workspace=$(cd "$repo_root/.." && pwd) server_repo=$workspace/cloudfile-server hub_repo=$workspace/cloudfile-hub manifest=$repo_root/release.yaml reader=$repo_root/build/cloudfile_14.0/read-manifest.py +server_pin=$(python3 "$reader" "$manifest" server_commit) +hub_pin=$(python3 "$reader" "$manifest" hub_commit) +# The Docker SHA is captured from the checkout in the build receipt. +# A commit cannot embed its own SHA in a tracked manifest. +for pin in "$server_pin" "$hub_pin"; do + [[ "$pin" =~ ^[0-9a-f]{40}$ ]] || { + echo "Historical release requires explicit immutable server/hub commits; Docker SHA is recorded from its checkout." >&2 + exit 2 + } +done dist=$repo_root/build/cloudfile_14.0/seafile-server-$version local_image=$(python3 "$reader" "$manifest" image) local_image=${local_image%%:*}:$version @@ -104,11 +122,9 @@ if [[ $mode != push ]]; then echo "Updating $(basename "$repo") ($branch)" git_at "$repo" pull --ff-only origin "$branch" done - server_ref=$(python3 "$reader" "$manifest" forks.cloudfile_server.ref) - hub_ref=$(python3 "$reader" "$manifest" forks.cloudfile_hub.ref) - [[ $(git_at "$server_repo" symbolic-ref --short HEAD) == "$server_ref" && - $(git_at "$hub_repo" symbolic-ref --short HEAD) == "$hub_ref" ]] || { - echo "Local Server/Hub branches differ from release.yaml refs." >&2 + [[ $(git_at "$server_repo" rev-parse HEAD) == "$server_pin" && + $(git_at "$hub_repo" rev-parse HEAD) == "$hub_pin" ]] || { + echo "Historical Server/Hub checkout differs from frozen commits; do not build floating dev." >&2 exit 2 } if [[ ${CF_FORCE_REBUILD:-0} != 1 ]] && can_reuse_build; then