Repository: benbjohnson/litestream

Issue:
If -exec is used after the replicate positional arguments it throws a confusing error
This works fine:
```
% litestream replicate \
  -exec "datasette -h 0.0.0.0 -p 8042 notebook.db" \
  notebook.db \
  "s3://my-litestream-bucket/notebook.db"
```
But if you put the `-exec` option AFTER the positional arguments (which is usually how I use CLI tools - positional arguments first, options second) you instead get a confusing error message:
```
% litestream replicate \
  notebook.db \
  "s3://my-litestream-bucket/notebook.db" \
  -exec "datasette -h 0.0.0.0 -p 8042 notebook.db"
litestream v0.3.6
replica url scheme required: -exec
```
I'd like it if `-exec` worked after the other arguments, but an alternative fix would be to improve that error message, maybe to:

```
-exec should be positioned before DB_PATH and REPLICA_URL
```

Hints from issue comments:
@simonw: I just spotted that `litestream replicate --help` does provide additional clues here:
```
% litestream replicate --help
The replicate command starts a server to monitor & replicate databases. 
You can specify your database & replicas in a configuration file or you can
replicate a single database file by specifying its path and its replicas in the
command line arguments.

Usage:

	litestream replicate [arguments]

	litestream replicate [arguments] DB_PATH REPLICA_URL [REPLICA_URL...]
```@benbjohnson: Thanks for reporting the issue, @simonw. I'm surprised to see that Go's built-in `flag` package doesn't handle flags after the positional args (https://play.golang.org/p/uWQiOrWIK18). I always assumed it did.

I don't necessarily want to add in a third party dependency like [cobra](https://github.com/spf13/cobra) just to add support for this but I can fix up the error message so it's more clear. I could potentially rewrite the args list to move flags to the beginning but that seems like it could have edge cases.

@alexpetros: Want to +1, had the same issue and found the solution here.@corylanou: This issue has been transferred to the litestream.io repository where documentation issues are now tracked: https://github.com/benbjohnson/litestream.io/issues/101@corylanou: Transferred to litestream.io repository for documentation tracking.

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
 }
 
diff --git a/cmd/litestream/replicate.go b/cmd/litestream/replicate.go
index 9d34edd..2cf2eb7 100644
--- a/cmd/litestream/replicate.go
+++ b/cmd/litestream/replicate.go
@@ -10,6 +10,7 @@ import (
 	_ "net/http/pprof"
 	"os"
 	"os/exec"
+	"strings"
 
 	"github.com/mattn/go-shellwords"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -79,6 +80,10 @@ func (c *ReplicateCommand) ParseFlags(_ context.Context, args []string) (err err
 
 		dbConfig := &DBConfig{Path: fs.Arg(0)}
 		for _, u := range fs.Args()[1:] {
+			// Check if this looks like a flag that was placed after positional arguments
+			if strings.HasPrefix(u, "-") {
+				return fmt.Errorf("flag %q must be positioned before DB_PATH and REPLICA_URL arguments", u)
+			}
 			syncInterval := litestream.DefaultSyncInterval
 			dbConfig.Replicas = append(dbConfig.Replicas, &ReplicaConfig{
 				URL:          u,
diff --git a/cmd/litestream/replicate_test.go b/cmd/litestream/replicate_test.go
new file mode 100644
index 0000000..6871fe7
--- /dev/null
+++ b/cmd/litestream/replicate_test.go
@@ -0,0 +1,97 @@
+package main_test
+
+import (
+	"context"
+	"strings"
+	"testing"
+
+	main "github.com/benbjohnson/litestream/cmd/litestream"
+)
+
+func TestReplicateCommand_ParseFlags_FlagPositioning(t *testing.T) {
+	t.Run("ExecFlagAfterPositionalArgs", func(t *testing.T) {
+		cmd := main.NewReplicateCommand()
+
+		// Test the scenario from issue #245: -exec flag after positional arguments
+		args := []string{"test.db", "s3://bucket/test.db", "-exec", "echo test"}
+
+		err := cmd.ParseFlags(context.Background(), args)
+		if err == nil {
+			t.Fatal("expected error when -exec flag is positioned after positional arguments")
+		}
+
+		expectedError := `flag "-exec" must be positioned before DB_PATH and REPLICA_URL arguments`
+		if !strings.Contains(err.Error(), expectedError) {
+			t.Errorf("expected error message to contain %q, got %q", expectedError, err.Error())
+		}
+	})
+
+	t.Run("ExecFlagBeforePositionalArgs", func(t *testing.T) {
+		cmd := main.NewReplicateCommand()
+
+		// Test the correct usage: -exec flag before positional arguments
+		args := []string{"-exec", "echo test", "test.db", "s3://bucket/test.db"}
+
+		err := cmd.ParseFlags(context.Background(), args)
+		if err != nil {
+			t.Fatalf("unexpected error when -exec flag is positioned correctly: %v", err)
+		}
+
+		// Verify the exec command was set correctly
+		if cmd.Config.Exec != "echo test" {
+			t.Errorf("expected exec command to be %q, got %q", "echo test", cmd.Config.Exec)
+		}
+	})
+
+	t.Run("ConfigFlagAfterPositionalArgs", func(t *testing.T) {
+		cmd := main.NewReplicateCommand()
+
+		// Test other flags after positional arguments
+		args := []string{"test.db", "s3://bucket/test.db", "-config", "/path/to/config"}
+
+		err := cmd.ParseFlags(context.Background(), args)
+		if err == nil {
+			t.Fatal("expected error when -config flag is positioned after positional arguments")
+		}
+
+		expectedError := `flag "-config" must be positioned before DB_PATH and REPLICA_URL arguments`
+		if !strings.Contains(err.Error(), expectedError) {
+			t.Errorf("expected error message to contain %q, got %q", expectedError, err.Error())
+		}
+	})
+
+	t.Run("MultipleFlags", func(t *testing.T) {
+		cmd := main.NewReplicateCommand()
+
+		// Test multiple flags in correct position
+		args := []string{"-exec", "echo test", "-no-expand-env", "test.db", "s3://bucket/test.db"}
+
+		err := cmd.ParseFlags(context.Background(), args)
+		if err != nil {
+			t.Fatalf("unexpected error with multiple flags positioned correctly: %v", err)
+		}
+
+		// Verify the exec command was set correctly
+		if cmd.Config.Exec != "echo test" {
+			t.Errorf("expected exec command to be %q, got %q", "echo test", cmd.Config.Exec)
+		}
+	})
+
+	t.Run("OnlyDatabasePathProvided", func(t *testing.T) {
+		cmd := main.NewReplicateCommand()
+
+		// Test with only database path (should error but for different reason)
+		args := []string{"test.db"}
+
+		err := cmd.ParseFlags(context.Background(), args)
+		if err == nil {
+			t.Fatal("expected error when only database path is provided without replica URL")
+		}
+
+		// Should get the "must specify at least one replica URL" error, not the flag positioning error
+		expectedError := "must specify at least one replica URL"
+		if !strings.Contains(err.Error(), expectedError) {
+			t.Errorf("expected error message to contain %q, got %q", expectedError, err.Error())
+		}
+	})
+}