Repository: benbjohnson/litestream

Issue:
Set up code signing certificates for automated releases
## Overview

This issue tracks the setup of code signing certificates needed to enable binary signing in our new GoReleaser-based release process. The automated release infrastructure is ready (#732), but requires certificates to enable signing.

## Priority: Medium
Unsigned binaries will work but may trigger security warnings on macOS and Windows.

## Required Actions for @benbjohnson

### 1. Apple Developer Certificate ($99/year) - REQUIRED for macOS

Without this, macOS users will see Gatekeeper warnings when running Litestream.

#### Steps:

1. **Enroll in Apple Developer Program**
   - Go to https://developer.apple.com/programs/
   - Sign up with Apple ID
   - Pay $99 annual fee
   - Wait for identity verification (24-48 hours)

2. **Create Developer ID Certificate**
   - Go to https://developer.apple.com/account
   - Navigate to Certificates, IDs & Profiles
   - Create "Developer ID Application" certificate
   - Follow CSR process in Keychain Access
   - Download and install certificate

3. **Export Certificate for CI**
   ```bash
   # Export from Keychain as .p12 with password
   # Then convert to base64:
   base64 -i certificate.p12 -o cert_base64.txt
   ```

4. **Create App Store Connect API Key**
   - Go to https://appstoreconnect.apple.com/access/api
   - Create key with Developer role
   - Download .p8 file (⚠️ one-time download!)
   - Note Issuer ID and Key ID
   ```bash
   base64 -i AuthKey_XXXXX.p8 -o key_base64.txt
   ```

5. **Create App-Specific Password**
   - Go to https://appleid.apple.com
   - Security → App-Specific Passwords
   - Generate password for "Litestream GoReleaser"

6. **Add GitHub Secrets**
   Go to Settings → Secrets and variables → Actions:

   | Secret Name | How to Get It |
   |------------|---------------|
   | `MACOS_CERTIFICATE_P12` | Contents of cert_base64.txt |
   | `MACOS_CERTIFICATE_PASSWORD` | Password from .p12 export |
   | `APPLE_API_KEY_ID` | Key ID from step 4 |
   | `APPLE_API_ISSUER_ID` | Issuer ID from step 4 |
   | `APPLE_API_KEY_P8` | Contents of key_base64.txt |
   | `AC_PASSWORD` | App-specific password from step 5 |
   | `APPLE_ID_USERNAME` | Your Apple ID email |
   | `APPLE_TEAM_ID` | Find in Membership section |
   | `APPLE_DEVELOPER_ID` | Full certificate name |

7. **Enable in Workflow**
   - Edit `.github/workflows/release.yml`
   - Remove `if: ${{ false }}` from `macos-sign` job
   - Commit and push

### 2. Windows Code Signing (OPTIONAL - Unsupported Platform)

Since Windows is not officially supported, this is optional. Unsigned Windows binaries will trigger SmartScreen warnings.

**If you want to sign Windows binaries:**

1. **Purchase Code Signing Certificate** (~$200-500/year)
   - Options: DigiCert, Sectigo, GlobalSign
   - Or use Microsoft Trusted Signing (Azure-based)

2. **Add GitHub Secrets**
   - `WINDOWS_CERTIFICATE_PFX`: Base64-encoded certificate
   - `WINDOWS_CERTIFICATE_PASSWORD`: Certificate password

3. **Enable in Workflow**
   - Remove `if: ${{ false }}` from `windows-sign` job

### 3. Homebrew Tap Token (REQUIRED - Quick Setup)

This is needed for Homebrew distribution:

1. **Create GitHub Personal Access Token**
   - Go to https://github.com/settings/tokens/new
   - Name: "Litestream Homebrew Tap"
   - Scope: `repo`
   - Generate and copy

2. **Add as Secret**
   - Name: `HOMEBREW_TAP_GITHUB_TOKEN`
   - Value: The token from step 1

## Timeline

- **Homebrew Token**: 5 minutes (do anytime)
- **Apple Certificate**: 1-2 days (includes verification wait)
- **Windows Certificate**: Optional, 1-2 hours if pursued

## Testing

After setting up certificates:

1. Create a test release tag
2. Verify signed binaries work without warnings
3. Test Homebrew installation

## Resources

- [Apple Developer Documentation](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution)
- [GoReleaser Signing Guide](https://goreleaser.com/customization/sign/)
- Detailed instructions in `docs/RELEASE.md`

## Notes

The release process will work without these certificates, but:
- macOS users will see Gatekeeper warnings
- Windows users will see SmartScreen warnings
- Homebrew tap won't auto-update without the GitHub token

Please comment here if you need any assistance with the setup process.

Discussion:

Solution patch:
diff --git a/.github/workflows/commit.yml b/.github/workflows/commit.yml
index 9827031..e2c8286 100644
--- a/.github/workflows/commit.yml
+++ b/.github/workflows/commit.yml
@@ -294,3 +294,55 @@ jobs:
           LITESTREAM_SFTP_USER:     "root"
           LITESTREAM_SFTP_KEY_PATH: /test/id_ed25519
           LITESTREAM_SFTP_PATH:     /test/data
+
+      - name: Test SFTP with concurrent writes enabled (default)
+        run: |
+          cat > /tmp/sftp-concurrent.yml <<EOF
+          dbs:
+            - path: /tmp/test-concurrent.db
+              replicas:
+                - type: sftp
+                  host: localhost:2222
+                  key-path: /test/id_ed25519
+                  user: root
+                  path: /test/data/concurrent
+                  concurrent-writes: true
+          EOF
+
+          # Create test database
+          sqlite3 /tmp/test-concurrent.db <<SQL
+          CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT);
+          INSERT INTO test (data) VALUES ('test1'), ('test2'), ('test3');
+          SQL
+
+          # Run replication briefly
+          timeout 5 ./bin/litestream replicate -config /tmp/sftp-concurrent.yml || true
+
+          # Check files were created
+          ssh -i /test/id_ed25519 -o StrictHostKeyChecking=accept-new -p 2222 root@localhost "ls -la /test/data/concurrent/" || true
+
+      - name: Test SFTP with concurrent writes disabled
+        run: |
+          cat > /tmp/sftp-sequential.yml <<EOF
+          dbs:
+            - path: /tmp/test-sequential.db
+              replicas:
+                - type: sftp
+                  host: localhost:2222
+                  key-path: /test/id_ed25519
+                  user: root
+                  path: /test/data/sequential
+                  concurrent-writes: false
+          EOF
+
+          # Create test database
+          sqlite3 /tmp/test-sequential.db <<SQL
+          CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT);
+          INSERT INTO test (data) VALUES ('test1'), ('test2'), ('test3');
+          SQL
+
+          # Run replication briefly
+          timeout 5 ./bin/litestream replicate -config /tmp/sftp-sequential.yml || true
+
+          # Check files were created
+          ssh -i /test/id_ed25519 -o StrictHostKeyChecking=accept-new -p 2222 root@localhost "ls -la /test/data/sequential/" || true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..90b3b14
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,140 @@
+name: Release
+
+on:
+  push:
+    tags:
+      - 'v*'
+  workflow_dispatch:
+    inputs:
+      tag:
+        description: 'Release tag (e.g., v0.3.14)'
+        required: true
+        type: string
+
+permissions:
+  contents: write
+  packages: write
+  id-token: write
+
+jobs:
+  goreleaser:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version-file: go.mod
+          cache: true
+
+      - name: Install cross-compilers
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf gcc-arm-linux-gnueabi
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: Import GPG key
+        if: ${{ env.GPG_PRIVATE_KEY != '' }}
+        id: import_gpg
+        uses: crazy-max/ghaction-import-gpg@v6
+        with:
+          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
+          passphrase: ${{ secrets.GPG_PASSPHRASE }}
+
+      - name: Run GoReleaser
+        uses: goreleaser/goreleaser-action@v6
+        with:
+          version: latest
+          args: release --clean
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
+          HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
+
+  # Signing jobs - uncomment after setting up certificates (see issue #733)
+  # Instructions: https://github.com/benbjohnson/litestream/issues/733
+  #
+  # macos-sign:
+  #   runs-on: macos-latest
+  #   needs: goreleaser
+  #   strategy:
+  #     matrix:
+  #       arch: [amd64, arm64]
+  #   steps:
+  #     - name: Checkout
+  #       uses: actions/checkout@v4
+  #
+  #     - name: Set up Go
+  #       uses: actions/setup-go@v5
+  #       with:
+  #         go-version-file: go.mod
+  #
+  #     - name: Download release artifacts
+  #       uses: actions/download-artifact@v4
+  #       with:
+  #         name: litestream-darwin-${{ matrix.arch }}
+  #         path: dist/
+  #
+  #     - name: Import Apple Developer Certificate
+  #       env:
+  #         MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE_P12 }}
+  #         MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+  #       run: |
+  #         echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12
+  #         security create-keychain -p actions temp.keychain
+  #         security default-keychain -s temp.keychain
+  #         security unlock-keychain -p actions temp.keychain
+  #         security import certificate.p12 -k temp.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
+  #         security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k actions temp.keychain
+  #
+  #     - name: Sign and Notarize
+  #       env:
+  #         APPLE_API_KEY: ${{ secrets.APPLE_API_KEY_P8 }}
+  #         APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
+  #         APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
+  #         AC_PASSWORD: ${{ secrets.AC_PASSWORD }}
+  #       run: |
+  #         gon etc/gon-${{ matrix.arch }}.hcl
+  #
+  #     - name: Upload signed binary
+  #       env:
+  #         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  #       run: |
+  #         gh release upload ${{ github.ref_name }} dist/litestream-*-darwin-${{ matrix.arch }}.zip
+  #
+  # windows-sign:
+  #   runs-on: windows-latest
+  #   needs: goreleaser
+  #   strategy:
+  #     matrix:
+  #       arch: [amd64, arm64]
+  #   steps:
+  #     - name: Checkout
+  #       uses: actions/checkout@v4
+  #
+  #     - name: Download release artifacts
+  #       uses: actions/download-artifact@v4
+  #       with:
+  #         name: litestream-windows-${{ matrix.arch }}
+  #         path: dist/
+  #
+  #     - name: Sign Windows binary
+  #       env:
+  #         WINDOWS_CERTIFICATE_PFX: ${{ secrets.WINDOWS_CERTIFICATE_PFX }}
+  #         WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
+  #       run: |
+  #         echo "$env:WINDOWS_CERTIFICATE_PFX" | base64 -d > cert.pfx
+  #         & signtool sign /f cert.pfx /p "$env:WINDOWS_CERTIFICATE_PASSWORD" /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com dist\litestream.exe
+  #         Remove-Item cert.pfx
+  #
+  #     - name: Upload signed binary
+  #       env:
+  #         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  #       run: |
+  #         gh release upload ${{ github.ref_name }} dist\litestream-*-windows-${{ matrix.arch }}.zip
diff --git a/.goreleaser.yml b/.goreleaser.yml
new file mode 100644
index 0000000..41004f4
--- /dev/null
+++ b/.goreleaser.yml
@@ -0,0 +1,170 @@
+version: 2
+
+project_name: litestream
+
+before:
+  hooks:
+    - go mod tidy
+
+builds:
+  - id: litestream
+    main: ./cmd/litestream
+    binary: litestream
+    env:
+      - CGO_ENABLED=0
+    goos:
+      - linux
+      - darwin
+      - windows
+    goarch:
+      - amd64
+      - arm64
+      - arm
+    goarm:
+      - "6"
+      - "7"
+    ldflags:
+      - -s -w -X main.Version={{.Version}}
+    ignore:
+      - goos: windows
+        goarch: arm
+      - goos: darwin
+        goarch: arm
+
+archives:
+  - id: main
+    format: tar.gz
+    format_overrides:
+      - goos: windows
+        format: zip
+    name_template: >-
+      {{ .ProjectName }}-
+      {{- .Version }}-
+      {{- .Os }}-
+      {{- if eq .Arch "amd64" }}x86_64
+      {{- else if eq .Arch "386" }}i386
+      {{- else }}{{ .Arch }}{{ end }}
+      {{- if .Arm }}v{{ .Arm }}{{ end }}
+    files:
+      - etc/litestream.yml
+      - etc/litestream.service
+      - README.md
+      - LICENSE
+
+nfpms:
+  - vendor: Litestream
+    homepage: https://litestream.io
+    maintainer: Litestream Contributors <benbjohnson@yahoo.com>
+    description: Streaming replication for SQLite databases
+    license: Apache 2.0
+    formats:
+      - deb
+      - rpm
+    contents:
+      - src: etc/litestream.yml
+        dst: /etc/litestream.yml
+        type: config
+      - src: etc/litestream.service
+        dst: /lib/systemd/system/litestream.service
+        type: config
+    bindir: /usr/bin
+    file_name_template: >-
+      {{ .ProjectName }}-
+      {{- .Version }}-
+      {{- .Os }}-
+      {{- if eq .Arch "amd64" }}x86_64
+      {{- else if eq .Arch "386" }}i386
+      {{- else }}{{ .Arch }}{{ end }}
+      {{- if .Arm }}v{{ .Arm }}{{ end }}
+
+brews:
+  - name: litestream
+    homepage: https://litestream.io
+    description: Streaming replication for SQLite databases
+    license: Apache-2.0
+    repository:
+      owner: benbjohnson
+      name: homebrew-litestream
+      branch: main
+    directory: Formula
+    install: |
+      bin.install "litestream"
+      etc.install "etc/litestream.yml" => "litestream.yml"
+    test: |
+      system "#{bin}/litestream", "version"
+    commit_author:
+      name: goreleaser
+      email: bot@goreleaser.com
+
+checksum:
+  name_template: 'checksums.txt'
+  algorithm: sha256
+
+snapshot:
+  version_template: "{{ .Tag }}-next"
+
+changelog:
+  sort: asc
+  filters:
+    exclude:
+      - '^docs:'
+      - '^test:'
+      - '^chore:'
+      - 'Merge pull request'
+      - 'Merge branch'
+
+release:
+  github:
+    owner: benbjohnson
+    name: litestream
+  draft: false
+  prerelease: auto
+  mode: replace
+  header: |
+    ## Platform Support
+
+    ⚠️ **Windows Notice**: Windows binaries are provided for convenience but Windows is NOT an officially supported platform. Use at your own risk. Community contributions for Windows improvements are welcome.
+
+    ✅ **Supported Platforms**: Linux (amd64, arm64, armv6, armv7), macOS (amd64, arm64)
+
+    ## Installation
+
+    ### Homebrew (macOS and Linux)
+    ```bash
+    brew tap benbjohnson/litestream
+    brew install litestream
+    ```
+
+    ### Debian/Ubuntu
+    Download the `.deb` file for your architecture and install:
+    ```bash
+    sudo dpkg -i litestream-*.deb
+    ```
+
+    ### RPM-based systems
+    Download the `.rpm` file for your architecture and install:
+    ```bash
+    sudo rpm -i litestream-*.rpm
+    ```
+
+    ### Binary installation
+    Download the appropriate archive for your platform, extract, and move to your PATH.
+
+# Signing configuration - uncomment after setting up certificates (see issue #733)
+# signs:
+#   - id: macos
+#     cmd: gon
+#     args:
+#       - "{{ .ProjectPath }}/gon-sign.hcl"
+#     artifacts: archive
+#     ids:
+#       - main
+#     signature: "${artifact}.zip"
+#     output: true
+#     env:
+#       - APPLE_DEVELOPER_ID_APPLICATION={{ .Env.APPLE_DEVELOPER_ID }}
+#       - APPLE_DEVELOPER_TEAM_ID={{ .Env.APPLE_TEAM_ID }}
+#       - AC_PASSWORD={{ .Env.AC_PASSWORD }}
+
+sboms:
+  - artifacts: archive
diff --git a/docs/RELEASE.md b/docs/RELEASE.md
new file mode 100644
index 0000000..a108fb2
--- /dev/null
+++ b/docs/RELEASE.md
@@ -0,0 +1,334 @@
+# Litestream Release Process
+
+This document describes the release process for Litestream using GoReleaser.
+
+## Quick Start for Maintainers
+
+To create a release after certificates are configured:
+
+```bash
+# Tag and push
+git tag -a v0.3.14 -m "Release v0.3.14"
+git push origin v0.3.14
+```
+
+The GitHub Actions workflow will handle everything else automatically.
+
+## Overview
+
+Litestream uses [GoReleaser](https://goreleaser.com/) to automate the release process, providing:
+
+- Cross-platform binary builds (Linux, macOS, Windows)
+- Automatic changelog generation
+- Homebrew formula updates
+- Debian/RPM package generation
+- Binary signing (when certificates are configured)
+- SBOM (Software Bill of Materials) generation
+
+## Platform Support
+
+### Officially Supported Platforms
+
+- Linux (amd64, arm64, armv6, armv7)
+- macOS (amd64, arm64)
+
+### Unsupported Platforms
+
+- **Windows (amd64, arm64)**: Binaries are provided for convenience but Windows is NOT an officially supported platform. Use at your own risk. Community contributions for Windows improvements are welcome.
+
+## Prerequisites
+
+### Required Tools
+
+- [GoReleaser](https://goreleaser.com/install/) v2.0+
+- [GitHub CLI](https://cli.github.com/) (for automated releases)
+- Go 1.24+
+
+### Optional Tools (for signing)
+
+- [gon](https://github.com/mitchellh/gon) (macOS signing and notarization)
+- signtool (Windows signing)
+
+## Release Process
+
+### 1. Prepare the Release
+
+1. Ensure all changes are merged to main
+2. Update CHANGELOG.md if needed (GoReleaser will auto-generate from commits)
+3. Ensure all tests pass:
+
+   ```bash
+   go test -v ./...
+   go vet ./...
+   staticcheck ./...
+   ```
+
+### 2. Create a Release Tag
+
+```bash
+# Create and push a tag
+git tag -a v0.3.14 -m "Release v0.3.14"
+git push origin v0.3.14
+```
+
+The tag push will automatically trigger the GitHub Actions release workflow.
+
+### 3. Manual Release (if needed)
+
+If you need to run a release manually:
+
+```bash
+# Export GitHub token
+export GITHUB_TOKEN="your-token-here"
+
+# Run GoReleaser
+goreleaser release --clean
+```
+
+### 4. Testing Releases
+
+To test the release process without publishing:
+
+```bash
+# Create a snapshot release (doesn't publish)
+goreleaser release --snapshot --clean
+
+# Test a single platform build
+goreleaser build --snapshot --clean --single-target
+```
+
+## GitHub Actions Workflow
+
+The release workflow (`.github/workflows/release.yml`) is triggered automatically when:
+
+- A tag matching `v*` is pushed
+- Manually via workflow dispatch
+
+The workflow:
+
+1. Sets up the build environment
+2. Runs GoReleaser to build all binaries
+3. Creates GitHub release with artifacts
+4. Updates Homebrew tap (if configured)
+5. Signs binaries (if certificates are configured)
+
+## Configuration Files
+
+### `.goreleaser.yml`
+
+Main GoReleaser configuration defining:
+
+- Build targets and flags
+- Archive formats
+- Package formats (deb, rpm)
+- Homebrew formula
+- Release notes template
+
+### `etc/gon-sign.hcl`
+
+macOS signing configuration (requires Apple Developer certificates)
+
+### `.github/workflows/release.yml`
+
+GitHub Actions workflow for automated releases
+
+## Setting Up Binary Signing
+
+### macOS Signing - Detailed Instructions
+
+#### Step 1: Get Apple Developer Account ($99/year)
+
+1. Go to <https://developer.apple.com/programs/>
+2. Click "Enroll" and follow the process
+3. Use your existing Apple ID or create a new one
+4. Complete identity verification (may take 24-48 hours)
+5. Pay the $99 annual fee
+
+#### Step 2: Create Developer ID Certificate
+
+1. Once enrolled, go to <https://developer.apple.com/account>
+2. Navigate to "Certificates, IDs & Profiles"
+3. Click the "+" button to create a new certificate
+4. Select "Developer ID Application" under "Software"
+5. Follow the Certificate Signing Request (CSR) process:
+   - Open Keychain Access on your Mac
+   - Menu: Keychain Access → Certificate Assistant → Request a Certificate
+   - Enter your email and name
+   - Select "Saved to disk"
+   - Save the CSR file
+6. Upload the CSR file in the Apple Developer portal
+7. Download the generated certificate
+8. Double-click to install in Keychain Access
+
+#### Step 3: Export Certificate for CI
+
+1. Open Keychain Access
+2. Find your "Developer ID Application: [Your Name]" certificate
+3. Right-click and select "Export"
+4. Save as .p12 format with a strong password
+5. Convert to base64 for GitHub secrets:
+   ```bash
+   base64 -i certificate.p12 -o certificate_base64.txt
+   ```
+
+#### Step 4: Create App Store Connect API Key
+
+1. Go to <https://appstoreconnect.apple.com/access/api>
+2. Click the "+" button to generate a new API key
+3. Name: "GoReleaser CI"
+4. Access: "Developer" role
+5. Download the .p8 file (IMPORTANT: Can only download once!)
+6. Note these values:
+   - Issuer ID (shown at the top of the API Keys page)
+   - Key ID (shown in the key list)
+7. Convert .p8 to base64:
+   ```bash
+   base64 -i AuthKey_XXXXX.p8 -o api_key_base64.txt
+   ```
+
+#### Step 5: Create App-Specific Password
+
+1. Go to <https://appleid.apple.com/account/manage>
+2. Sign in and go to "Security"
+3. Under "App-Specific Passwords", click "Generate Password"
+4. Label it "Litestream GoReleaser"
+5. Save the generated password securely
+
+#### Step 6: Configure GitHub Secrets
+
+Go to GitHub repository Settings → Secrets and variables → Actions:
+
+| Secret Name | How to Get It |
+|------------|---------------|
+| `MACOS_CERTIFICATE_P12` | Contents of certificate_base64.txt from Step 3 |
+| `MACOS_CERTIFICATE_PASSWORD` | Password used when exporting .p12 in Step 3 |
+| `APPLE_API_KEY_ID` | Key ID from Step 4 |
+| `APPLE_API_ISSUER_ID` | Issuer ID from Step 4 |
+| `APPLE_API_KEY_P8` | Contents of api_key_base64.txt from Step 4 |
+| `AC_PASSWORD` | App-specific password from Step 5 |
+| `APPLE_ID_USERNAME` | Your Apple ID email |
+| `APPLE_TEAM_ID` | Find in Apple Developer account under Membership |
+| `APPLE_DEVELOPER_ID` | Full certificate name (e.g., "Developer ID Application: Your Name (TEAMID)") |
+
+#### Step 7: Enable in Workflow
+
+Edit `.github/workflows/release.yml`:
+- Find the `macos-sign` job
+- Remove or change `if: ${{ false }}` to `if: true`
+
+### Windows Signing (Optional - Unsupported Platform)
+
+Since Windows is not officially supported, signing is optional.
+If you choose to sign:
+
+1. **Obtain Code Signing Certificate**
+   - Purchase from DigiCert, Sectigo, or GlobalSign (~$200-500/year)
+   - Or use Microsoft Trusted Signing (Azure-based)
+
+2. **Configure GitHub Secrets**
+
+   ```text
+   WINDOWS_CERTIFICATE_PFX: Base64-encoded .pfx file
+   WINDOWS_CERTIFICATE_PASSWORD: Certificate password
+   ```
+
+3. **Enable in workflow**
+   - Remove `if: ${{ false }}` from windows-sign job in release.yml
+
+## Homebrew Tap Setup (Required for macOS Distribution)
+
+### Step 1: Create the Tap Repository
+
+Run the provided script or manually create the repository:
+
+```bash
+./scripts/setup-homebrew-tap.sh
+```
+
+Or manually:
+1. Create a new repository named `homebrew-litestream` under the `benbjohnson` account
+2. Make it public
+3. Add a README and Formula directory
+
+### Step 2: Create GitHub Personal Access Token
+
+1. Go to <https://github.com/settings/tokens/new>
+2. Name: "Litestream Homebrew Tap"
+3. Expiration: No expiration (or 1 year if you prefer)
+4. Select scopes:
+   - `repo` (Full control of private repositories)
+   - This allows GoReleaser to push formula updates
+5. Click "Generate token"
+6. Copy the token immediately (won't be shown again)
+
+### Step 3: Add Token to Repository Secrets
+
+1. Go to Litestream repository settings
+2. Navigate to Settings → Secrets and variables → Actions
+3. Click "New repository secret"
+4. Name: `HOMEBREW_TAP_GITHUB_TOKEN`
+5. Value: Paste the token from Step 2
+
+### Step 4: Test Installation
+
+After the first release:
+
+```bash
+brew tap benbjohnson/litestream
+brew install litestream
+```
+
+## Troubleshooting
+
+### Common Issues
+
+#### Build fails with "version: 0" error
+
+- Ensure `.goreleaser.yml` starts with `version: 2`
+
+#### Homebrew formula not updated
+
+- Check HOMEBREW_TAP_GITHUB_TOKEN secret is set
+- Verify tap repository exists and is accessible
+
+#### macOS binary rejected by Gatekeeper
+
+- Ensure signing certificates are valid
+- Check notarization completed successfully
+- Verify AC_PASSWORD is an app-specific password
+
+#### Windows SmartScreen warning
+
+- This is expected for unsigned binaries
+- Consider signing if distributing widely (though platform is unsupported)
+
+### Testing Local Builds
+
+```bash
+# Test specific platform
+GOOS=linux GOARCH=arm64 goreleaser build --snapshot --clean --single-target
+
+# Check configuration
+goreleaser check
+
+# Dry run (no upload)
+goreleaser release --skip=publish --clean
+```
+
+## Migration from Manual Process
+
+The old manual release process using Makefile targets and individual workflows has been replaced by GoReleaser. The following are deprecated:
+
+- `make dist-linux`
+- `make dist-macos`
+- `.github/workflows/release.linux.yml`
+- Manual gon invocation for macOS
+
+## Support and Issues
+
+For release process issues:
+
+- Check GoReleaser documentation: <https://goreleaser.com/>
+- File issues at: <https://github.com/benbjohnson/litestream/issues>
+
+Remember: Windows binaries are provided as-is without official support.
diff --git a/etc/gon-sign.hcl b/etc/gon-sign.hcl
new file mode 100644
index 0000000..a53356b
--- /dev/null
+++ b/etc/gon-sign.hcl
@@ -0,0 +1,23 @@
+source = ["./dist/litestream"]
+bundle_id = "com.middlemost.litestream"
+
+apple_id {
+  username = "@env:APPLE_ID_USERNAME"
+  password = "@env:AC_PASSWORD"
+  provider = "@env:APPLE_TEAM_ID"
+}
+
+sign {
+  application_identity = "@env:APPLE_DEVELOPER_ID_APPLICATION"
+  entitlements_file = ""
+}
+
+notarize {
+  path = "./dist/litestream.zip"
+  bundle_id = "com.middlemost.litestream"
+  staple = true
+}
+
+zip {
+  output_path = "./dist/litestream-signed.zip"
+}
diff --git a/grafana/README.md b/grafana/README.md
new file mode 100644
index 0000000..fdf8772
--- /dev/null
+++ b/grafana/README.md
@@ -0,0 +1,55 @@
+# Litestream Grafana Dashboard
+
+This directory contains a Grafana dashboard for monitoring Litestream metrics.
+
+## Prerequisites
+
+1. Litestream configured with metrics endpoint enabled in `litestream.yml`:
+
+   ```yaml
+   addr: ":9090"
+   ```
+
+2. Prometheus configured to scrape Litestream metrics:
+
+   ```yaml
+   scrape_configs:
+     - job_name: 'litestream'
+       static_configs:
+         - targets: ['localhost:9090']
+   ```
+
+3. Grafana with Prometheus data source configured
+
+## Installation
+
+1. Open Grafana and navigate to **Dashboards** → **Import**
+2. Upload the `litestream-dashboard.json` file or paste its contents
+3. Select your Prometheus data source
+4. Click **Import**
+
+## Metrics Included
+
+The dashboard monitors the following key metrics:
+
+- **Database & WAL Size**: Current size of the database and Write-Ahead Log
+- **Total WAL Bytes Written**: Cumulative bytes written to shadow WAL
+- **Sync Operations**: Rate of sync operations and any sync errors
+- **Sync Duration**: Time spent syncing shadow WAL
+- **Checkpoint Operations**: Rate of checkpoint operations by mode
+- **Checkpoint Errors**: Any checkpoint errors that occur
+- **Transaction ID**: Current transaction ID for each database
+- **Replica Operations**: Operations performed by replica type (GET/PUT)
+- **Replica Throughput**: Bytes transferred by replica operations
+
+## Configuration
+
+The dashboard uses template variables:
+
+- `datasource`: Select your Prometheus data source
+- `job`: Select the Prometheus job name (defaults to "litestream")
+
+## Support
+
+For issues or improvements to this dashboard, please open an issue at:
+<https://github.com/benbjohnson/litestream/issues>
diff --git a/grafana/litestream-dashboard.json b/grafana/litestream-dashboard.json
new file mode 100644
index 0000000..7622e54
--- /dev/null
+++ b/grafana/litestream-dashboard.json
@@ -0,0 +1,872 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": "-- Grafana --",
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "type": "dashboard"
+      }
+    ]
+  },
+  "editable": true,
+  "gnetId": null,
+  "graphTooltip": 0,
+  "id": null,
+  "links": [],
+  "panels": [
+    {
+      "datasource": "${datasource}",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "tooltip": false,
+              "viz": false,
+              "legend": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": true,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "bytes"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 0,
+        "y": 0
+      },
+      "id": 2,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.0.0",
+      "targets": [
+        {
+          "expr": "litestream_db_size{job=\"$job\"}",
+          "legendFormat": "{{db}} - DB Size",
+          "refId": "A"
+        },
+        {
+          "expr": "litestream_wal_size{job=\"$job\"}",
+          "legendFormat": "{{db}} - WAL Size",
+          "refId": "B"
+        }
+      ],
+      "title": "Database & WAL Size",
+      "type": "timeseries"
+    },
+    {
+      "datasource": "${datasource}",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "bytes"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 12,
+        "y": 0
+      },
+      "id": 10,
+      "options": {
+        "orientation": "auto",
+        "reduceOptions": {
+          "values": false,
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": ""
+        },
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "text": {}
+      },
+      "pluginVersion": "8.0.0",
+      "targets": [
+        {
+          "expr": "litestream_total_wal_bytes{job=\"$job\"}",
+          "legendFormat": "{{db}}",
+          "refId": "A"
+        }
+      ],
+      "title": "Total WAL Bytes Written",
+      "type": "gauge"
+    },
+    {
+      "datasource": "${datasource}",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "tooltip": false,
+              "viz": false,
+              "legend": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": true,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "ops"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 0,
+        "y": 8
+      },
+      "id": 3,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.0.0",
+      "targets": [
+        {
+          "expr": "rate(litestream_sync_count{job=\"$job\"}[5m])",
+          "legendFormat": "{{db}} - Sync Rate",
+          "refId": "A"
+        }
+      ],
+      "title": "Sync Operations Rate",
+      "type": "timeseries"
+    },
+    {
+      "datasource": "${datasource}",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "tooltip": false,
+              "viz": false,
+              "legend": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": true,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "errors/sec"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 12,
+        "y": 8
+      },
+      "id": 4,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.0.0",
+      "targets": [
+        {
+          "expr": "rate(litestream_sync_error_count{job=\"$job\"}[5m])",
+          "legendFormat": "{{db}} - Sync Errors",
+          "refId": "A"
+        }
+      ],
+      "title": "Sync Error Rate",
+      "type": "timeseries"
+    },
+    {
+      "datasource": "${datasource}",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "tooltip": false,
+              "viz": false,
+              "legend": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": true,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "s"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 0,
+        "y": 16
+      },
+      "id": 5,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.0.0",
+      "targets": [
+        {
+          "expr": "rate(litestream_sync_seconds{job=\"$job\"}[5m])",
+          "legendFormat": "{{db}} - Sync Time",
+          "refId": "A"
+        }
+      ],
+      "title": "Sync Duration",
+      "type": "timeseries"
+    },
+    {
+      "datasource": "${datasource}",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "tooltip": false,
+              "viz": false,
+              "legend": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": true,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "ops"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 12,
+        "y": 16
+      },
+      "id": 6,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.0.0",
+      "targets": [
+        {
+          "expr": "rate(litestream_checkpoint_count{job=\"$job\"}[5m])",
+          "legendFormat": "{{db}} - {{mode}}",
+          "refId": "A"
+        }
+      ],
+      "title": "Checkpoint Operations Rate",
+      "type": "timeseries"
+    },
+    {
+      "datasource": "${datasource}",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "tooltip": false,
+              "viz": false,
+              "legend": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": true,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "errors/sec"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 0,
+        "y": 24
+      },
+      "id": 7,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.0.0",
+      "targets": [
+        {
+          "expr": "rate(litestream_checkpoint_error_count{job=\"$job\"}[5m])",
+          "legendFormat": "{{db}} - {{mode}}",
+          "refId": "A"
+        }
+      ],
+      "title": "Checkpoint Error Rate",
+      "type": "timeseries"
+    },
+    {
+      "datasource": "${datasource}",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "tooltip": false,
+              "viz": false,
+              "legend": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": true,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "none"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 12,
+        "y": 24
+      },
+      "id": 11,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.0.0",
+      "targets": [
+        {
+          "expr": "litestream_txid{job=\"$job\"}",
+          "legendFormat": "{{db}} - Transaction ID",
+          "refId": "A"
+        }
+      ],
+      "title": "Current Transaction ID",
+      "type": "timeseries"
+    },
+    {
+      "datasource": "${datasource}",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "tooltip": false,
+              "viz": false,
+              "legend": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": true,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "ops"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 0,
+        "y": 32
+      },
+      "id": 8,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.0.0",
+      "targets": [
+        {
+          "expr": "rate(litestream_replica_operation_total{job=\"$job\"}[5m])",
+          "legendFormat": "{{replica_type}} - {{operation}}",
+          "refId": "A"
+        }
+      ],
+      "title": "Replica Operations Rate",
+      "type": "timeseries"
+    },
+    {
+      "datasource": "${datasource}",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 10,
+            "gradientMode": "none",
+            "hideFrom": {
+              "tooltip": false,
+              "viz": false,
+              "legend": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "never",
+            "spanNulls": true,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "binBps"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 12,
+        "y": 32
+      },
+      "id": 9,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.0.0",
+      "targets": [
+        {
+          "expr": "rate(litestream_replica_operation_bytes{job=\"$job\"}[5m])",
+          "legendFormat": "{{replica_type}} - {{operation}}",
+          "refId": "A"
+        }
+      ],
+      "title": "Replica Throughput",
+      "type": "timeseries"
+    }
+  ],
+  "refresh": "30s",
+  "schemaVersion": 27,
+  "style": "dark",
+  "tags": ["litestream", "sqlite", "replication"],
+  "templating": {
+    "list": [
+      {
+        "current": {
+          "selected": false,
+          "text": "Prometheus",
+          "value": "Prometheus"
+        },
+        "hide": 0,
+        "includeAll": false,
+        "label": "Data Source",
+        "multi": false,
+        "name": "datasource",
+        "options": [],
+        "query": "prometheus",
+        "queryValue": "",
+        "refresh": 1,
+        "regex": "",
+        "skipUrlSync": false,
+        "type": "datasource"
+      },
+      {
+        "current": {
+          "selected": false,
+          "text": "litestream",
+          "value": "litestream"
+        },
+        "datasource": "${datasource}",
+        "definition": "label_values(litestream_db_size, job)",
+        "hide": 0,
+        "includeAll": false,
+        "label": "Job",
+        "multi": false,
+        "name": "job",
+        "options": [],
+        "query": {
+          "query": "label_values(litestream_db_size, job)",
+          "refId": "StandardVariableQuery"
+        },
+        "refresh": 1,
+        "regex": "",
+        "skipUrlSync": false,
+        "sort": 0,
+        "type": "query"
+      }
+    ]
+  },
+  "time": {
+    "from": "now-1h",
+    "to": "now"
+  },
+  "timepicker": {},
+  "timezone": "",
+  "title": "Litestream Monitoring",
+  "uid": "litestream-monitoring",
+  "version": 1
+}
diff --git a/scripts/setup-homebrew-tap.sh b/scripts/setup-homebrew-tap.sh
new file mode 100755
index 0000000..176e894
--- /dev/null
+++ b/scripts/setup-homebrew-tap.sh
@@ -0,0 +1,85 @@
+#!/bin/bash
+set -e
+
+echo "Setting up Homebrew tap repository for Litestream..."
+
+REPO_NAME="homebrew-litestream"
+GITHUB_USER="benbjohnson"
+
+echo "This script will help you create the ${GITHUB_USER}/${REPO_NAME} repository."
+echo ""
+echo "Prerequisites:"
+echo "1. GitHub CLI (gh) must be installed and authenticated"
+echo "2. You must have permission to create repositories under ${GITHUB_USER}"
+echo ""
+read -p "Do you want to continue? (y/n) " -n 1 -r
+echo ""
+
+if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+    echo "Aborted."
+    exit 1
+fi
+
+echo "Creating repository ${GITHUB_USER}/${REPO_NAME}..."
+gh repo create ${GITHUB_USER}/${REPO_NAME} \
+    --public \
+    --description "Homebrew tap for Litestream" \
+    --clone=false || echo "Repository may already exist, continuing..."
+
+echo ""
+echo "Cloning repository..."
+TEMP_DIR=$(mktemp -d)
+cd "$TEMP_DIR"
+gh repo clone ${GITHUB_USER}/${REPO_NAME} || git clone "https://github.com/${GITHUB_USER}/${REPO_NAME}.git"
+
+cd ${REPO_NAME}
+
+echo "Creating Formula directory..."
+mkdir -p Formula
+
+echo "Creating README..."
+cat > README.md << 'EOF'
+# Homebrew Tap for Litestream
+
+This is the official Homebrew tap for [Litestream](https://github.com/benbjohnson/litestream).
+
+## Installation
+
+```bash
+brew tap benbjohnson/litestream
+brew install litestream
+```
+
+## Documentation
+
+For more information about Litestream, visit:
+- [GitHub Repository](https://github.com/benbjohnson/litestream)
+- [Official Documentation](https://litestream.io)
+
+## License
+
+Apache License 2.0
+EOF
+
+echo "Creating initial Formula placeholder..."
+cat > Formula/.gitkeep << 'EOF'
+# This file ensures the Formula directory is tracked by git
+# GoReleaser will automatically create and update formula files here
+EOF
+
+echo "Committing initial structure..."
+git add .
+git commit -m "Initial tap structure" || echo "Nothing to commit"
+
+echo "Pushing to GitHub..."
+git push origin main || git push origin master
+
+echo ""
+echo "✅ Homebrew tap repository setup complete!"
+echo ""
+echo "Repository: https://github.com/${GITHUB_USER}/${REPO_NAME}"
+echo ""
+echo "Next steps:"
+echo "1. Create a GitHub Personal Access Token with 'repo' scope"
+echo "2. Add it as HOMEBREW_TAP_GITHUB_TOKEN secret in the main repository"
+echo "3. GoReleaser will automatically update the tap on each release"
diff --git a/sftp/replica_client.go b/sftp/replica_client.go
index 4bb8f4f..f77cf80 100644
--- a/sftp/replica_client.go
+++ b/sftp/replica_client.go
@@ -42,12 +42,17 @@ type ReplicaClient struct {
 	Path        string
 	KeyPath     string
 	DialTimeout time.Duration
+
+	// ConcurrentWrites enables concurrent writes for better performance.
+	// Note: This makes resuming failed transfers unsafe.
+	ConcurrentWrites bool
 }
 
 // NewReplicaClient returns a new instance of ReplicaClient.
 func NewReplicaClient() *ReplicaClient {
 	return &ReplicaClient{
-		DialTimeout: DefaultDialTimeout,
+		DialTimeout:      DefaultDialTimeout,
+		ConcurrentWrites: true, // Default to true for better performance
 	}
 }
 
@@ -104,7 +109,13 @@ func (c *ReplicaClient) Init(ctx context.Context) (_ *sftp.Client, err error) {
 	}
 
 	// Wrap connection with an SFTP client.
-	if c.sftpClient, err = sftp.NewClient(c.sshClient); err != nil {
+	// Configure options based on client settings
+	opts := []sftp.ClientOption{}
+	if c.ConcurrentWrites {
+		opts = append(opts, sftp.UseConcurrentWrites(true))
+	}
+
+	if c.sftpClient, err = sftp.NewClient(c.sshClient, opts...); err != nil {
 		c.sshClient.Close()
 		c.sshClient = nil
 		return nil, err



Test patch:
diff --git a/cmd/litestream/main.go b/cmd/litestream/main.go
index 0121edf..3bf00b1 100644
--- a/cmd/litestream/main.go
+++ b/cmd/litestream/main.go
@@ -556,10 +556,11 @@ type ReplicaConfig struct {
 	AccountKey  string `yaml:"account-key"`
 
 	// SFTP settings
-	Host     string `yaml:"host"`
-	User     string `yaml:"user"`
-	Password string `yaml:"password"`
-	KeyPath  string `yaml:"key-path"`
+	Host             string `yaml:"host"`
+	User             string `yaml:"user"`
+	Password         string `yaml:"password"`
+	KeyPath          string `yaml:"key-path"`
+	ConcurrentWrites *bool  `yaml:"concurrent-writes"`
 
 	// NATS settings
 	JWT           string         `yaml:"jwt"`
@@ -868,6 +869,12 @@ func newSFTPReplicaClientFromConfig(c *ReplicaConfig, _ *litestream.Replica) (_
 	client.Password = password
 	client.Path = path
 	client.KeyPath = c.KeyPath
+
+	// Set concurrent writes if specified, otherwise use default (true)
+	if c.ConcurrentWrites != nil {
+		client.ConcurrentWrites = *c.ConcurrentWrites
+	}
+
 	return client, nil
 }