From b81829012a33fd7478aa117ef1819ef51c183c55 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Mon, 19 Oct 2026 15:02:33 +0100 Subject: [PATCH] new scripts --- .gitignore | 2 + Scripts/build.sh | 71 +++++++-- Scripts/old/license-keygen.swift | 28 ---- Scripts/old/license-sign.swift | 71 --------- Scripts/old/release-preflight.sh | 41 ------ Scripts/old/release.sh | 206 --------------------------- Scripts/old/security-check.sh | 40 ------ Scripts/old/verify-release-config.sh | 51 ------- Scripts/old/verify-signed-app.sh | 44 ------ Scripts/old/verify-stage4-server.sh | 19 --- Scripts/old/verify-stage5-server.sh | 12 -- Scripts/release.sh | 29 ++-- 12 files changed, 75 insertions(+), 539 deletions(-) mode change 100644 => 100755 Scripts/build.sh delete mode 100755 Scripts/old/license-keygen.swift delete mode 100755 Scripts/old/license-sign.swift delete mode 100755 Scripts/old/release-preflight.sh delete mode 100755 Scripts/old/release.sh delete mode 100755 Scripts/old/security-check.sh delete mode 100755 Scripts/old/verify-release-config.sh delete mode 100755 Scripts/old/verify-signed-app.sh delete mode 100755 Scripts/old/verify-stage4-server.sh delete mode 100755 Scripts/old/verify-stage5-server.sh mode change 100644 => 100755 Scripts/release.sh diff --git a/.gitignore b/.gitignore index 452d03b..7180e29 100755 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ build/ DerivedData/ *.xcuserstate xcuserdata/ +/releases/ +/.build/ diff --git a/Scripts/build.sh b/Scripts/build.sh old mode 100644 new mode 100755 index f4c4756..91d850f --- a/Scripts/build.sh +++ b/Scripts/build.sh @@ -1,22 +1,65 @@ -#!/usr/bin/env bash +#!/bin/bash set -euo pipefail -cd /Volumes/Developer/Applications/DbSyn +# Resolve script location under Bash or Zsh. +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -# Remove previous build -rm -rf ./build +cd "$PROJECT_ROOT" + +# Find the Xcode project. +PROJECT="$(find "$PROJECT_ROOT" -maxdepth 2 -name '*.xcodeproj' -type d -print -quit)" + +if [[ -z "$PROJECT" ]]; then + echo "ERROR: No Xcode project found in $PROJECT_ROOT" + exit 1 +fi + +echo "Project: $PROJECT" + +# Detect the application scheme. +SCHEME="$(xcodebuild -list -json -project "$PROJECT" | python3 -c ' +import json +import sys + +data = json.load(sys.stdin) +schemes = data.get("project", {}).get("schemes", []) + +if not schemes: + sys.exit("ERROR: No Xcode schemes found") + +print(schemes[0]) +')" + +if [[ -z "$SCHEME" ]]; then + echo "ERROR: Could not detect Xcode scheme" + exit 1 +fi + +echo "Scheme: $SCHEME" + +DERIVED_DATA="$PROJECT_ROOT/.build" + +echo "Building Rsync Studio..." -# Clean and build DBSyn xcodebuild \ - -project DBSyn.xcodeproj \ - -scheme DBSyn \ + -project "$PROJECT" \ + -scheme "$SCHEME" \ -configuration Debug \ - -destination 'platform=macOS,arch=arm64' \ - -derivedDataPath ./build \ - clean build + -derivedDataPath "$DERIVED_DATA" \ + CODE_SIGNING_ALLOWED=NO \ + build -# Stop any running instance -pkill -x DBSyn 2>/dev/null || true +APP="$(find "$DERIVED_DATA/Build/Products/Debug" -maxdepth 1 -name '*.app' -type d -print -quit)" -# Launch the newly built application -open ./build/Build/Products/Debug/DBSyn.app \ No newline at end of file +if [[ -z "$APP" ]]; then + echo "ERROR: Built application not found" + exit 1 +fi + +echo "Build successful: $APP" + +if [[ "${LAUNCH_AFTER_BUILD:-1}" == "1" ]]; then + echo "Launching application..." + open "$APP" +fi diff --git a/Scripts/old/license-keygen.swift b/Scripts/old/license-keygen.swift deleted file mode 100755 index d0df42b..0000000 --- a/Scripts/old/license-keygen.swift +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env swift -import Foundation -import CryptoKit - -let fm = FileManager.default -let home = fm.homeDirectoryForCurrentUser -let directory = home.appendingPathComponent("Library/Application Support/Rsync Studio Licensing", isDirectory: true) -let privateURL = directory.appendingPathComponent("license-private-key.txt") -let publicURL = directory.appendingPathComponent("license-public-key.txt") - -try fm.createDirectory(at: directory, withIntermediateDirectories: true) -if fm.fileExists(atPath: privateURL.path) || fm.fileExists(atPath: publicURL.path) { - fputs("Refusing to overwrite an existing licensing keypair at \(directory.path)\n", stderr) - exit(1) -} - -let privateKey = Curve25519.Signing.PrivateKey() -let privateText = privateKey.rawRepresentation.base64EncodedString() -let publicText = privateKey.publicKey.rawRepresentation.base64EncodedString() -try privateText.write(to: privateURL, atomically: true, encoding: .utf8) -try publicText.write(to: publicURL, atomically: true, encoding: .utf8) -try fm.setAttributes([.posixPermissions: 0o600], ofItemAtPath: privateURL.path) -try fm.setAttributes([.posixPermissions: 0o644], ofItemAtPath: publicURL.path) - -print("Created Rsync Studio Ed25519 licensing keypair.") -print("PRIVATE (KEEP SECRET): \(privateURL.path)") -print("PUBLIC: \(publicURL.path)") -print("\nPublic key for RsyncStudioLicensePublicKey:\n\(publicText)") diff --git a/Scripts/old/license-sign.swift b/Scripts/old/license-sign.swift deleted file mode 100755 index f827cca..0000000 --- a/Scripts/old/license-sign.swift +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env swift -import Foundation -import CryptoKit - -struct Payload: Codable { - let licenseID: String - let customerName: String? - let customerEmail: String? - let edition: String - let installationID: String - let issuedAt: Date - let expiresAt: Date? - let maxMajorVersion: Int? -} - -func base64URL(_ data: Data) -> String { - data.base64EncodedString() - .replacingOccurrences(of: "+", with: "-") - .replacingOccurrences(of: "/", with: "_") - .replacingOccurrences(of: "=", with: "") -} - -let args = CommandLine.arguments -func value(_ flag: String) -> String? { - guard let index = args.firstIndex(of: flag), index + 1 < args.count else { return nil } - return args[index + 1] -} - -guard let installationID = value("--installation-id"), UUID(uuidString: installationID) != nil else { - fputs("Usage: swift Scripts/license-sign.swift --installation-id UUID [--name NAME] [--email EMAIL] [--edition Professional] [--license-id ID] [--max-major 1] [--expires YYYY-MM-DD]\n", stderr) - exit(2) -} - -let fm = FileManager.default -let defaultPrivate = fm.homeDirectoryForCurrentUser.appendingPathComponent("Library/Application Support/Rsync Studio Licensing/license-private-key.txt") -let privateURL = value("--private-key").map { URL(fileURLWithPath: $0) } ?? defaultPrivate -let privateText = try String(contentsOf: privateURL, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines) -guard let privateData = Data(base64Encoded: privateText) else { throw NSError(domain: "RsyncStudioLicensing", code: 1, userInfo: [NSLocalizedDescriptionKey: "Private key is not Base64."]) } -let privateKey = try Curve25519.Signing.PrivateKey(rawRepresentation: privateData) - -var expiry: Date? -if let text = value("--expires") { - let formatter = DateFormatter() - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.dateFormat = "yyyy-MM-dd" - formatter.timeZone = TimeZone(secondsFromGMT: 0) - guard let date = formatter.date(from: text) else { - fputs("Invalid --expires date; use YYYY-MM-DD.\n", stderr) - exit(2) - } - expiry = date -} - -let payload = Payload( - licenseID: value("--license-id") ?? UUID().uuidString.lowercased(), - customerName: value("--name"), - customerEmail: value("--email"), - edition: value("--edition") ?? "Professional", - installationID: installationID.lowercased(), - issuedAt: Date(), - expiresAt: expiry, - maxMajorVersion: value("--max-major").flatMap(Int.init) ?? 1 -) - -let encoder = JSONEncoder() -encoder.outputFormatting = [.sortedKeys] -encoder.dateEncodingStrategy = .iso8601 -let data = try encoder.encode(payload) -let signature = try privateKey.signature(for: data) -let token = "RS3.\(base64URL(data)).\(base64URL(signature))" -print(token) diff --git a/Scripts/old/release-preflight.sh b/Scripts/old/release-preflight.sh deleted file mode 100755 index 6324507..0000000 --- a/Scripts/old/release-preflight.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PROJECT="$ROOT/Rsync Studio.xcodeproj" -SCHEME="Rsync Studio" - -if [[ "$(uname -s)" != "Darwin" ]]; then - echo "release-preflight.sh must run on macOS with Xcode installed." >&2 - exit 2 -fi - -for tool in xcodebuild plutil codesign spctl; do - command -v "$tool" >/dev/null || { echo "Missing required tool: $tool" >&2; exit 2; } -done - -"$SCRIPT_DIR/verify-release-config.sh" -"$SCRIPT_DIR/security-check.sh" -if ! grep -Eq 'INFOPLIST_KEY_RsyncStudioLicensePublicKey = "[A-Za-z0-9+/_=-]{40,}";' "$PROJECT/project.pbxproj"; then - echo "RsyncStudioLicensePublicKey is not configured. Generate the licensing keypair and embed the PUBLIC key before a commercial release." >&2 - exit 1 -fi -plutil -lint "$ROOT/Rsync Studio/Resources/PrivacyInfo.xcprivacy" - -BUILD_DIR="$ROOT/build/preflight" -rm -rf "$BUILD_DIR" - -xcodebuild \ - -project "$PROJECT" \ - -scheme "$SCHEME" \ - -configuration Release \ - -destination 'platform=macOS' \ - -derivedDataPath "$BUILD_DIR" \ - CODE_SIGNING_ALLOWED=NO \ - clean build - -APP="$BUILD_DIR/Build/Products/Release/Rsync Studio.app" -test -d "$APP" || { echo "Release app was not produced at $APP" >&2; exit 1; } - -echo "Unsigned Release build passed: $APP" -echo "Next: Archive with Developer ID, notarise, staple, then run the signed-app checks in Docs/RELEASE.md." diff --git a/Scripts/old/release.sh b/Scripts/old/release.sh deleted file mode 100755 index 327c28f..0000000 --- a/Scripts/old/release.sh +++ /dev/null @@ -1,206 +0,0 @@ -#!/bin/bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PROJECT="$ROOT/Rsync Studio.xcodeproj" -SCHEME="Rsync Studio" -CONFIGURATION="Release" -PRODUCT_NAME="Rsync Studio" -DISPLAY_NAME="Rsync Studio" -BUNDLE_ID="net.absdev.RsyncStudio" -NOTARY_PROFILE="${NOTARY_PROFILE:-Rsync Studio Notary}" -RELEASE_ROOT="${RELEASE_ROOT:-$ROOT/build/releases}" -SKIP_PREFLIGHT=0 -SKIP_NOTARIZATION=0 - -usage() { - cat <&2; exit 2; }; NOTARY_PROFILE="$2"; shift 2 ;; - --output) [[ $# -ge 2 ]] || { echo "Missing value for $1" >&2; exit 2; }; RELEASE_ROOT="$2"; shift 2 ;; - --skip-preflight) SKIP_PREFLIGHT=1; shift ;; - --skip-notarization) SKIP_NOTARIZATION=1; shift ;; - -h|--help) usage; exit 0 ;; - *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; - esac -done - -[[ "$(uname -s)" == "Darwin" ]] || { echo "ERROR: release.sh must run on macOS." >&2; exit 2; } -for tool in xcodebuild xcrun hdiutil codesign spctl plutil shasum ditto; do - command -v "$tool" >/dev/null || { echo "ERROR: Missing required tool: $tool" >&2; exit 2; } -done - -PBX="$PROJECT/project.pbxproj" -VERSION="$(awk -F' = ' '/MARKETING_VERSION = / {gsub(/;/,"",$2); print $2; exit}' "$PBX")" -BUILD="$(awk -F' = ' '/CURRENT_PROJECT_VERSION = / {gsub(/;/,"",$2); print $2; exit}' "$PBX")" -[[ -n "$VERSION" && -n "$BUILD" ]] || { echo "ERROR: Could not determine version/build from Xcode project." >&2; exit 1; } - -RELEASE_DIR="$RELEASE_ROOT/$VERSION-$BUILD" -ARCHIVE="$RELEASE_DIR/$PRODUCT_NAME.xcarchive" -EXPORT_DIR="$RELEASE_DIR/export" -DMG_STAGING="$RELEASE_DIR/dmg-root" -DMG="$RELEASE_DIR/$PRODUCT_NAME-$VERSION.dmg" -CHECKSUM="$DMG.sha256" -METADATA="$RELEASE_DIR/release-info.txt" -EXPORT_OPTIONS="$RELEASE_DIR/ExportOptions.plist" - -if [[ -e "$RELEASE_DIR" ]]; then - echo "ERROR: Release directory already exists: $RELEASE_DIR" >&2 - echo "Refusing to overwrite an existing version/build artifact." >&2 - exit 1 -fi -mkdir -p "$RELEASE_DIR" - -cleanup() { - rm -rf "$DMG_STAGING" -} -trap cleanup EXIT - -if [[ "$SKIP_PREFLIGHT" -eq 0 ]]; then - "$SCRIPT_DIR/release-preflight.sh" -fi - -cat > "$EXPORT_OPTIONS" < - - - - method - developer-id - signingStyle - automatic - stripSwiftSymbols - - - -PLIST -plutil -lint "$EXPORT_OPTIONS" >/dev/null - -XCODE_SIGN_ARGS=() -if [[ -n "${DEVELOPMENT_TEAM:-}" ]]; then - XCODE_SIGN_ARGS+=("DEVELOPMENT_TEAM=$DEVELOPMENT_TEAM") -fi - -echo "== Archive $DISPLAY_NAME $VERSION ($BUILD) ==" -xcodebuild \ - -project "$PROJECT" \ - -scheme "$SCHEME" \ - -configuration "$CONFIGURATION" \ - -destination 'generic/platform=macOS' \ - -archivePath "$ARCHIVE" \ - clean archive \ - "${XCODE_SIGN_ARGS[@]}" - -echo "== Export Developer ID app ==" -xcodebuild \ - -exportArchive \ - -archivePath "$ARCHIVE" \ - -exportPath "$EXPORT_DIR" \ - -exportOptionsPlist "$EXPORT_OPTIONS" \ - -allowProvisioningUpdates - -APP="$EXPORT_DIR/$PRODUCT_NAME.app" -[[ -d "$APP" ]] || { echo "ERROR: Exported app not found: $APP" >&2; exit 1; } - -ACTUAL_BUNDLE_ID="$(plutil -extract CFBundleIdentifier raw -o - "$APP/Contents/Info.plist" 2>/dev/null || true)" -[[ "$ACTUAL_BUNDLE_ID" == "$BUNDLE_ID" ]] || { echo "ERROR: Exported bundle id is '$ACTUAL_BUNDLE_ID', expected '$BUNDLE_ID'." >&2; exit 1; } -"$SCRIPT_DIR/verify-signed-app.sh" "$APP" - -if [[ "$SKIP_NOTARIZATION" -eq 0 ]]; then - echo "== Validate notarization credentials ==" - xcrun notarytool history --keychain-profile "$NOTARY_PROFILE" >/dev/null - - echo "== Notarize exported app ==" - APP_ZIP="$RELEASE_DIR/$PRODUCT_NAME-$VERSION-notary.zip" - ditto -c -k --keepParent "$APP" "$APP_ZIP" - xcrun notarytool submit "$APP_ZIP" --keychain-profile "$NOTARY_PROFILE" --wait - xcrun stapler staple "$APP" - xcrun stapler validate "$APP" - rm -f "$APP_ZIP" - "$SCRIPT_DIR/verify-signed-app.sh" "$APP" -fi - -echo "== Create drag-to-Applications DMG ==" -mkdir -p "$DMG_STAGING" -ditto "$APP" "$DMG_STAGING/$PRODUCT_NAME.app" -ln -s /Applications "$DMG_STAGING/Applications" -cat > "$DMG_STAGING/README.txt" </dev/null - -hdiutil verify "$DMG" >/dev/null - -if [[ "$SKIP_NOTARIZATION" -eq 0 ]]; then - echo "== Notarize final DMG ==" - xcrun notarytool submit "$DMG" --keychain-profile "$NOTARY_PROFILE" --wait - xcrun stapler staple "$DMG" - xcrun stapler validate "$DMG" - - echo "== Final Gatekeeper verification ==" - "$SCRIPT_DIR/verify-signed-app.sh" "$APP" - spctl --assess --type open --context context:primary-signature --verbose=4 "$DMG" -else - echo "WARNING: Notarization was skipped. This output is NOT ready for public distribution." >&2 -fi - -printf '%s %s\n' "$(shasum -a 256 "$DMG" | awk '{print $1}')" "$(basename "$DMG")" > "$CHECKSUM" - -DMG_SIZE="$(stat -f '%z' "$DMG")" -DMG_SHA256="$(awk '{print $1}' "$CHECKSUM")" -cat > "$METADATA" <&2 - exit 1 -fi - -grep -q 'BatchMode=yes' "$ROOT/Rsync Studio/Services/RsyncCommandBuilder.swift" -grep -q 'allowUnattendedDelete' "$ROOT/Rsync Studio/Models/SyncJob.swift" -grep -q 'RsyncSecurityValidator' "$ROOT/Rsync Studio/Services/RsyncCommandBuilder.swift" -grep -q 'isSafeRemotePath' "$ROOT/Rsync Studio/Services/RsyncCommandBuilder.swift" -grep -q 'hasBalancedShellSyntax' "$ROOT/Rsync Studio/Services/RsyncCommandBuilder.swift" -grep -q -- '--files-from' "$ROOT/Rsync Studio/Services/RsyncCommandBuilder.swift" -grep -q -- '--log-file' "$ROOT/Rsync Studio/Services/RsyncCommandBuilder.swift" - -grep -q 'final class LicensingManager' "$ROOT/Rsync Studio/Services/LicensingManager.swift" -grep -q 'trialLengthDays = 30' "$ROOT/Rsync Studio/Services/LicensingManager.swift" -grep -q 'Curve25519.Signing.PublicKey' "$ROOT/Rsync Studio/Services/LicensingManager.swift" -grep -q 'RsyncStudioLicensePublicKey' "$PROJECT/project.pbxproj" -if find "$ROOT" -type f \( -iname '*private*key*' -o -iname '*.p8' \) ! -path '*/build/*' | grep -q .; then - echo "A private signing key appears to be present in the project tree." >&2 - exit 1 -fi - -echo "Static policy checks passed." -echo "" -echo "After creating a signed Release app, also run:" -echo " codesign --verify --deep --strict --verbose=2 /path/to/Rsync Studio.app" -echo " spctl --assess --type execute --verbose=4 /path/to/Rsync Studio.app" -echo " xcrun stapler validate /path/to/Rsync Studio.app" diff --git a/Scripts/old/verify-release-config.sh b/Scripts/old/verify-release-config.sh deleted file mode 100755 index b77f417..0000000 --- a/Scripts/old/verify-release-config.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PBX="$ROOT/Rsync Studio.xcodeproj/project.pbxproj" -EXPECTED_BUNDLE_ID="net.absdev.RsyncStudio" -EXPECTED_VERSION="1.0.0" -EXPECTED_BUILD="1" -EXPECTED_MIN_MACOS="14.0" - -fail() { echo "ERROR: $*" >&2; exit 1; } -pass() { echo "OK: $*"; } - -[[ -f "$PBX" ]] || fail "Missing Xcode project file" - -[[ $(grep -c "PRODUCT_BUNDLE_IDENTIFIER = $EXPECTED_BUNDLE_ID;" "$PBX") -ge 2 ]] || fail "Bundle identifier must remain $EXPECTED_BUNDLE_ID in all app configurations" -pass "Bundle identifier is $EXPECTED_BUNDLE_ID" - -[[ $(grep -c "MARKETING_VERSION = $EXPECTED_VERSION;" "$PBX") -ge 2 ]] || fail "Marketing version must be $EXPECTED_VERSION" -pass "Marketing version is $EXPECTED_VERSION" - -[[ $(grep -c "CURRENT_PROJECT_VERSION = $EXPECTED_BUILD;" "$PBX") -ge 2 ]] || fail "Build number must be $EXPECTED_BUILD" -pass "Build number is $EXPECTED_BUILD" - -[[ $(grep -c 'ENABLE_HARDENED_RUNTIME = YES;' "$PBX") -ge 2 ]] || fail "Hardened Runtime must be enabled" -pass "Hardened Runtime is enabled" - -[[ $(grep -c 'ENABLE_APP_SANDBOX = NO;' "$PBX") -ge 2 ]] || fail "App Sandbox baseline changed unexpectedly" -pass "App Sandbox remains intentionally disabled for direct distribution" - -[[ $(grep -c "MACOSX_DEPLOYMENT_TARGET = $EXPECTED_MIN_MACOS;" "$PBX") -ge 2 ]] || fail "macOS deployment target must remain $EXPECTED_MIN_MACOS" -pass "Minimum macOS is $EXPECTED_MIN_MACOS" - -[[ $(grep -c 'INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";' "$PBX") -ge 2 ]] || fail "Application category is missing" -pass "Application category is Utilities" - -if grep -q 'CODE_SIGN_IDENTITY = "Apple Development"' "$PBX"; then - fail "A project-level Apple Development identity is pinned; Release distribution must use Developer ID Application" -fi -pass "No development certificate is pinned into the project" - -if grep -q 'com.apple.security.get-task-allow' "$ROOT/Rsync Studio"/*.entitlements 2>/dev/null; then - fail "get-task-allow must never ship in a commercial Release build" -fi -pass "No get-task-allow entitlement is present in source entitlements" - -cat <<'MSG' - -Release identity baseline passed. -Before the first commercial archive, select your Apple Developer Team in Xcode and ensure the archived app is signed with a "Developer ID Application" certificate. The exact certificate identity is intentionally not committed to source control. -MSG diff --git a/Scripts/old/verify-signed-app.sh b/Scripts/old/verify-signed-app.sh deleted file mode 100755 index a610719..0000000 --- a/Scripts/old/verify-signed-app.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -set -euo pipefail - -APP="${1:-}" -[[ -n "$APP" ]] || { echo "Usage: $0 /path/to/Rsync Studio.app" >&2; exit 2; } -[[ -d "$APP" ]] || { echo "ERROR: App not found: $APP" >&2; exit 2; } - -for tool in codesign spctl; do - command -v "$tool" >/dev/null || { echo "ERROR: Missing required tool: $tool" >&2; exit 2; } -done - -echo "== Verifying code signature ==" -codesign --verify --deep --strict --verbose=2 "$APP" - -echo "== Inspecting signing authority ==" -SIGNING_INFO="$(codesign -dvvv "$APP" 2>&1)" -printf '%s\n' "$SIGNING_INFO" -grep -q 'Authority=Developer ID Application:' <<<"$SIGNING_INFO" || { - echo "ERROR: App is not signed with Developer ID Application." >&2 - exit 1 -} - -echo "== Checking Hardened Runtime ==" -grep -Eq '^CodeDirectory .* flags=.*runtime' <<<"$SIGNING_INFO" || { - echo "ERROR: Hardened Runtime flag was not found in the signed app." >&2 - exit 1 -} - -echo "== Checking release entitlements ==" -ENTITLEMENTS="$(codesign -d --entitlements :- "$APP" 2>/dev/null || true)" -if grep -q 'com.apple.security.get-task-allow' <<<"$ENTITLEMENTS"; then - echo "ERROR: Release app contains com.apple.security.get-task-allow." >&2 - exit 1 -fi - -if grep -q '' <<<"$(printf '%s\n' "$ENTITLEMENTS" | sed -n '/com.apple.security.app-sandbox/,+1p')"; then - echo "ERROR: App Sandbox is unexpectedly enabled in the direct-distribution build." >&2 - exit 1 -fi - -echo "== Gatekeeper assessment ==" -spctl --assess --type execute --verbose=4 "$APP" - -echo "Signed Developer ID app verification passed: $APP" diff --git a/Scripts/old/verify-stage4-server.sh b/Scripts/old/verify-stage4-server.sh deleted file mode 100755 index 82fc661..0000000 --- a/Scripts/old/verify-stage4-server.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -ROOT="$(cd "$(dirname "$0")/.." && pwd)" -SERVER="$ROOT/rsync-studio-licensing" -[[ -d "$SERVER" ]] || { echo 'Licensing Server directory is missing.' >&2; exit 1; } -if command -v php >/dev/null 2>&1; then - while IFS= read -r -d '' file; do php -l "$file" >/dev/null; done < <(find "$SERVER" -name '*.php' -type f -print0) - echo 'OK: all Stage 4 PHP files pass php -l' -else - echo 'WARN: PHP is unavailable; skipped php -l checks.' -fi -bash -n "$SERVER/install-overlay.sh" -! grep -RniE --exclude='*.md' --exclude='*.example' '(PRIVATE[_ -]?KEY|PADDLE_WEBHOOK_SECRET|LEMON_SQUEEZY_WEBHOOK_SECRET)[[:space:]]*=[[:space:]]*[A-Za-z0-9+/=_-]{20,}' "$SERVER" >/dev/null || { echo 'Possible committed secret detected.' >&2; exit 1; } -grep -q "sodium_crypto_sign_detached" "$SERVER/app/Services/Licensing/LicenseSigner.php" -grep -q "hash_equals" "$SERVER/app/Services/Payments/PaddleWebhookVerifier.php" -grep -q "hash_equals" "$SERVER/app/Services/Payments/LemonSqueezyWebhookVerifier.php" -grep -q "lockForUpdate" "$SERVER/app/Http/Controllers/Api/LicenseActivationController.php" -grep -q "license_delivery_outbox" "$SERVER/database/migrations/2026_09_08_200000_create_rsync_studio_licensing_tables.php" -echo 'Stage 4 licensing-server static checks passed.' diff --git a/Scripts/old/verify-stage5-server.sh b/Scripts/old/verify-stage5-server.sh deleted file mode 100755 index 99b1109..0000000 --- a/Scripts/old/verify-stage5-server.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -ROOT="$(cd "$(dirname "$0")/.." && pwd)" -LS="$ROOT/rsync-studio-licensing" -find "$LS/app" "$LS/config" "$LS/database" "$LS/routes" -name '*.php' -print0 | while IFS= read -r -d '' f; do php -l "$f" >/dev/null; done -grep -q "implements MustVerifyEmail" "$LS/app/Models/User.php" -grep -q "AdminOnly::class" "$LS/routes/web.stage5.php" -grep -q "middleware(\['auth','verified'\])" "$LS/routes/web.stage5.php" -grep -q "api: __DIR__.'/../routes/api.php'" "$LS/bootstrap/app.php" -grep -q "RsyncStudioLicensingServiceProvider::class" "$LS/bootstrap/providers.php" -grep -q "create_rsync_studio_auth_tables" <(find "$LS/database/migrations" -maxdepth 1 -type f -printf '%f\n') -echo 'Stage 5 integrated licensing portal static verification passed.' diff --git a/Scripts/release.sh b/Scripts/release.sh old mode 100644 new mode 100755 index 3c1d085..8753286 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -1,11 +1,11 @@ #!/bin/bash set -euo pipefail -# DBSyn direct distribution: Developer ID signed, notarized app and DMG. -# macOS/Xcode required. Install as scripts/release-dbsyn-direct.sh. -APP_NAME=DBSyn -SCHEME=DBSyn -PROJECT=DBSyn.xcodeproj +# Rsync Studio direct distribution: Developer ID signed, notarized app and DMG. +# macOS/Xcode required. Install as Scripts/release.sh. +APP_NAME="Rsync Studio" +SCHEME="Rsync Studio" +PROJECT="Rsync Studio.xcodeproj" TEAM_ID="${TEAM_ID:-39R6U3437D}" SIGNING_IDENTITY="${SIGNING_IDENTITY:-B99D353C88996866C49E4389B375645F76870A3F}" NOTARY_PROFILE="${NOTARY_PROFILE:-orion-notary}" @@ -49,7 +49,7 @@ cat > "$EXPORT_OPTIONS" < PLIST -echo '==> Archiving DBSyn with hardened runtime...' +echo '==> Archiving Rsync Studio with hardened runtime...' xcodebuild -project "$PROJECT" -scheme "$SCHEME" -configuration Release \ -destination 'generic/platform=macOS' -archivePath "$ARCHIVE_PATH" \ DEVELOPMENT_TEAM="$TEAM_ID" CODE_SIGN_STYLE=Automatic \ @@ -64,20 +64,23 @@ xcodebuild -exportArchive -archivePath "$ARCHIVE_PATH" \ # Xcode's generated Info.plist can omit CFBundleIconFile even when the # build setting specifies it. Set it on the exported bundle BEFORE the # final Developer ID signature, so the signed app includes the icon key. -ICON_PATH="$APP_PATH/Contents/Resources/DBSyn.icns" +ICON_PATH="$APP_PATH/Contents/Resources/AppIcon.icns" +if [[ ! -s "$ICON_PATH" ]]; then + ICON_PATH="$(find "$APP_PATH/Contents/Resources" -maxdepth 1 -name "*.icns" -type f -print -quit)" +fi [[ -s "$ICON_PATH" ]] || { echo "ERROR: Missing bundled icon $ICON_PATH" >&2; exit 1; } PLIST_PATH="$APP_PATH/Contents/Info.plist" ICON_KEY="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "$PLIST_PATH" 2>/dev/null || true)" -if [[ "$ICON_KEY" != DBSyn && "$ICON_KEY" != DBSyn.icns ]]; then +if [[ "$ICON_KEY" != "$(basename "$ICON_PATH" .icns)" && "$ICON_KEY" != "$(basename "$ICON_PATH")" ]]; then echo "==> Setting CFBundleIconFile in exported app (before signing)..." if /usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "$PLIST_PATH" >/dev/null 2>&1; then - /usr/libexec/PlistBuddy -c 'Set :CFBundleIconFile DBSyn.icns' "$PLIST_PATH" + /usr/libexec/PlistBuddy -c 'Set :CFBundleIconFile $(basename "$ICON_PATH")' "$PLIST_PATH" else - /usr/libexec/PlistBuddy -c 'Add :CFBundleIconFile string DBSyn.icns' "$PLIST_PATH" + /usr/libexec/PlistBuddy -c 'Add :CFBundleIconFile string $(basename "$ICON_PATH")' "$PLIST_PATH" fi fi ICON_KEY="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "$PLIST_PATH")" -[[ "$ICON_KEY" == DBSyn || "$ICON_KEY" == DBSyn.icns ]] || { +[[ "$ICON_KEY" == "$(basename "$ICON_PATH" .icns)" || "$ICON_KEY" == "$(basename "$ICON_PATH")" ]] || { echo "ERROR: CFBundleIconFile still incorrect: '$ICON_KEY'" >&2; exit 1; } echo "==> Application icon verified: $ICON_KEY" @@ -116,7 +119,7 @@ MOUNTED=1 ditto "$DMG_STAGE/$APP_NAME.app" "$MOUNT_POINT/$APP_NAME.app" ln -s /Applications "$MOUNT_POINT/Applications" cp "$ICON_PATH" "$MOUNT_POINT/.VolumeIcon.icns" -xcrun SetFile -a C "$MOUNT_POINT" +if xcrun --find SetFile >/dev/null 2>&1; then xcrun SetFile -a C "$MOUNT_POINT"; else echo "WARNING: SetFile unavailable; skipping custom volume icon flag"; fi hdiutil detach "$MOUNT_POINT" -quiet MOUNTED=0 hdiutil convert "$RW_DMG" -format UDZO -o "$TMP_DMG" -ov @@ -154,4 +157,4 @@ ditto "$TMP_DMG" "$DMG_PATH" echo '==> Release ready:' ls -lh "$DMG_PATH" shasum -a 256 "$DMG_PATH" -printf '\n========== DBSyn RELEASE COMPLETE ==========\n%s\n' "$DMG_PATH" +printf '\n========== RSYNC STUDIO RELEASE COMPLETE ==========\n%s\n' "$DMG_PATH"