Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/macos/entitlements.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
</dict>
</plist>
75 changes: 75 additions & 0 deletions .github/workflows/macos-signing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: macOS signing

on:
pull_request:
branches: [main]
paths:
- scripts/sign-macos.sh
- .github/macos/entitlements.plist
- .github/workflows/macos-signing.yml
push:
branches: [main]
paths:
- scripts/sign-macos.sh
- .github/macos/entitlements.plist
- .github/workflows/macos-signing.yml

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
sign-macos:
runs-on: macos-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/install-frontend-dependencies
- name: Set up cargo cache
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
env:
NODE_OPTIONS: --no-deprecation
- name: Test the signing helper on pull requests
if: github.event_name == 'pull_request'
env:
APPLE_SIGNING_IDENTITY: "-" # Ad hoc signing requires no Apple credentials.
run: scripts/sign-macos.sh
- name: Run the signing helper with Apple credentials
if: github.event_name == 'push'
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_NOTARIZATION_APPLE_ID }}
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
APPLE_NOTARIZATION_TEAM_ID: ${{ secrets.APPLE_NOTARIZATION_TEAM_ID }}
P12_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_P12_BASE64 }}
Comment on lines +43 to +47

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep signing credentials out of PR-controlled scripts

For any same-repository pull request touching one of the filtered files, this job checks out the PR merge commit and executes its scripts/sign-macos.sh while the production P12, its password, and notarization credentials are in the environment. An unreviewed change to that script can therefore exfiltrate the Developer ID private key or use it to sign arbitrary code; GitHub confirms that pull_request workflows from branches in the same repository receive repository secrets and run code from the merge branch (GitHub security guidance). Limit this credentialed job to trusted post-merge pushes, or protect the secrets with an environment requiring approval.

Useful? React with 👍 / 👎.

P12_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }}
run: |
set -euo pipefail
: "${APPLE_SIGNING_IDENTITY:?Missing APPLE_SIGNING_IDENTITY secret}"
: "${APPLE_NOTARIZATION_APPLE_ID:?Missing APPLE_NOTARIZATION_APPLE_ID secret}"
: "${APPLE_NOTARIZATION_PASSWORD:?Missing APPLE_NOTARIZATION_PASSWORD secret}"
: "${APPLE_NOTARIZATION_TEAM_ID:?Missing APPLE_NOTARIZATION_TEAM_ID secret}"
: "${P12_BASE64:?Missing APPLE_SIGNING_CERTIFICATE_P12_BASE64 secret}"
: "${P12_PASSWORD:?Missing APPLE_SIGNING_CERTIFICATE_PASSWORD secret}"
CERTIFICATE_PATH="$RUNNER_TEMP/signing.p12"
KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db"
umask 077
trap 'security delete-keychain "$KEYCHAIN_PATH"; rm -f "$CERTIFICATE_PATH" "$CERTIFICATE_PATH.pem"' EXIT
# Use PKCS12 algorithms supported by macOS Keychain.
printf '%s' "$P12_BASE64" | base64 -d > "$CERTIFICATE_PATH"
openssl pkcs12 -in "$CERTIFICATE_PATH" -passin env:P12_PASSWORD -nodes -out "$CERTIFICATE_PATH.pem"
openssl pkcs12 -export -in "$CERTIFICATE_PATH.pem" -passout env:P12_PASSWORD \
-keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg sha1 -out "$CERTIFICATE_PATH"
rm -f "$CERTIFICATE_PATH.pem"
security create-keychain -p "" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 3600 "$KEYCHAIN_PATH"
security unlock-keychain -p "" "$KEYCHAIN_PATH"
security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_PATH" -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "" "$KEYCHAIN_PATH" > /dev/null
security list-keychains -d user -s "$KEYCHAIN_PATH"
scripts/sign-macos.sh
- name: Run the signed binary
run: target/aarch64-apple-darwin/superoptimized/sqlpage --version
49 changes: 49 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,55 @@ jobs:
- uses: ./.github/actions/install-frontend-dependencies
- name: Build
run: cargo build --profile superoptimized --locked --target ${{ matrix.target }} --features "${{ matrix.features }}"

# macOS: sign and notarize the binary
- name: Sign and notarize macOS binary
if: matrix.os == 'macos-latest'
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_NOTARIZATION_APPLE_ID }}
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
APPLE_NOTARIZATION_TEAM_ID: ${{ secrets.APPLE_NOTARIZATION_TEAM_ID }}
P12_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_P12_BASE64 }}
P12_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }}
run: |
set -euo pipefail
BIN=target/${{ matrix.target }}/superoptimized/sqlpage
# Import the signing certificate into a temporary keychain
umask 077
trap 'security delete-keychain /tmp/signing.keychain; rm -f /tmp/signing.p12 /tmp/signing.pem' EXIT
# Use PKCS12 algorithms supported by macOS Keychain.
printf '%s' "$P12_BASE64" | base64 -d > /tmp/signing.p12
openssl pkcs12 -in /tmp/signing.p12 -passin env:P12_PASSWORD -nodes -out /tmp/signing.pem
openssl pkcs12 -export -in /tmp/signing.pem -passout env:P12_PASSWORD \
-keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg sha1 -out /tmp/signing.p12
rm -f /tmp/signing.pem
security create-keychain -p "" /tmp/signing.keychain
security set-keychain-settings -lut 3600 /tmp/signing.keychain
security unlock-keychain -p "" /tmp/signing.keychain
security import /tmp/signing.p12 -k /tmp/signing.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "" /tmp/signing.keychain > /dev/null
security list-keychains -d user -s /tmp/signing.keychain "$(security default-keychain)"
# Sign with hardened runtime and entitlements
codesign --force --options runtime --entitlements .github/macos/entitlements.plist --sign "$APPLE_SIGNING_IDENTITY" --timestamp "$BIN"
codesign --verify --deep --strict --verbose=2 "$BIN"
# Package in a zip and submit for notarization.
# Note: stapler only supports .app bundles, disk images, and flat packages —
# not bare Mach-O executables or zip archives. For a standalone binary,
# notarization alone is sufficient: Gatekeeper checks Apple's servers online.
ditto -c -k --keepParent "$BIN" sqlpage-macos.zip
xcrun notarytool submit sqlpage-macos.zip \
--apple-id "$APPLE_NOTARIZATION_APPLE_ID" \
--password "$APPLE_NOTARIZATION_PASSWORD" \
--team-id "$APPLE_NOTARIZATION_TEAM_ID" \
--wait --output-format plist > notarization-result.plist
if [[ "$(plutil -extract status raw -o - notarization-result.plist)" != "Accepted" ]]; then
echo "Notarization was not accepted:" >&2
cat notarization-result.plist >&2
exit 1
fi
codesign --verify --deep --strict "$BIN"

- name: Upload unsigned Windows artifact
if: matrix.os == 'windows-latest'
id: upload_unsigned
Expand Down
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@ sqlpage/sqlpage.db
tests_uploads/
/test-results/
/playwright-report/

# Local environment overrides (never commit secrets here!)
# .env is tracked with safe defaults; use .env.local for secrets
.env.local
.env.*.local

# Apple signing certificates (sensitive - do not commit!)
*.cer
*.p12
*.csr
developerID_application.cer
sqlpage.key
sqlpage.p12
sqlpage_cert.pem
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# CHANGELOG.md

## v0.47.0 (unreleased)
- **Mac users:** the downloadable `sqlpage-macos.tgz` is now code-signed with a Developer ID certificate and notarized by Apple. macOS may still ask you to confirm opening it the first time. The binary can still load third-party ODBC database drivers.
- **Mac users:** the downloadable `sqlpage-macos.tgz` now runs natively on Apple silicon (M-series Macs) and no longer runs on Intel Macs. Homebrew remains the recommended and easiest installation method. On an Intel Mac, [install Homebrew](https://brew.sh/) if needed, then run `brew install sqlpage` (or `brew update` followed by `brew upgrade sqlpage` if you already installed it with Homebrew). Open Terminal in your existing website folder and run `sqlpage` instead of `./sqlpage.bin`; keep your SQL files, database, and `sqlpage` configuration folder in place. Intel installations may build from source and take longer; see the [macOS installation guide](https://sql-page.com/your-first-sql-website/?os=macos#download) for setup and older macOS requirements.
- Chart data points can now include a `link`. Clicking a point or its tooltip value opens that URL; a text x value in the tooltip title links to it too. The tooltip remains open while the pointer moves onto the link. ApexCharts was updated to [v7.6.0](https://github.com/apexcharts/apexcharts.js/releases/tag/v7.6.0) to support this.
- Updated sqlx-oldapi to v0.6.57 to fix SQL Server fallback expressions such as `ISNULL($missing, 'default')` truncating defaults or failing for date values when the bound variable is `NULL`.
Expand Down
2 changes: 1 addition & 1 deletion examples/official-site/your-first-sql-website/index.sql
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Let''s create a simple website with a database from scratch, to learn SQLPage ba
ELSE 'https://github.com/sqlpage/SQLPage/releases'
END AS link,
CASE $os
WHEN 'macos' THEN CONCAT('Install SQLPage ', $sqlpage_version, ' using Homebrew')
WHEN 'macos' THEN CONCAT('Install SQLPage ', $sqlpage_version, ' for macOS')
WHEN 'windows' THEN CONCAT('Download SQLPage ', $sqlpage_version, ' for Windows')
WHEN 'linux' THEN CONCAT('Download SQLPage ', $sqlpage_version, ' for Linux')
ELSE CONCAT('Download SQLPage ', $sqlpage_version)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
# Install SQLPage on macOS

The recommended and easiest way to install SQLPage on macOS is [Homebrew](https://brew.sh/), including on Intel Macs.
If you do not already have Homebrew, open Terminal and run:
For a quick start on an Apple silicon Mac (M-series), download SQLPage below.
If you already use [Homebrew](https://brew.sh/) or want easier updates, use the Homebrew instructions instead.
On an Intel Mac, use Homebrew.

## Download SQLPage for Apple silicon

[Download SQLPage for Apple silicon](https://github.com/sqlpage/SQLPage/releases/latest/download/sqlpage-macos.tgz).
Open the downloaded archive to extract it. Keep `sqlpage.bin` and the `sqlpage` folder together in your website folder.
Open Terminal in that folder and run:

```sh
./sqlpage.bin
```

macOS may ask you to confirm opening SQLPage the first time.
To update later, download the latest version and replace `sqlpage.bin`. Keep your SQL files, database, and `sqlpage` configuration folder.

Starting with SQLPage v0.47.0, this download is for Apple silicon only. On an Intel Mac, use Homebrew below.

## Install with Homebrew

If you do not already have Homebrew, open Terminal and install it:

```sh
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Expand All @@ -16,16 +36,13 @@ brew install sqlpage
Open Terminal in your website folder and run `sqlpage` to start your website.
To update SQLPage later, run `brew update` followed by `brew upgrade sqlpage`.

**Using an Intel Mac?** Starting with SQLPage v0.47.0, the `sqlpage-macos.tgz` download is for Apple silicon (M-series Macs) only.
Use Homebrew on Intel Macs. If you previously used the downloaded executable, keep your SQL files, database, and `sqlpage` configuration folder in place and run `sqlpage` from the same website folder instead of `./sqlpage.bin`.
If SQLPage is already installed through Homebrew, use the upgrade commands above.
If you previously used the downloaded executable, keep your SQL files, database, and `sqlpage` configuration folder in place and run `sqlpage` from the same website folder instead of `./sqlpage.bin`.

Homebrew may compile SQLPage and its dependencies from source on Intel Macs, so installation can take longer.
Source builds require Apple's Command Line Tools, which you can install with `xcode-select --install`.
Homebrew classifies Intel Macs as Tier 3 (limited support); older macOS versions also have restrictions. Check [Homebrew's macOS requirements](https://docs.brew.sh/Installation#macos-requirements) if installation fails.

> **Note**: Advanced users can alternatively install SQLPage using
> [the precompiled binaries for Apple silicon](https://github.com/sqlpage/SQLPage/releases/latest),
> [docker](https://hub.docker.com/repository/docker/lovasoa/SQLPage/general),
> [nix](https://search.nixos.org/packages?channel=unstable&show=sqlpage),
> or [cargo](https://crates.io/crates/sqlpage).
Expand Down
2 changes: 1 addition & 1 deletion examples/official-site/your-first-sql-website/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ In the rest of this tutorial, we will call this folder the **root folder** of yo

- On **Windows**, place the `sqlpage.exe` you downloaded above at the root of the folder. Then double-click the `sqlpage.exe` file to start the server.
- On **Linux**, place `sqlpage.bin` at the root of the folder. Then open a terminal, cd to the root folder of your website, and run `./sqlpage.bin` to start the server.
- On **Mac OS**, if you installed SQLPage using Homebrew, then you do not need to place anything at the root of the folder. Open Terminal, cd to the root folder of your website, and type `sqlpage` to start the server.
- On **Mac OS**, if you installed SQLPage using Homebrew, then you do not need to place anything at the root of the folder. Open Terminal, cd to the root folder of your website, and type `sqlpage` to start the server. If you downloaded SQLPage instead, place `sqlpage.bin` in this folder and run `./sqlpage.bin`.

![screenshot for the sql website setup on linux](first-sql-website-launch.png)

Expand Down
109 changes: 109 additions & 0 deletions scripts/sign-macos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/bin/bash
# Script for local macOS signing and notarization testing
#
# Prerequisites:
# 1. Build the frontend assets first: npm ci && npm run build
# 2. Install the Rust target: rustup target add aarch64-apple-darwin
# 3. Import your Developer ID certificate into the login keychain:
# security import sqlpage.p12 -k ~/Library/Keychains/login.keychain-db -P "<p12-password>" -T /usr/bin/codesign
#
# Required environment variables for notarization:
# APPLE_SIGNING_IDENTITY - e.g. "Developer ID Application: Your Name (TEAMID)", or "-" for ad hoc signing
# APPLE_NOTARIZATION_APPLE_ID - Your Apple ID email
# APPLE_NOTARIZATION_PASSWORD - App-specific password
# APPLE_NOTARIZATION_TEAM_ID - Your 10-character Team ID

set -euo pipefail

# Check if we're on macOS
if [[ "$(uname)" != "Darwin" ]]; then
echo "This script must be run on macOS"
exit 1
fi

# Check if required tools are available (use xcrun --find as tools may not be on PATH)
if ! xcrun --find codesign &> /dev/null; then
echo "codesign not found. Please install Xcode command line tools:"
echo " xcode-select --install"
exit 1
fi

# Check that the signing identity is set
if [[ -z "${APPLE_SIGNING_IDENTITY:-}" ]]; then
echo "APPLE_SIGNING_IDENTITY is not set."
echo "Example: export APPLE_SIGNING_IDENTITY=\"Developer ID Application: Your Name (TEAMID)\""
exit 1
fi

# Check that frontend assets exist (build.rs requires them)
if [[ ! -f frontend/dist/tabler-sprite.svg ]]; then
echo "Frontend assets not found. Building them now..."
npm ci
npm run build
fi

# Install the ARM target if on Intel Mac
if [[ "$(uname -m)" == "x86_64" ]]; then
echo "Intel Mac detected, ensuring aarch64-apple-darwin target is installed..."
rustup target add aarch64-apple-darwin
fi

# Build the binary
echo "Building SQLPage for aarch64-apple-darwin..."
cargo build --profile superoptimized --locked --target aarch64-apple-darwin --features "odbc-static"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Build frontend assets before invoking Cargo

On a fresh checkout, this helper reaches cargo build without running the frontend setup used by the release workflow. frontend/dist is ignored, while build.rs unconditionally opens frontend/dist/tabler-sprite.svg and aborts with instructions to run npm ci && npm run build, so the documented local signing command cannot build its input binary unless the developer happens to have stale generated assets. Run the frontend build in this script or document it as a prerequisite.

AGENTS.md reference: AGENTS.md:L147-L147

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Install the ARM Rust target before building

On an Intel Mac with a fresh Rust installation, this hard-coded aarch64-apple-darwin build fails before signing because rustup installs only the host platform's standard library, and rust-toolchain.toml does not request the ARM target. The documented local procedure applies to any macOS machine and does not tell Intel users to install it; add rustup target add aarch64-apple-darwin, declare the target in the toolchain file, or build for the detected host architecture as appropriate (rustup cross-compilation documentation).

Useful? React with 👍 / 👎.


# Check if the binary exists
BINARY_PATH="target/aarch64-apple-darwin/superoptimized/sqlpage"
if [[ ! -f "$BINARY_PATH" ]]; then
echo "Binary not found at $BINARY_PATH"
exit 1
fi

# Sign the binary
echo "Signing the binary..."
SIGNING_TIMESTAMP_OPTION=--timestamp
if [[ "$APPLE_SIGNING_IDENTITY" == "-" ]]; then
SIGNING_TIMESTAMP_OPTION=--timestamp=none
fi
codesign --force --options runtime --entitlements .github/macos/entitlements.plist --sign "$APPLE_SIGNING_IDENTITY" "$SIGNING_TIMESTAMP_OPTION" "$BINARY_PATH"

# Verify the signature
echo "Verifying the signature..."
codesign --verify --deep --strict --verbose=2 "$BINARY_PATH"

# Create a zip archive for notarization
echo "Creating zip archive for notarization..."
ditto -c -k --keepParent "$BINARY_PATH" sqlpage-macos.zip

# Notarize the binary (if credentials are provided)
# Note: stapler does not support bare Mach-O executables or zip archives.
# For a standalone binary, notarization alone is sufficient — Gatekeeper
# checks Apple's notarization servers online when the binary is first run.
if [[ -n "${APPLE_NOTARIZATION_APPLE_ID:-}" && -n "${APPLE_NOTARIZATION_PASSWORD:-}" && -n "${APPLE_NOTARIZATION_TEAM_ID:-}" ]]; then
if ! xcrun --find notarytool &> /dev/null; then
echo "notarytool not found. Please install Xcode command line tools:"
echo " xcode-select --install"
exit 1
fi

echo "Submitting for notarization..."
xcrun notarytool submit sqlpage-macos.zip \
--apple-id "$APPLE_NOTARIZATION_APPLE_ID" \
--password "$APPLE_NOTARIZATION_PASSWORD" \
--team-id "$APPLE_NOTARIZATION_TEAM_ID" \
--wait --output-format plist > notarization-result.plist
if [[ "$(plutil -extract status raw -o - notarization-result.plist)" != "Accepted" ]]; then
echo "Notarization was not accepted:" >&2
cat notarization-result.plist >&2
exit 1
fi

# Verify the signature and notarization ticket for the standalone binary.
echo "Final verification..."
codesign --verify --deep --strict --verbose=2 --check-notarization -R=notarized "$BINARY_PATH"
else
echo "Skipping notarization. Set APPLE_NOTARIZATION_APPLE_ID, APPLE_NOTARIZATION_PASSWORD, and APPLE_NOTARIZATION_TEAM_ID to enable notarization."
echo "Note: The binary is signed but not notarized. Gatekeeper will still show a warning."
fi

echo "macOS signing complete!"
Loading