diff --git a/.github/workflows/full-build.yml b/.github/workflows/full-build.yml index 0e7c09ecaf4..8f9a80b1b7f 100644 --- a/.github/workflows/full-build.yml +++ b/.github/workflows/full-build.yml @@ -68,6 +68,8 @@ jobs: container: image: ${{ matrix.container_image }} options: ${{ matrix.container_options }} --volume /mnt:/mnt + permissions: + contents: write strategy: fail-fast: false matrix: @@ -363,7 +365,7 @@ jobs: if [ $overall_exit_code -ne 0 ]; then echo "Rerunning failing tests..." - ctest -C ${{ env.BUILD_TYPE }} --rerun-failed -T test --no-compress-output || overall_exit_code=$? + ctest -C ${{ env.BUILD_TYPE }} --rerun-failed -T test --no-compress-output && overall_exit_code=0 || overall_exit_code=$? fi echo "exit_code=${overall_exit_code}" >> $GITHUB_OUTPUT @@ -464,7 +466,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: OS-Installers-${{ matrix.platform }}-TGZ-${{ github.sha }} - path: ${{ steps.build_path.outputs.path }}/*.tar.gz + path: ${{ steps.build_path.outputs.path }}/OpenStudio-*.tar.gz if-no-files-found: ignore - name: Upload WHEEL installer @@ -478,7 +480,7 @@ jobs: name: Publish Linux Artifacts needs: [linux-build] runs-on: ubuntu-latest - if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' steps: - name: Download all installers uses: actions/download-artifact@v4 @@ -510,6 +512,80 @@ jobs: if command -v md5sum >/dev/null 2>&1; then md5sum "$file"; else md5 "$file"; fi done + - name: Trigger Docker Build + if: inputs.skip_docker_trigger != 'true' && github.event.inputs.skip_docker_trigger != 'true' + working-directory: installers + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH_NAME: ${{ github.ref_name }} + S3_PREFIX: ${{ github.ref_type == 'tag' && format('releases/{0}', github.ref_name) || format('{0}', github.ref_name) }} + run: | + set -euo pipefail + + # Find the 22.04 deb file locally + DEB_FILE=$(find . -name "*22.04*.deb" | head -n 1) + if [ -z "$DEB_FILE" ]; then + echo "::error::Could not find Ubuntu 22.04 deb file in installers directory" + ls -la + exit 1 + fi + + FILENAME=$(basename "$DEB_FILE") + echo "Found file: $FILENAME" + + # Construct S3 URL + # Replace + with %2B + ENCODED_FILENAME=${FILENAME//+/%2B} + BINARY_URL="https://${AWS_S3_BUCKET}.s3.amazonaws.com/${S3_PREFIX}/${ENCODED_FILENAME}" + echo "Binary URL: $BINARY_URL" + + # Parse version from filename + # Expected format: OpenStudio--.deb + if [[ "$FILENAME" =~ OpenStudio-(.+)-Ubuntu-22\.04.*\.deb ]]; then + OS_VERSION_FULL=${BASH_REMATCH[1]} + elif [[ "$FILENAME" =~ OpenStudio-(.+)-Linux.*\.deb ]]; then + OS_VERSION_FULL=${BASH_REMATCH[1]} + else + echo "::error::Could not parse version from filename: $FILENAME" + exit 1 + fi + + echo "Full Version: $OS_VERSION_FULL" + + # Logic from Jenkins: + # Split by + + IFS='+' read -r VER_PART SHA_PART <<< "$OS_VERSION_FULL" + + if [[ "$VER_PART" == *"-"* ]]; then + # 3.3.0-rc1 -> Ver: 3.3.0, Ext: rc1 + IFS='-' read -r OS_VERSION OS_VERSION_EXT <<< "$VER_PART" + else + # 3.3.0+sha -> Ver: 3.3.0, Ext: sha + OS_VERSION="$VER_PART" + OS_VERSION_EXT="$SHA_PART" + fi + + echo "OS Version: $OS_VERSION" + echo "OS Version Ext: $OS_VERSION_EXT" + + # Docker Tag Logic + if [[ "$BRANCH_NAME" == "develop" ]]; then + DOCKER_IMAGE_TAG="develop" + else + DOCKER_IMAGE_TAG="${OS_VERSION}-${OS_VERSION_EXT}" + fi + echo "Docker Image Tag: $DOCKER_IMAGE_TAG" + + # Trigger Workflow + echo "Triggering manual_update_develop workflow in NREL/docker-openstudio..." + gh workflow run 'manual_update_develop' \ + --repo NREL/docker-openstudio \ + --ref develop \ + -f docker_image_tag="$DOCKER_IMAGE_TAG" \ + -f os_installer_link="$BINARY_URL" \ + -f os_version="$OS_VERSION" \ + -f os_version_ext="$OS_VERSION_EXT" + macos-build: name: Build Packages for ${{ matrix.pretty }} runs-on: ${{ matrix.os }} @@ -645,7 +721,7 @@ jobs: - name: Create Build Directory run: cmake -E make_directory ${{ env.OPENSTUDIO_BUILD }} - + - name: Configure Conan remotes run: | set -euo pipefail @@ -797,13 +873,13 @@ jobs: if [ $overall_exit_code -ne 0 ]; then echo "Rerunning failing tests..." - ctest -C ${{ env.BUILD_TYPE }} --rerun-failed -T test --no-compress-output --output-on-failure || overall_exit_code=$? + ctest -C ${{ env.BUILD_TYPE }} --rerun-failed -T test --no-compress-output --output-on-failure && overall_exit_code=0 || overall_exit_code=$? fi echo "exit_code=${overall_exit_code}" >> $GITHUB_OUTPUT - name: Setup Keychain - if: ${{ success() && !cancelled() && (github.ref == 'refs/heads/develop' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' || matrix.os == 'macOS') }} + if: success() && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' ) env: APPLE_CERT_DATA: ${{ secrets.APPLE_CERT_DATA }} APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} @@ -829,6 +905,8 @@ jobs: - name: Create packages if: ${{ success() && !cancelled() }} working-directory: ${{ env.OPENSTUDIO_BUILD }} + env: + COPYFILE_DISABLE: 1 run: | set -euo pipefail . ./conanbuild.sh @@ -841,6 +919,28 @@ jobs: find . -name "*.o" -type f -delete || true df -h . + - name: Ad-hoc Sign Inner Installer (Fix "Killed" Error) + if: ${{ success() && !cancelled() }} + working-directory: ${{ env.OPENSTUDIO_BUILD }} + env: + # Check if we have a real ID; if not, we must patch the installer + APPLE_DEV_ID: ${{ secrets.APPLE_DEV_ID }} + run: | + set -euo pipefail + + # Only run this fix if we DO NOT have a valid Developer ID. + if [ -n "$APPLE_DEV_ID" ]; then + echo "Valid Developer ID detected. Skipping ad-hoc patch." + exit 0 + fi + + echo "No Developer ID found. Patching DMGs with ad-hoc signature..." + + # Loop through all generated DMGs + find . -maxdepth 1 -name "*.dmg" -print0 | while IFS= read -r -d '' dmg_file; do + ../${{ env.OPENSTUDIO_SOURCE }}/developer/scripts/patch_adhoc_dmg.sh "$dmg_file" + done + - name: Sign DMG and Notarize if: ${{ steps.create_packages.outcome == 'success' && (github.ref == 'refs/heads/develop' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' || matrix.os == 'macOS') }} working-directory: ${{ env.OPENSTUDIO_BUILD }} @@ -980,7 +1080,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: OS-TGZ-${{ matrix.platform }}-${{ github.sha }} - path: ${{ env.OPENSTUDIO_BUILD }}/*.tar.gz + path: ${{ env.OPENSTUDIO_BUILD }}/OpenStudio-*.tar.gz if-no-files-found: ignore - name: Fail job on test failures @@ -992,7 +1092,7 @@ jobs: name: Publish MacOS Artifacts needs: [macos-build] runs-on: ubuntu-latest - if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' steps: - name: Download all installers uses: actions/download-artifact@v4 @@ -1028,6 +1128,8 @@ jobs: windows-build: name: Build ${{ matrix.pretty }} runs-on: ${{ matrix.os }} + permissions: + contents: write strategy: fail-fast: false matrix: @@ -1307,7 +1409,7 @@ jobs: if ($overall_exit_code -ne 0) { Write-Host "Rerunning failing tests..." ctest -C ${{ env.BUILD_TYPE }} --rerun-failed -T test - if ($LASTEXITCODE -ne 0) { $overall_exit_code = 1 } + if ($LASTEXITCODE -eq 0) { $overall_exit_code = 0 } else { $overall_exit_code = 1 } } "exit_code=$overall_exit_code" | Out-File -FilePath $env:GITHUB_OUTPUT -Append @@ -1392,25 +1494,25 @@ jobs: # CODE SIGNING - AWS Signing Service - name: Setup Node.js - if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' uses: actions/setup-node@v4 with: node-version: "18" - name: Install Signing Client Dependencies - if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' run: npm install working-directory: ./.github/signing-client - name: Create .env file for Signing - if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' run: | echo "ACCESS_KEY=${{ secrets.AWS_SIGNING_ACCESS_KEY }}" > .env echo "SECRET_KEY=${{ secrets.AWS_SIGNING_SECRET_KEY }}" >> .env working-directory: ${{ env.OPENSTUDIO_BUILD }} - name: Code sign installer - if: success() && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true') + if: success() && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true') working-directory: ${{ env.OPENSTUDIO_BUILD }} run: | # Check if signing client exists @@ -1517,7 +1619,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: OS-Installers-${{ matrix.platform }}-TGZ-${{ github.sha }} - path: ${{ env.OPENSTUDIO_BUILD }}/*.tar.gz + path: ${{ env.OPENSTUDIO_BUILD }}/OpenStudio-*.tar.gz if-no-files-found: ignore - name: Upload Signed installers @@ -1531,7 +1633,7 @@ jobs: name: Publish Windows Artifacts needs: [windows-build] runs-on: ubuntu-latest - if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' steps: - name: Download all installers uses: actions/download-artifact@v4 diff --git a/README.md b/README.md index 043005aef6b..b77b7dc0c84 100644 --- a/README.md +++ b/README.md @@ -11,22 +11,12 @@ More information and documentation is available at the [OpenStudio website](http For development builds (artifacts downloaded from GitHub Actions), you may encounter a "Damaged" error or "Unidentified Developer" warning on macOS, especially on Apple Silicon (ARM) machines. This is because these builds are not notarized by Apple. -If you encounter these issues, standard `xattr` commands on the DMG may not be sufficient. Please follow these steps: - -1. **Mount the DMG** image. -2. **Copy** the Installer application (e.g., `OpenStudio-3.11.0...app`) from the mounted volume to a local folder (e.g., your `Downloads` folder). *Do not run it directly from the DMG.* -3. Open a **Terminal** and run the following commands on the *local copy* of the installer: - - ```bash - # 1. Remove quarantine attributes - xattr -cr path/to/local/OpenStudio-Installer.app - - # 2. Ad-hoc sign the application (fixes "Killed" or crashes on startup) - codesign --force --deep --sign - path/to/local/OpenStudio-Installer.app - ``` - -4. Run the installer. If double-clicking fails, run the executable directly with `sudo`: - - ```bash - sudo path/to/local/OpenStudio-Installer.app/Contents/MacOS/OpenStudio-3.11.0--Darwin- - ``` +If you encounter these issues, please follow these steps to bypass the security check for this specific installer: + +1. **Mount the DMG**: Locate the downloaded `.dmg` file in Finder. Right-click (or Control-click) the file and select **Open**. +2. **Launch Installer**: Inside the mounted disk image window, Right-click (or Control-click) the `OpenStudio-Installer.app` file and select **Open**. +3. **Acknowledge Warning**: A security warning dialog will appear. Click **Open** if available. If only **OK** is available, click it (the installer might close). +4. **Security Settings**: Open **System Settings** (or System Preferences) and navigate to **Privacy & Security**. +5. **Allow the App**: Scroll down to the "Security" section. Look for a note about the OpenStudio application being blocked. Click the **Open Anyway** button. +6. **Confirm Open**: A final confirmation dialog will appear. Click **Open**. +7. **Authenticate**: Enter your system password when prompted to authorize the installation. diff --git a/developer/scripts/patch_adhoc_dmg.sh b/developer/scripts/patch_adhoc_dmg.sh new file mode 100755 index 00000000000..faa4856ec57 --- /dev/null +++ b/developer/scripts/patch_adhoc_dmg.sh @@ -0,0 +1,100 @@ +#!/bin/bash +set -euo pipefail + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +DMG_FILE="$1" +MOUNT_POINT="mount_point_$(date +%s)" +TEMP_RW="temp_rw_$(date +%s).dmg" + +echo "-------------------------------------------------------" +echo "Target DMG: $DMG_FILE" +echo "-------------------------------------------------------" + +# 1. Convert to Read-Write +echo "Converting to Read-Write..." +rm -f "$TEMP_RW" +hdiutil convert "$DMG_FILE" -format UDRW -o "$TEMP_RW" -quiet + +# 2. Resize DMG +# Ad-hoc signatures add metadata. We resize to 2GB to be absolutely sure there's space. +# For DMGs with partition maps (like CPack's GUID images), standard resize works. +# For flat images, -imageonly might be needed. +echo "Resizing DMG..." +if hdiutil resize -size 2g "$TEMP_RW" 2>/dev/null; then + echo " Standard resize to 2GB succeeded." +elif hdiutil resize -size 2g -imageonly "$TEMP_RW" 2>/dev/null; then + echo " Image-only resize to 2GB succeeded." +else + echo "Warning: Resize failed, proceeding anyway. This may cause 'internal error' in codesign if space is tight." +fi + +# 3. Mount +echo "Mounting..." +mkdir -p "$MOUNT_POINT" +hdiutil attach "$TEMP_RW" -mountpoint "$MOUNT_POINT" -nobrowse -noverify -quiet + +# 4. Sign +echo "Applying ad-hoc signature..." +export CODESIGN_ALLOCATE=$(xcrun -find codesign_allocate) + +# Find all .app bundles in the mount point +find "$MOUNT_POINT" -maxdepth 1 -name "*.app" -print0 | while IFS= read -r -d '' app_path; do + echo "Processing app bundle: $app_path" + + # Ensure everything is writable + echo "Ensuring app bundle is writable..." + chmod -R u+w "$app_path" || true + + # 1. Clear extended attributes + echo "Clearing extended attributes..." + xattr -cr "$app_path" || echo "Warning: Some extended attributes could not be cleared." + + # 2. Sign nested components + echo "Signing nested components..." + # Sign in depth-first order so that inner items are signed before their containers + find "$app_path" -depth -print0 \( \ + \( -type d -name "*.framework" \) -o \ + \( -type d -name "*.bundle" \) -o \ + \( -type d -name "*.plugin" \) -o \ + \( -type f -name "*.dylib" \) -o \ + \( -type f -perm -111 \) \ + \) | while IFS= read -r -d '' item; do + if [ "$item" != "$app_path" ]; then + # Double check it's actually signable (directory bundle or Mach-O file) + if [ -d "$item" ]; then + echo " Signing nested bundle: $item" + codesign --force --verify --verbose --sign - --timestamp=none --generate-entitlement-der "$item" || true + elif [ -f "$item" ]; then + if file "$item" | grep -qE "Mach-O|current ar archive"; then + echo " Signing nested binary: $item" + # Try to remove signature first if it fails with internal error + codesign --force --verify --verbose --sign - --timestamp=none --generate-entitlement-der "$item" || { + echo " Failed to sign $item, attempting signature removal first..." + codesign --remove-signature "$item" || true + codesign --force --verify --verbose --sign - --timestamp=none --generate-entitlement-der "$item" + } + fi + fi + fi + done + + echo "Signing top-level app..." + codesign --force --verify --verbose --sign - --timestamp=none --generate-entitlement-der "$app_path" +done + +# 5. Cleanup and Convert Back +echo "Detaching..." +hdiutil detach "$MOUNT_POINT" -force +rmdir "$MOUNT_POINT" + +echo "Converting back to compressed DMG..." +FINAL_DMG="signed_repacked.dmg" +hdiutil convert "$TEMP_RW" -format UDZO -o "$FINAL_DMG" -quiet +mv "$FINAL_DMG" "$DMG_FILE" +rm "$TEMP_RW" + +echo "Success: $DMG_FILE updated."