apply stignore synchronization patch
custom release / build-custom-release (push) Successful in 3m12s
custom release / build-custom-release (push) Successful in 3m12s
This commit is contained in:
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
upstream_url="${SYNC_UPSTREAM_URL:-https://github.com/syncthing/syncthing.git}"
|
||||
remote="${SYNC_REMOTE:-origin}"
|
||||
upstream_branch="${SYNC_UPSTREAM_BRANCH:-upstream}"
|
||||
main_branch="${SYNC_MAIN_BRANCH:-main}"
|
||||
tmp=""
|
||||
|
||||
die() {
|
||||
printf 'error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
[[ -z "$tmp" ]] || rm -rf "$tmp"
|
||||
}
|
||||
|
||||
main() {
|
||||
[[ -z "$(git status --porcelain)" ]] || die "working tree has uncommitted changes"
|
||||
|
||||
local current_main
|
||||
local current_upstream
|
||||
local main_ahead_count
|
||||
local main_parent
|
||||
local new_upstream
|
||||
local upstream_date
|
||||
current_main="$(git rev-parse "refs/heads/$main_branch")"
|
||||
current_upstream="$(git rev-parse "refs/remotes/$remote/$upstream_branch")"
|
||||
main_ahead_count="$(git rev-list --count "$current_upstream..$current_main")"
|
||||
main_parent="$(git rev-parse "$current_main^" 2>/dev/null || true)"
|
||||
|
||||
git fetch "$upstream_url" "refs/heads/main:refs/remotes/official/main"
|
||||
new_upstream="$(git rev-parse refs/remotes/official/main)"
|
||||
if [[ "$new_upstream" == "$current_upstream" && "$main_ahead_count" == "1" && "$main_parent" == "$current_upstream" ]]; then
|
||||
printf 'Upstream is current and main is exactly one commit ahead at %s.\n' "$new_upstream"
|
||||
return
|
||||
fi
|
||||
upstream_date="$(git show -s --format=%cI "$new_upstream")"
|
||||
|
||||
tmp="$(mktemp -d)"
|
||||
trap cleanup EXIT
|
||||
mkdir -p "$tmp/.gitea/workflows" "$tmp/.github/workflows" "$tmp/patches" "$tmp/scripts/tests"
|
||||
cp .gitea/workflows/custom-release.yml "$tmp/.gitea/workflows/"
|
||||
cp .github/workflows/custom-release.yml "$tmp/.github/workflows/"
|
||||
cp patches/*.patch patches/README.md "$tmp/patches/"
|
||||
cp scripts/update-custom-release.sh scripts/sync-upstream.sh "$tmp/scripts/"
|
||||
cp scripts/tests/test-custom-release-macos-runner.bats "$tmp/scripts/tests/"
|
||||
|
||||
if [[ "$new_upstream" != "$current_upstream" ]]; then
|
||||
git branch -f "$upstream_branch" "$new_upstream"
|
||||
git push --force-with-lease="$upstream_branch:$current_upstream" "$remote" "$upstream_branch"
|
||||
fi
|
||||
|
||||
git checkout -B "$main_branch" "$new_upstream"
|
||||
cp -R "$tmp/.gitea" "$tmp/.github" "$tmp/patches" "$tmp/scripts" .
|
||||
git apply patches/sync-stignore.patch patches/webui-build-marker.patch
|
||||
git add -A
|
||||
GIT_AUTHOR_DATE="$upstream_date" GIT_COMMITTER_DATE="$upstream_date" \
|
||||
git -c user.name="Syncthing .stignore Fork" \
|
||||
-c user.email="actions@felixfoertsch.de" \
|
||||
commit -m "apply stignore synchronization patch"
|
||||
git push --force-with-lease="$main_branch:$current_main" "$remote" "$main_branch"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
setup() {
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||
WORKFLOW="$REPO_ROOT/.gitea/workflows/custom-release.yml"
|
||||
GITHUB_WORKFLOW="$REPO_ROOT/.github/workflows/custom-release.yml"
|
||||
RELEASE_SCRIPT="$REPO_ROOT/scripts/update-custom-release.sh"
|
||||
SYNC_SCRIPT="$REPO_ROOT/scripts/sync-upstream.sh"
|
||||
}
|
||||
|
||||
@test "both hosts mirror upstream before building their own release" {
|
||||
run rg -n 'run: ./scripts/sync-upstream.sh' "$WORKFLOW" "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "${#lines[@]}" -eq 2 ]
|
||||
|
||||
run rg -n 'git push --force-with-lease=.*upstream|git push --force-with-lease=.*main' "$SYNC_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'git apply patches/sync-stignore.patch patches/webui-build-marker.patch' "$SYNC_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'main_parent=.*current_main\^' "$SYNC_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'main_parent.*current_upstream' "$SYNC_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'main_ahead_count=.*rev-list --count' "$SYNC_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'main_ahead_count.*== "1"' "$SYNC_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'GIT_AUTHOR_DATE=.*GIT_COMMITTER_DATE=' "$SYNC_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "github custom release uses a github macos runner and github releases" {
|
||||
run rg -n 'runs-on:[[:space:]]*macos-14' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'gh release create' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_CREATE_GITEA_RELEASE: "0"' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_SIGN_DARWIN: "1"' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'tea|ffmini_macos_arm64' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "github imports signing secrets into a disposable keychain" {
|
||||
run rg -n 'DEVELOPER_ID_APPLICATION_P12_BASE64:.*secrets.DEVELOPER_ID_APPLICATION_P12_BASE64' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'DEVELOPER_ID_APPLICATION_P12_PASSWORD:.*secrets.DEVELOPER_ID_APPLICATION_P12_PASSWORD' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'security import .* -P "\$DEVELOPER_ID_APPLICATION_P12_PASSWORD"' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n '::add-mask::\$keychain_password' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'if: always\(\)' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'security delete-keychain "\$CUSTOM_RELEASE_KEYCHAIN_PATH"' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'rm -f "\$CUSTOM_RELEASE_CERTIFICATE_PATH"' "$GITHUB_WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "custom release runs as one job on ffmini macos runner" {
|
||||
run rg -n 'runs-on:[[:space:]]*ffmini_macos_arm64' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'ubuntu-latest' "$WORKFLOW"
|
||||
[ "$status" -ne 0 ]
|
||||
|
||||
run rg -n 'actions/upload-artifact' "$WORKFLOW"
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "custom release tea login setup is idempotent on persistent host runner" {
|
||||
run rg -n 'tea" logins delete actions >/dev/null 2>&1 \|\| true' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'tea" logins add --name actions' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "custom release workflow imports developer id signing material into temporary keychain" {
|
||||
run rg -n 'DEVELOPER_ID_APPLICATION_P12_BASE64' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'DEVELOPER_ID_APPLICATION_P12_PASSWORD' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'DEVELOPER_ID_APPLICATION_P12_BASE64 secret is required' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'DEVELOPER_ID_APPLICATION_P12_PASSWORD secret is required' "$WORKFLOW"
|
||||
[ "$status" -ne 0 ]
|
||||
|
||||
run rg -n 'security import .* -P "\$DEVELOPER_ID_APPLICATION_P12_PASSWORD"' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'security import .* -A ' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'security find-identity -v -p codesigning "\$keychain_path"' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'security default-keychain -d user -s "\$keychain_path"' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'previous_default_keychain=' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN=\$previous_default_keychain' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_PREVIOUS_DYNAMIC_DEFAULT_KEYCHAIN' "$WORKFLOW"
|
||||
[ "$status" -ne 0 ]
|
||||
|
||||
run rg -n 'security default-keychain -s "\$CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN"' "$WORKFLOW"
|
||||
[ "$status" -ne 0 ]
|
||||
|
||||
run rg -n 'security default-keychain -d dynamic' "$WORKFLOW"
|
||||
[ "$status" -ne 0 ]
|
||||
|
||||
run rg -n 'security find-identity -v -p codesigning$' "$WORKFLOW" "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'codesign_identity_sha1=' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'sed -n .*Developer ID Application' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_CODESIGN_IDENTITY=\$codesign_identity' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_CODESIGN_IDENTITY_SHA1=\$codesign_identity_sha1' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_KEYCHAIN_PASSWORD=\$keychain_password' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'codesign --force --dryrun --sign "\$codesign_identity" --keychain "\$keychain_path" --options runtime --timestamp "\$probe_binary"' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_CODESIGN_IDENTITY: "Developer ID Application' "$WORKFLOW"
|
||||
[ "$status" -ne 0 ]
|
||||
|
||||
run rg -n 'security create-keychain' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'keychain_dir="\$HOME/Library/Keychains"' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'rm -f "\$keychain_path"' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'existing_keychains=\(\)' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'security list-keychains -s "\$keychain_path"' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'security list-keychains -d dynamic' "$WORKFLOW"
|
||||
[ "$status" -ne 0 ]
|
||||
|
||||
run rg -n 'security import' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'security delete-keychain' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "custom release carries per-target cgo mode" {
|
||||
run rg -n 'darwin/arm64/zip/1' "$WORKFLOW" "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'linux/amd64/tar/0' "$WORKFLOW" "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_CGO_ENABLED' "$WORKFLOW"
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "custom release signs darwin assets with hardened runtime and timestamp" {
|
||||
run rg -n 'codesign_args=\(--force --sign "\$codesign_identity"\)' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n -- '--keychain "\$CUSTOM_RELEASE_KEYCHAIN_PATH"' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'codesign_args\+=\(--options runtime --timestamp\)' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'security unlock-keychain -p "\$CUSTOM_RELEASE_KEYCHAIN_PASSWORD" "\$CUSTOM_RELEASE_KEYCHAIN_PATH"' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'security find-identity -v -p codesigning "\$CUSTOM_RELEASE_KEYCHAIN_PATH"' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run awk '
|
||||
/codesign_args=\(--force --sign "\$codesign_identity"\)/ { sign = NR }
|
||||
/codesign_args\+=\(--keychain "\$CUSTOM_RELEASE_KEYCHAIN_PATH"\)/ { keychain = NR }
|
||||
/codesign_args\+=\(--options runtime --timestamp\)/ { options = NR }
|
||||
END { exit !(sign && keychain && options && sign < keychain && keychain < options) }
|
||||
' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'Developer ID Application' "$WORKFLOW" "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_CODESIGN_IDENTITY' "$WORKFLOW" "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "custom release validates signed darwin binaries before publishing" {
|
||||
run rg -n 'modernc-sqlite' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'codesign --verify --strict --verbose=2' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'TeamIdentifier=NG5W75WE8U|TeamIdentifier.*NG5W75WE8U' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_REQUIRE_GATEKEEPER_ASSESSMENT: "0"' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'CUSTOM_RELEASE_SIGN_DARWIN: "1"' "$WORKFLOW"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'require_gatekeeper_assessment="\$\{CUSTOM_RELEASE_REQUIRE_GATEKEEPER_ASSESSMENT:-0\}"' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'if \[\[ "\$require_gatekeeper_assessment" == "1" \]\]; then' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run rg -n 'spctl -a -vv --type execute' "$RELEASE_SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
Executable
+417
@@ -0,0 +1,417 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Description: create a patched Syncthing release from the latest upstream tag.
|
||||
# Usage: ./scripts/update-custom-release.sh
|
||||
|
||||
upstream_url="${CUSTOM_RELEASE_UPSTREAM_URL:-https://github.com/syncthing/syncthing.git}"
|
||||
upstream_tag="${CUSTOM_RELEASE_UPSTREAM_TAG:-}"
|
||||
suffix="${CUSTOM_RELEASE_SUFFIX:-stignore.7}"
|
||||
branch_prefix="${CUSTOM_RELEASE_BRANCH_PREFIX:-custom}"
|
||||
dist_dir="${CUSTOM_RELEASE_DIST_DIR:-dist}"
|
||||
target="${CUSTOM_RELEASE_TARGET:-syncthing}"
|
||||
archive_kind="${CUSTOM_RELEASE_ARCHIVE:-tar}"
|
||||
build_specs="${CUSTOM_RELEASE_BUILDS:-}"
|
||||
default_cgo_enabled="${CUSTOM_RELEASE_CGO_ENABLED:-0}"
|
||||
codesign_identity="${CUSTOM_RELEASE_CODESIGN_IDENTITY:-}"
|
||||
codesign_team_id="${CUSTOM_RELEASE_CODESIGN_TEAM_ID:-NG5W75WE8U}"
|
||||
sign_darwin="${CUSTOM_RELEASE_SIGN_DARWIN:-1}"
|
||||
require_gatekeeper_assessment="${CUSTOM_RELEASE_REQUIRE_GATEKEEPER_ASSESSMENT:-0}"
|
||||
push_release="${CUSTOM_RELEASE_PUSH:-0}"
|
||||
push_branch="${CUSTOM_RELEASE_PUSH_BRANCH:-0}"
|
||||
push_remote="${CUSTOM_RELEASE_REMOTE:-origin}"
|
||||
run_tests="${CUSTOM_RELEASE_TEST:-1}"
|
||||
force="${CUSTOM_RELEASE_FORCE:-0}"
|
||||
publish_gitea_release="${CUSTOM_RELEASE_CREATE_GITEA_RELEASE:-0}"
|
||||
tea_repo="${CUSTOM_RELEASE_TEA_REPO:-}"
|
||||
patch_tmp_dir=""
|
||||
patch_files=()
|
||||
assets_rebuilt=0
|
||||
|
||||
if [[ -n "${CUSTOM_RELEASE_PATCHES:-}" ]]; then
|
||||
read -r -a patch_files <<< "$CUSTOM_RELEASE_PATCHES"
|
||||
elif [[ -n "${CUSTOM_RELEASE_PATCH:-}" ]]; then
|
||||
patch_files=("$CUSTOM_RELEASE_PATCH")
|
||||
else
|
||||
patch_files=(
|
||||
patches/sync-stignore.patch
|
||||
patches/webui-build-marker.patch
|
||||
)
|
||||
fi
|
||||
|
||||
log() {
|
||||
printf '%s\n' "$*"
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_clean_worktree() {
|
||||
if [[ -n "$(git status --porcelain)" ]]; then
|
||||
die "working tree has uncommitted changes"
|
||||
fi
|
||||
}
|
||||
|
||||
latest_stable_tag() {
|
||||
git ls-remote --refs --tags --sort='version:refname' "$upstream_url" 'v[0-9]*' \
|
||||
| awk '{ tag = $2; sub("refs/tags/", "", tag); if (tag ~ /^v[0-9]+\.[0-9]+\.[0-9]+$/) latest = tag } END { print latest }'
|
||||
}
|
||||
|
||||
tag_exists() {
|
||||
local tag="$1"
|
||||
|
||||
if git rev-parse --quiet --verify "refs/tags/$tag" >/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
git ls-remote --quiet --exit-code --tags "$push_remote" "refs/tags/$tag" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
copy_patches_to_temp() {
|
||||
local patch_tmp_dir="$1"
|
||||
local patch_file
|
||||
local patch_name
|
||||
local patch_index=0
|
||||
|
||||
mkdir -p "$patch_tmp_dir"
|
||||
for patch_file in "${patch_files[@]}"; do
|
||||
[[ -f "$patch_file" ]] || die "patch file not found: $patch_file"
|
||||
printf -v patch_name '%03d-%s' "$patch_index" "$(basename "$patch_file")"
|
||||
cp "$patch_file" "$patch_tmp_dir/$patch_name"
|
||||
patch_index=$((patch_index + 1))
|
||||
done
|
||||
}
|
||||
|
||||
fetch_upstream_tag() {
|
||||
local tag="$1"
|
||||
|
||||
git fetch --force "$upstream_url" "refs/tags/$tag:refs/tags/$tag"
|
||||
}
|
||||
|
||||
create_release_commit() {
|
||||
local tag="$1"
|
||||
local custom_tag="$2"
|
||||
local branch="$3"
|
||||
local patch_tmp_dir="$4"
|
||||
local patch_file
|
||||
|
||||
git checkout -B "$branch" "$tag"
|
||||
rm -rf "$dist_dir"
|
||||
for patch_file in "$patch_tmp_dir"/*; do
|
||||
[[ -f "$patch_file" ]] || continue
|
||||
log "Applying $(basename "$patch_file")"
|
||||
git apply --3way "$patch_file"
|
||||
done
|
||||
remove_upstream_workflows
|
||||
git add -A
|
||||
git commit -m "apply local Syncthing patches for $tag"
|
||||
git tag -a "$custom_tag" -m "Syncthing $tag with local patches"
|
||||
}
|
||||
|
||||
remove_upstream_workflows() {
|
||||
local workflow_dir
|
||||
|
||||
for workflow_dir in .github/workflows .gitea/workflows; do
|
||||
if [[ -e "$workflow_dir" ]]; then
|
||||
log "Removing upstream workflow directory $workflow_dir from release commit"
|
||||
rm -rf "$workflow_dir"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
delete_local_tag_if_forced() {
|
||||
local tag="$1"
|
||||
|
||||
if [[ "$force" != "1" ]]; then
|
||||
return
|
||||
fi
|
||||
if git rev-parse --quiet --verify "refs/tags/$tag" >/dev/null; then
|
||||
git tag -d "$tag"
|
||||
fi
|
||||
}
|
||||
|
||||
test_release() {
|
||||
if [[ "$run_tests" != "1" ]]; then
|
||||
log "Skipping tests because CUSTOM_RELEASE_TEST=$run_tests"
|
||||
return
|
||||
fi
|
||||
|
||||
rebuild_assets_once
|
||||
go test ./lib/api/auto ./lib/fs ./lib/ignore ./lib/scanner ./lib/model
|
||||
}
|
||||
|
||||
rebuild_assets_once() {
|
||||
if [[ "$assets_rebuilt" == "1" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log "Regenerating embedded GUI assets"
|
||||
go run build.go assets
|
||||
assets_rebuilt=1
|
||||
}
|
||||
|
||||
format_build_specs() {
|
||||
local spec
|
||||
|
||||
for spec in $build_specs; do
|
||||
printf -- '- %s\n' "$spec"
|
||||
done
|
||||
}
|
||||
|
||||
build_release() {
|
||||
local custom_tag="$1"
|
||||
|
||||
rebuild_assets_once
|
||||
rm -rf "$dist_dir"
|
||||
mkdir -p "$dist_dir"
|
||||
|
||||
if [[ -z "$build_specs" ]]; then
|
||||
build_specs="$(go env GOOS)/$(go env GOARCH)/$archive_kind"
|
||||
fi
|
||||
|
||||
local spec
|
||||
for spec in $build_specs; do
|
||||
build_one "$custom_tag" "$spec"
|
||||
done
|
||||
|
||||
cat > "$dist_dir/release-notes.md" <<EOF
|
||||
# $custom_tag
|
||||
|
||||
Syncthing $upstream_tag with the local patches applied.
|
||||
|
||||
Builds:
|
||||
$(format_build_specs)
|
||||
|
||||
Patches:
|
||||
$(printf -- '- %s\n' "${patch_files[@]}")
|
||||
|
||||
Patch behavior:
|
||||
- Root-level .stignore syncs like regular folder content.
|
||||
- .stfolder and .stversions stay protected as Syncthing internals.
|
||||
- The web GUI footer includes "It syncs .stignore now!" as a custom build marker.
|
||||
EOF
|
||||
|
||||
(
|
||||
cd "$dist_dir"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum ./* > SHA256SUMS
|
||||
else
|
||||
shasum -a 256 ./* > SHA256SUMS
|
||||
fi
|
||||
)
|
||||
}
|
||||
|
||||
build_one() {
|
||||
local custom_tag="$1"
|
||||
local spec="$2"
|
||||
local goos
|
||||
local goarch
|
||||
local kind
|
||||
local cgo_enabled
|
||||
|
||||
IFS=/ read -r goos goarch kind cgo_enabled <<< "$spec"
|
||||
[[ -n "$goos" && -n "$goarch" && -n "$kind" ]] || die "invalid build spec: $spec"
|
||||
cgo_enabled="${cgo_enabled:-$default_cgo_enabled}"
|
||||
|
||||
log "Building $target for $goos/$goarch as $kind with CGO_ENABLED=$cgo_enabled"
|
||||
|
||||
case "$kind" in
|
||||
tar|zip)
|
||||
local archive
|
||||
archive="$(CGO_ENABLED="$cgo_enabled" go run build.go -version "$custom_tag" -goos "$goos" -goarch "$goarch" "$kind" "$target" | tail -n 1)"
|
||||
if [[ "$goos" == "darwin" && "$sign_darwin" == "1" ]]; then
|
||||
sign_and_validate_darwin_archive "$archive" "$kind"
|
||||
fi
|
||||
mv "$archive" "$dist_dir/"
|
||||
;;
|
||||
binary)
|
||||
CGO_ENABLED="$cgo_enabled" go run build.go -version "$custom_tag" -goos "$goos" -goarch "$goarch" -build-out "$dist_dir/$target-$goos-$goarch" build "$target"
|
||||
if [[ "$goos" == "darwin" && "$sign_darwin" == "1" ]]; then
|
||||
sign_and_validate_darwin_binary "$dist_dir/$target-$goos-$goarch"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
die "unknown build archive kind in $spec"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
find_release_binary() {
|
||||
local root="$1"
|
||||
local candidate
|
||||
|
||||
while IFS= read -r candidate; do
|
||||
if [[ -x "$candidate" ]]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return
|
||||
fi
|
||||
done < <(find "$root" -type f -name "$target")
|
||||
|
||||
die "could not find executable $target in $root"
|
||||
}
|
||||
|
||||
sign_and_validate_darwin_archive() {
|
||||
local archive="$1"
|
||||
local kind="$2"
|
||||
local tmp
|
||||
local archive_abs
|
||||
local binary
|
||||
|
||||
[[ -n "$codesign_identity" ]] || die "CUSTOM_RELEASE_CODESIGN_IDENTITY is required for darwin builds"
|
||||
|
||||
tmp="$(mktemp -d)"
|
||||
archive_abs="$(cd "$(dirname "$archive")" && pwd -P)/$(basename "$archive")"
|
||||
case "$kind" in
|
||||
zip)
|
||||
unzip -q "$archive_abs" -d "$tmp"
|
||||
;;
|
||||
tar)
|
||||
tar -xf "$archive_abs" -C "$tmp"
|
||||
;;
|
||||
*)
|
||||
die "cannot sign archive kind $kind"
|
||||
;;
|
||||
esac
|
||||
|
||||
binary="$(find_release_binary "$tmp")"
|
||||
sign_and_validate_darwin_binary "$binary"
|
||||
|
||||
rm -f "$archive_abs"
|
||||
case "$kind" in
|
||||
zip)
|
||||
(
|
||||
cd "$tmp"
|
||||
zip -qr "$archive_abs" .
|
||||
)
|
||||
;;
|
||||
tar)
|
||||
(
|
||||
cd "$tmp"
|
||||
tar -czf "$archive_abs" .
|
||||
)
|
||||
;;
|
||||
esac
|
||||
rm -rf "$tmp"
|
||||
}
|
||||
|
||||
sign_and_validate_darwin_binary() {
|
||||
local binary="$1"
|
||||
local version_output
|
||||
local codesign_details
|
||||
local codesign_args
|
||||
|
||||
[[ -n "$codesign_identity" ]] || die "CUSTOM_RELEASE_CODESIGN_IDENTITY is required for darwin builds"
|
||||
|
||||
codesign_args=(--force --sign "$codesign_identity")
|
||||
if [[ -n "${CUSTOM_RELEASE_KEYCHAIN_PATH:-}" ]]; then
|
||||
if [[ -n "${CUSTOM_RELEASE_KEYCHAIN_PASSWORD:-}" ]]; then
|
||||
security unlock-keychain -p "$CUSTOM_RELEASE_KEYCHAIN_PASSWORD" "$CUSTOM_RELEASE_KEYCHAIN_PATH"
|
||||
fi
|
||||
security find-identity -v -p codesigning "$CUSTOM_RELEASE_KEYCHAIN_PATH"
|
||||
security find-identity -v -p codesigning
|
||||
codesign_args+=(--keychain "$CUSTOM_RELEASE_KEYCHAIN_PATH")
|
||||
fi
|
||||
codesign_args+=(--options runtime --timestamp)
|
||||
codesign "${codesign_args[@]}" "$binary"
|
||||
|
||||
version_output="$("$binary" --version)"
|
||||
if [[ "$version_output" == *modernc-sqlite* ]]; then
|
||||
die "darwin build unexpectedly reports [modernc-sqlite]: $version_output"
|
||||
fi
|
||||
|
||||
codesign --verify --strict --verbose=2 "$binary"
|
||||
codesign_details="$(codesign -dv --verbose=4 "$binary" 2>&1)"
|
||||
if [[ "$codesign_team_id" == "NG5W75WE8U" && "$codesign_details" != *"TeamIdentifier=NG5W75WE8U"* ]]; then
|
||||
printf '%s\n' "$codesign_details" >&2
|
||||
die "darwin build is not signed by TeamIdentifier=NG5W75WE8U"
|
||||
fi
|
||||
if [[ "$codesign_details" != *"TeamIdentifier=$codesign_team_id"* ]]; then
|
||||
printf '%s\n' "$codesign_details" >&2
|
||||
die "darwin build is not signed by TeamIdentifier=$codesign_team_id"
|
||||
fi
|
||||
if [[ "$require_gatekeeper_assessment" == "1" ]]; then
|
||||
spctl -a -vv --type execute "$binary"
|
||||
else
|
||||
log "Skipping Gatekeeper assessment because CUSTOM_RELEASE_REQUIRE_GATEKEEPER_ASSESSMENT=$require_gatekeeper_assessment"
|
||||
fi
|
||||
}
|
||||
|
||||
push_refs() {
|
||||
local branch="$1"
|
||||
local custom_tag="$2"
|
||||
|
||||
if [[ "$push_release" != "1" ]]; then
|
||||
log "Skipping push because CUSTOM_RELEASE_PUSH=$push_release"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$push_branch" == "1" ]]; then
|
||||
git push "$push_remote" "$branch"
|
||||
else
|
||||
log "Skipping release branch push because CUSTOM_RELEASE_PUSH_BRANCH=$push_branch"
|
||||
fi
|
||||
git push "$push_remote" "$custom_tag"
|
||||
}
|
||||
|
||||
publish_release() {
|
||||
local custom_tag="$1"
|
||||
|
||||
if [[ "$publish_gitea_release" != "1" ]]; then
|
||||
log "Skipping Gitea release publishing because CUSTOM_RELEASE_CREATE_GITEA_RELEASE=$publish_gitea_release"
|
||||
return
|
||||
fi
|
||||
|
||||
command -v tea >/dev/null 2>&1 || die "tea is required for Gitea release publishing"
|
||||
|
||||
local args=(releases create "$custom_tag" --title "$custom_tag" --note-file "$dist_dir/release-notes.md")
|
||||
if [[ -n "$tea_repo" ]]; then
|
||||
args+=(--repo "$tea_repo")
|
||||
else
|
||||
args+=(--remote "$push_remote")
|
||||
fi
|
||||
|
||||
local asset
|
||||
for asset in "$dist_dir"/*; do
|
||||
[[ -f "$asset" ]] || continue
|
||||
[[ "$(basename "$asset")" == "release-notes.md" ]] && continue
|
||||
args+=(--asset "$asset")
|
||||
done
|
||||
|
||||
tea "${args[@]}"
|
||||
}
|
||||
|
||||
main() {
|
||||
require_clean_worktree
|
||||
|
||||
if [[ -z "$upstream_tag" ]]; then
|
||||
upstream_tag="$(latest_stable_tag)"
|
||||
fi
|
||||
[[ -n "$upstream_tag" ]] || die "could not determine latest upstream tag"
|
||||
|
||||
local custom_tag="${upstream_tag}-${suffix}"
|
||||
local branch="${branch_prefix}/${upstream_tag#v}-${suffix}"
|
||||
|
||||
if tag_exists "$custom_tag" && [[ "$force" != "1" ]]; then
|
||||
log "Custom release $custom_tag already exists; nothing to do."
|
||||
return
|
||||
fi
|
||||
|
||||
patch_tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$patch_tmp_dir"' EXIT
|
||||
|
||||
copy_patches_to_temp "$patch_tmp_dir"
|
||||
fetch_upstream_tag "$upstream_tag"
|
||||
delete_local_tag_if_forced "$custom_tag"
|
||||
create_release_commit "$upstream_tag" "$custom_tag" "$branch" "$patch_tmp_dir"
|
||||
test_release
|
||||
build_release "$custom_tag"
|
||||
push_refs "$branch" "$custom_tag"
|
||||
publish_release "$custom_tag"
|
||||
|
||||
log "Built custom release $custom_tag from upstream $upstream_tag"
|
||||
log "Artifacts are in $dist_dir/"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user