diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..0c131bba --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# Ora Browser Private Key +# This file contains sensitive cryptographic keys - NEVER commit to git! +# Copy this file to .env and add your actual private key + +ORA_PRIVATE_KEY=-----BEGIN PRIVATE KEY----- +# Paste your Ed25519 private key here +# This key is used to sign app updates for security +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/.github/workflows/deploy-appcast.yml b/.github/workflows/deploy-appcast.yml new file mode 100644 index 00000000..e0567570 --- /dev/null +++ b/.github/workflows/deploy-appcast.yml @@ -0,0 +1,32 @@ +name: Deploy Appcast + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to deploy' + required: true + type: string + +jobs: + deploy-appcast: + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download appcast from release + run: | + # Download the appcast.xml from the latest release + curl -L -o appcast.xml "https://raw.githubusercontent.com/${{ github.repository }}/release/appcast.xml" + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: . + publish_branch: gh-pages + keep_files: true + exclude_assets: 'build/**,*.sh,*.yml,*.md,*.json,*.xcodeproj/**,ora/**,docs/**,SECURITY.md,check-security.sh,.github/**,.gitignore,README.md,LICENSE.md' + destination_dir: . \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4e718e53..987092ae 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,45 @@ Temporary Items .vscode/ .idea/ -# App specific -*.app \ No newline at end of file +# Build directory (allow directory but ignore contents) +build/* +!build/ +!build/.gitkeep + +# Build artifacts and releases +*.app +*.dmg +*.pkg +*.zip +*.tar.gz +appcast.xml +appcast.xml.bak +dsa_priv.pem +dsa_pub.pem +exportOptions.plist +Ora-Browser.dmg +rw.*.Ora-Browser.dmg + +# Sparkle and signing (SECURITY: Never commit private keys!) +*_private.pem +*_public.pem +*.pem +dsa_priv.pem # CRITICAL: Never commit this file! +dsa_pub.pem # Public key (safe to commit if needed) + +# Environment files (contains private keys!) +.env +.env.local +.env.*.local + +# Archives and temporary files +*.xcarchive +*.xctimeline +*.trace +*.log + +# Release artifacts (keep in build/ folder) +# Uncomment these if you want to commit release artifacts: +# !build/*.dmg +# !build/appcast.xml +# !build/dsa_pub.pem \ No newline at end of file diff --git a/README.md b/README.md index 1c52e9fd..7f2eeb35 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,10 @@ Ora is a fast, secure, and beautiful browser built for macOS. Inspired by Safari and Arc, Ora delivers a clean, native experience that feels at home on macOS β€” without unnecessary bloat. > **⚠️ Disclaimer** -> Ora is currently in active development and **not yet ready for day-to-day use**. -> An alpha version with core features will be released soon. Use at your own discretion. +Ora is currently in early stages of development and **not yet ready for day-to-day use**. An alpha version with core functionalities will be released soon. + +If you would like to support the project, please consider donating via [Buy Me A Coffee](https://buymeacoffee.com/orabrowser). +[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://buymeacoffee.com/orabrowser) ## Features @@ -47,6 +49,33 @@ Ora is a fast, secure, and beautiful browser built for macOS. Inspired by Safari - Tools: `xcodegen`, `swiftlint`, `swiftformat` (installed by the setup script) - Optional: `xcbeautify` (for prettier CLI build output) +## Key Management for Updates + +Ora Browser uses Ed25519 cryptographic keys to sign and verify app updates for security: + +### Public Key (Committed to Git) +- **File**: `ora_public_key.pem` +- **Purpose**: Verifies update signatures in the app +- **Status**: Committed to git repository +- **Safety**: Public keys are safe to share + +### Private Key (Never Commit!) +- **File**: `.env` (contains `ORA_PRIVATE_KEY`) +- **Purpose**: Signs app updates during release +- **Status**: Never committed to git +- **Safety**: Keep secure and private + +### Setup Process +1. **First machine**: Keys auto-generated and saved appropriately +2. **Additional machines**: Copy `.env` file from first machine +3. **Release process**: `./create-release.sh` handles key management automatically + +### Security Notes +- `.env` is in `.gitignore` - it will never be committed +- Public key is committed - this is safe and required +- Never share your private key with anyone +- If private key is lost, you'll need to regenerate keys (breaks update chain) + ## Installation 1. Clone the repository. @@ -126,37 +155,39 @@ rm -f "$(getconf DARWIN_USER_DIR 2>/dev/null || echo "$HOME/Library/Application ### Releases and Updates -Ora uses [Sparkle](https://sparkle-project.org/) for automatic updates. To set up releases: +Ora uses [Sparkle](https://sparkle-project.org/) for automatic updates. All build artifacts are organized in the `build/` directory. 1. **Add Sparkle dependency:** - Open `Ora.xcodeproj` in Xcode - - Go to File > Add Packages... + - Go to File β†’ Add Packages... - Add `https://github.com/sparkle-project/Sparkle` (version 2.6.3+) - Add Sparkle to your target -2. **Setup Sparkle keys:** +2. **Setup Sparkle tools:** ```bash + brew install --cask sparkle + ./setup-sparkle-tools.sh ./setup-sparkle.sh ``` - This generates DSA keys for signing releases. + This generates DSA keys in `build/` directory. 3. **Configure signing:** - - Copy the public key from `dsa_pub.pem` to your `Info.plist` as `SUPublicEDKey` - - Keep `dsa_priv.pem` secure for signing releases + - Copy the public key from `build/dsa_pub.pem` to your `Info.plist` as `SUPublicEDKey` + - Keep `build/dsa_priv.pem` secure for signing releases - Add `SUFeedURL` to Info.plist pointing to your appcast.xml URL 4. **Create a release:** ```bash - ./create-release.sh 0.0.2 dsa_priv.pem + ./create-release.sh 0.0.2 build/dsa_priv.pem ``` - This builds, signs, and prepares the release files. + This builds, signs, and prepares release files in `build/`. 5. **Host appcast.xml:** - - Upload `appcast.xml` to a public URL (e.g., GitHub Pages) + - Upload `build/appcast.xml` to a public URL (e.g., GitHub Pages) - Update `SUFeedURL` in `Info.plist` to point to your appcast.xml 6. **Publish release:** - - Upload `Ora-Browser.dmg` to GitHub releases + - Upload `build/Ora-Browser.dmg` to GitHub releases - Users will automatically receive update notifications The app includes automatic update checking in Settings > General. @@ -178,6 +209,12 @@ The app includes automatic update checking in Settings > General. Keyboard shortcuts: see `ora/Common/Constants/KeyboardShortcuts.swift`. +## Documentation + +- **[Quick Start Guide](docs/QUICK_START.md)** - 5-minute setup for hosting and updates +- **[Hosting Setup Guide](docs/HOSTING_SETUP.md)** - Complete guide for update hosting and deployment +- **[Documentation Index](docs/README.md)** - All documentation organized by topic + ## Contributing Contributions are welcome! To propose changes: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..aab01e4e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,43 @@ +# Ora Browser Security Guide + +## πŸ” DSA Key Management + +### Private Key Security +- **NEVER** commit `build/dsa_priv.pem` to version control +- **NEVER** share the private key with anyone +- **NEVER** delete the private key once you've published releases with it + +### Key Generation & Reuse +- Generate DSA keys **once** when setting up Sparkle updates +- **Reuse the same keys** for all future releases +- Deleting and regenerating keys will break the update chain for existing users + +### Security Checks +Run `./check-security.sh` to verify: +- Private key exists but is not tracked by git +- Public key is available for app integration +- `.gitignore` properly excludes sensitive files + +### Release Process +1. Run `./create-release.sh ` - reuses existing keys automatically +2. Upload `build/Ora-Browser.dmg` to GitHub releases +3. Host `build/appcast.xml` at a public URL +4. Add `build/dsa_pub.pem` content to app's `SUPublicEDKey` in Info.plist + +### Files to Keep Secure +- `build/dsa_priv.pem` - **KEEP PRIVATE, NEVER COMMIT** +- `build/dsa_pub.pem` - Safe to commit if needed for CI/CD +- `build/appcast.xml` - Contains signatures, safe to host publicly + +### Emergency Key Regeneration +If private key is compromised: +1. Delete `build/dsa_priv.pem` and `build/dsa_pub.pem` +2. Run `./create-release.sh ` to generate fresh keys +3. Update app's `SUPublicEDKey` with new public key +4. Existing users will need to download fresh installers + +## 🚨 Security Violations +If you see any of these, stop immediately: +- `dsa_priv.pem` appears in `git status` +- Private key is committed to repository +- Private key is shared or transmitted insecurely \ No newline at end of file diff --git a/appcast.xml b/appcast.xml index def3d302..53d06f2b 100644 --- a/appcast.xml +++ b/appcast.xml @@ -1,31 +1,30 @@ - - Ora Browser Changelog - Most recent changes with links to updates. - en - - Ora Browser 0.0.1 - - Initial Release -

Ora Browser is a fast, secure, and beautiful browser built for macOS.

-
    -
  • Native macOS UI built with SwiftUI
  • -
  • Fast browsing powered by WebKit
  • -
  • Privacy-first with built-in content blocker
  • -
  • Multiple search engine support
  • -
  • Developer tools and extensions support
  • -
- ]]> -
- Mon, 04 Sep 2025 12:00:00 +0000 - -
-
-
\ No newline at end of file + + Ora Browser Changelog + Most recent changes with links to updates. + en + + Version 0.0.62 + Ora Browser v0.0.62 +

Latest release of Ora Browser with the following features:

+
    +
  • Modern web browsing experience
  • +
  • Tabbed interface with sidebar
  • +
  • Built-in ad blocking
  • +
  • Privacy-focused design
  • +
  • Automatic update system
  • +
+

This release includes bug fixes and performance improvements. Enjoy browsing with Ora!

+ ]]>
+ Fri, 05 Sep 2025 14:54:35 +0000 + +
+
+ diff --git a/appcast.xml.backup b/appcast.xml.backup new file mode 100644 index 00000000..bac01bbd --- /dev/null +++ b/appcast.xml.backup @@ -0,0 +1,30 @@ + + + + Ora Browser Changelog + Most recent changes with links to updates. + en + + Version 0.0.30 + Ora Browser v0.0.30 +

Latest release of Ora Browser with the following features:

+
    +
  • Modern web browsing experience
  • +
  • Tabbed interface with sidebar
  • +
  • Built-in ad blocking
  • +
  • Privacy-focused design
  • +
  • Automatic update system
  • +
+

This release includes bug fixes and performance improvements. Enjoy browsing with Ora!

+ ]]>
+ Fri, 05 Sep 2025 08:19:17 +0000 + +
+
+
diff --git a/build-release.sh b/build-release.sh index aee8775f..31865d79 100755 --- a/build-release.sh +++ b/build-release.sh @@ -6,55 +6,147 @@ set -e echo "πŸ—οΈ Building Ora Browser Release..." -# Clean previous builds +# Clean previous builds but preserve DSA keys and appcast echo "🧹 Cleaning previous builds..." +# Preserve important files +if [ -f "build/dsa_priv.pem" ]; then + mv build/dsa_priv.pem /tmp/dsa_priv.pem.backup +fi +if [ -f "build/dsa_pub.pem" ]; then + mv build/dsa_pub.pem /tmp/dsa_pub.pem.backup +fi +if [ -f "build/appcast.xml" ]; then + mv build/appcast.xml /tmp/appcast.xml.backup +fi +if [ -f "appcast.xml" ]; then + mv appcast.xml /tmp/root_appcast.xml.backup +fi +if [ -f "build/.gitkeep" ]; then + mv build/.gitkeep /tmp/.gitkeep.backup +fi + rm -rf build/ -rm -rf Ora.app/ +mkdir -p build -# Generate Xcode project if needed -if [ ! -f "Ora.xcodeproj" ]; then - echo "πŸ“‹ Generating Xcode project..." - xcodegen +# Restore preserved files +if [ -f "/tmp/dsa_priv.pem.backup" ]; then + mv /tmp/dsa_priv.pem.backup build/dsa_priv.pem +fi +if [ -f "/tmp/dsa_pub.pem.backup" ]; then + mv /tmp/dsa_pub.pem.backup build/dsa_pub.pem +fi +if [ -f "/tmp/appcast.xml.backup" ]; then + mv /tmp/appcast.xml.backup build/appcast.xml +fi +if [ -f "/tmp/root_appcast.xml.backup" ]; then + mv /tmp/root_appcast.xml.backup appcast.xml fi +if [ -f "/tmp/.gitkeep.backup" ]; then + mv /tmp/.gitkeep.backup build/.gitkeep +fi + +# Clean up any leftover DMG files +rm -f *.dmg + +# Get version from project.yml +VERSION=$(grep "MARKETING_VERSION:" project.yml | sed 's/.*MARKETING_VERSION: //' | tr -d ' ') +DMG_NAME="Ora-Browser-${VERSION}.dmg" + +# Create export options plist +echo "βš™οΈ Creating export options..." +cat > build/exportOptions.plist << 'EOF' + + + + + method + mac-application + teamID + + destination + export + signingStyle + automatic + stripSwiftSymbols + + + +EOF + +# Generate Xcode project (always regenerate to ensure latest project.yml) +echo "πŸ“‹ Generating Xcode project..." +xcodegen -# Build the app +# Build the app directly (faster than archiving) echo "πŸ”¨ Building release version..." xcodebuild build \ -scheme ora \ -configuration Release \ -destination "platform=macOS" \ - -archivePath "build/Ora.xcarchive" \ - archive + -derivedDataPath "build/DerivedData" \ + > /dev/null 2>&1 -# Export the app -echo "πŸ“¦ Exporting app..." -xcodebuild -exportArchive \ - -archivePath "build/Ora.xcarchive" \ - -exportPath "build/" \ - -exportOptionsPlist "exportOptions.plist" +# Copy the built app to build directory +echo "πŸ“¦ Copying built app..." +if [ -d "build/DerivedData/Build/Products/Release/Ora.app" ]; then + cp -r "build/DerivedData/Build/Products/Release/Ora.app" "build/" + echo "βœ… App copied to build directory" +else + echo "❌ Built app not found in expected location" + exit 1 +fi # Create DMG if create-dmg is available if command -v create-dmg &> /dev/null; then - echo "πŸ’Ώ Creating DMG..." - create-dmg \ - --volname "Ora Browser" \ - --volicon "ora/Assets.xcassets/AppIcon.appiconset/ora-white-macos-icon.png" \ - --window-pos 200 120 \ - --window-size 800 400 \ - --icon-size 100 \ - --icon "Ora.app" 200 190 \ - --hide-extension "Ora.app" \ - --app-drop-link 600 185 \ - "Ora-Browser.dmg" \ - "build/Ora.app" + if [ -d "build/Ora.app" ]; then + echo "πŸ’Ώ Creating DMG..." + # Remove any existing DMG first + rm -f "build/${DMG_NAME}" + # Use simpler create-dmg command to avoid parsing issues + # --window-pos 200 120 \ + # --icon-size 100 \ + # --icon "build/Ora.app" 200 190 \ + # --volname "Ora Browser" \ + # --hide-extension "build/Ora.app" \ + create-dmg \ + --app-drop-link 600 185 \ + --window-size 800 400 \ + "build/${DMG_NAME}" \ + "build/Ora.app" 2>/dev/null || { + echo "⚠️ create-dmg had warnings but continuing..." + } + # Rename DMG if it was created with temporary name + TEMP_DMG=$(ls build/rw.*.${DMG_NAME} 2>/dev/null | head -1) + if [ -n "$TEMP_DMG" ]; then + mv "$TEMP_DMG" "build/${DMG_NAME}" + fi + + # Verify DMG was created + if [ -f "build/${DMG_NAME}" ]; then + echo "βœ… DMG created successfully!" + else + echo "❌ DMG creation failed!" + exit 1 + fi + else + echo "❌ Ora.app not found in build directory. Cannot create DMG." + exit 1 + fi else echo "⚠️ create-dmg not found. Skipping DMG creation." echo "Install with: brew install create-dmg" fi -echo "βœ… Release build complete!" -echo "πŸ“ Release files:" -ls -la build/ -if [ -f "Ora-Browser.dmg" ]; then - ls -la Ora-Browser.dmg +# Verify DMG creation +if [ -f "build/${DMG_NAME}" ]; then + echo "βœ… Release build complete!" + echo "πŸ“ Release files in build/:" + ls -la build/ + echo "" + echo "πŸš€ Ready for distribution:" + echo " - build/${DMG_NAME} ($(du -h build/${DMG_NAME} | cut -f1))" + echo " - build/Ora.app (macOS application)" +else + echo "❌ DMG creation failed!" + exit 1 fi \ No newline at end of file diff --git a/check-security.sh b/check-security.sh new file mode 100755 index 00000000..716edbaf --- /dev/null +++ b/check-security.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +echo "πŸ”’ Checking Ora Browser Security..." + +# Check if private key exists +if [ -f "build/dsa_priv.pem" ]; then + echo "βœ… DSA private key found in build/dsa_priv.pem" + + # Check if it's in git (it shouldn't be) + if git ls-files | grep -q "dsa_priv.pem"; then + echo "❌ SECURITY ISSUE: Private key is tracked by git!" + echo " Run: git rm --cached build/dsa_priv.pem" + exit 1 + else + echo "βœ… Private key is not tracked by git" + fi +else + echo "⚠️ DSA private key not found - will be generated on next release" +fi + +# Check if public key exists +if [ -f "build/dsa_pub.pem" ]; then + echo "βœ… DSA public key found in build/dsa_pub.pem" +else + echo "⚠️ DSA public key not found" +fi + +# Check .gitignore +if grep -q "dsa_priv.pem" .gitignore; then + echo "βœ… Private key is properly ignored in .gitignore" +else + echo "❌ SECURITY ISSUE: Private key not in .gitignore!" + exit 1 +fi + +echo "" +echo "πŸ” Security check complete!" +echo "Remember: Never commit build/dsa_priv.pem to version control!" \ No newline at end of file diff --git a/create-release.sh b/create-release.sh index 47a3740e..8f20c88c 100755 --- a/create-release.sh +++ b/create-release.sh @@ -3,63 +3,440 @@ set -e # Create Release Script for Ora Browser # This script creates a signed release and updates the appcast +# +# Key Management: +# - Public key: Stored in ora_public_key.pem (committed to git) +# - Private key: Stored in .env file (NEVER commit this!) +# - If keys don't exist, new Ed25519 keys are generated +# - If private key missing, build fails with clear instructions +# +# Usage: $0 [version] +# If no version is provided, it will auto-increment the patch version from project.yml +# Example: $0 0.0.36 +# Handle version argument if [ $# -lt 1 ]; then - echo "Usage: $0 [private_key_file]" - echo "Example: $0 0.0.2 ../dsa_priv.pem" - exit 1 + # Auto-increment version + if [ -f "project.yml" ]; then + CURRENT_VERSION=$(grep "MARKETING_VERSION:" project.yml | sed 's/.*MARKETING_VERSION: //' | tr -d ' ') + if [ -n "$CURRENT_VERSION" ]; then + # Increment the last number + VERSION=$(echo "$CURRENT_VERSION" | awk -F. '{print $1"."$2"."($3+1)}') + echo "Auto-incrementing version from $CURRENT_VERSION to $VERSION" + else + echo "Could not find MARKETING_VERSION in project.yml" + exit 1 + fi + else + echo "project.yml not found for auto-increment" + exit 1 + fi +else + VERSION=$1 fi +# Private key will be determined by the key management section above +# PRIVATE_KEY is set dynamically based on available keys -VERSION=$1 -PRIVATE_KEY=${2:-"dsa_priv.pem"} +# Save original directory +ORIGINAL_DIR="$(pwd)" echo "πŸš€ Creating Ora Browser Release v$VERSION..." +# Update project.yml with the release version and public key +echo "πŸ“ Updating project.yml with version $VERSION..." +if [ -f "project.yml" ]; then + # Update MARKETING_VERSION + sed -i.bak "s/MARKETING_VERSION: .*/MARKETING_VERSION: $VERSION/" project.yml + + # Update CURRENT_PROJECT_VERSION (use the numeric part after last dot, or increment) + BUILD_VERSION=$(echo $VERSION | awk -F. '{print $NF + 0}') + sed -i.bak "s/CURRENT_PROJECT_VERSION: .*/CURRENT_PROJECT_VERSION: $BUILD_VERSION/" project.yml + + echo "βœ… Updated project.yml: MARKETING_VERSION=$VERSION, CURRENT_PROJECT_VERSION=$BUILD_VERSION" +else + echo "⚠️ project.yml not found, skipping version update" +fi + +# Clean build directory for fresh build +echo "🧹 Cleaning build directory..." +rm -rf build/ +mkdir -p build + +# Setup Sparkle (generate DSA keys and install tools) +echo "πŸ” Setting up Sparkle for Ora Browser..." + +# Setup Sparkle tools PATH +echo "πŸ”§ Setting up Sparkle tools..." + +# Check if Sparkle is installed via Homebrew +if command -v brew &> /dev/null && brew list sparkle &> /dev/null; then + echo "βœ… Sparkle found via Homebrew" + SPARKLE_BIN_PATH="/opt/homebrew/Caskroom/sparkle/2.7.1/bin" + export PATH="$SPARKLE_BIN_PATH:$PATH" +elif [ -d "/opt/homebrew/Caskroom/sparkle" ]; then + # Find the latest version + SPARKLE_VERSION=$(ls /opt/homebrew/Caskroom/sparkle/ | sort -V | tail -1) + SPARKLE_BIN_PATH="/opt/homebrew/Caskroom/sparkle/$SPARKLE_VERSION/bin" + export PATH="$SPARKLE_BIN_PATH:$PATH" +else + echo "❌ Sparkle not found. Installing via Homebrew..." + if command -v brew &> /dev/null; then + brew install sparkle + SPARKLE_BIN_PATH="/opt/homebrew/Caskroom/sparkle/2.7.1/bin" + export PATH="$SPARKLE_BIN_PATH:$PATH" + else + echo "❌ Homebrew not found. Please install Homebrew first." + exit 1 + fi +fi + +echo "πŸ”§ Sparkle tools path: $SPARKLE_BIN_PATH" + +# Verify tools are available +if ! command -v generate_keys &> /dev/null; then + echo "❌ generate_keys command not found in PATH" + echo "Current PATH: $PATH" + exit 1 +fi + +if ! command -v sign_update &> /dev/null; then + echo "❌ sign_update command not found in PATH" + echo "Current PATH: $PATH" + exit 1 +fi + +echo "βœ… Sparkle tools ready!" + +# Ensure build directory exists +mkdir -p build + +# Key management: Public key in root (committed), Private key in .env (not committed) +PUBLIC_KEY_FILE="ora_public_key.pem" +PRIVATE_KEY_FILE=".env" +PRIVATE_KEY_CONTENT="" # Global variable to store private key content + +# Check if public key exists in root directory +if [ -f "$PUBLIC_KEY_FILE" ]; then + echo "πŸ”‘ Found existing public key in $PUBLIC_KEY_FILE" + PUBLIC_KEY=$(cat "$PUBLIC_KEY_FILE") + echo "βœ… Using existing public key: ${PUBLIC_KEY:0:20}..." + + # Update SUPublicEDKey in project.yml now that we have the public key + if [ -f "project.yml" ]; then + # Escape forward slashes in PUBLIC_KEY for sed + ESCAPED_PUBLIC_KEY=$(echo "$PUBLIC_KEY" | sed 's/\//\\\//g') + sed -i.bak "s/SUPublicEDKey: .*/SUPublicEDKey: \"$ESCAPED_PUBLIC_KEY\"/" project.yml + echo "βœ… Updated SUPublicEDKey in project.yml: ${PUBLIC_KEY:0:20}..." + fi + + # Check if private key exists in .env file + if [ -f "$PRIVATE_KEY_FILE" ]; then + echo "πŸ”‘ Found private key in $PRIVATE_KEY_FILE" + PRIVATE_KEY_CONTENT=$(grep "ORA_PRIVATE_KEY=" "$PRIVATE_KEY_FILE" | cut -d'=' -f2-) + if [ -n "$PRIVATE_KEY_CONTENT" ]; then + echo "βœ… Private key found in .env file" + # Store content globally and create temporary private key file for now + echo "$PRIVATE_KEY_CONTENT" > build/temp_private_key.pem + PRIVATE_KEY="build/temp_private_key.pem" + else + echo "❌ Private key not found in .env file!" + echo " Please add your private key to $PRIVATE_KEY_FILE in this format:" + echo " ORA_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----..." + exit 1 + fi + else + echo "❌ Private key file (.env) not found!" + echo " Please create a .env file with your private key:" + echo " ORA_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----..." + echo " Or run this script from a machine that has the keys." + exit 1 + fi +else + echo "πŸ”‘ No public key found. Generating new Ed25519 keypair..." + + # Generate new Ed25519 keys + if generate_keys --account "ora-browser-ed25519"; then + echo "βœ… New Ed25519 keys generated" + + # Get public key and save to root directory (will be committed) + PUBLIC_KEY=$(generate_keys --account "ora-browser-ed25519" -p) + echo "$PUBLIC_KEY" > "$PUBLIC_KEY_FILE" + echo "βœ… Public key saved to $PUBLIC_KEY_FILE (will be committed to git)" + + # Export private key and save to .env file (will NOT be committed) + if generate_keys --account "ora-browser-ed25519" -x build/temp_private_key.pem 2>/dev/null; then + PRIVATE_KEY_CONTENT=$(cat build/temp_private_key.pem) + echo "ORA_PRIVATE_KEY=$PRIVATE_KEY_CONTENT" > "$PRIVATE_KEY_FILE" + echo "βœ… Private key saved to $PRIVATE_KEY_FILE (DO NOT commit this file!)" + echo "⚠️ IMPORTANT: Add .env to your .gitignore if not already there" + PRIVATE_KEY="build/temp_private_key.pem" + else + echo "❌ Failed to export private key" + exit 1 + fi + else + echo "❌ Failed to generate Ed25519 keys" + exit 1 + fi +fi + +# Ensure build directory exists before creating appcast +mkdir -p build + +# Create appcast.xml with current version +echo "πŸ“ Creating appcast.xml for version $VERSION..." +PUB_DATE=$(date -u +"%a, %d %b %Y %H:%M:%S %z") +cat > appcast.xml << EOF + + + + Ora Browser Changelog + Most recent changes with links to updates. + en + + Version $VERSION + Ora Browser v$VERSION +

Latest release of Ora Browser with the following features:

+
    +
  • Modern web browsing experience
  • +
  • Tabbed interface with sidebar
  • +
  • Built-in ad blocking
  • +
  • Privacy-focused design
  • +
  • Automatic update system
  • +
+

This release includes bug fixes and performance improvements. Enjoy browsing with Ora!

+ ]]>
+ $PUB_DATE + +
+
+
+EOF + +echo "βœ… Sparkle setup complete!" + # Build the release echo "πŸ”¨ Building release..." -chmod +x build-release.sh -./build-release.sh +BUILD_SCRIPT="./build-release.sh" + +# Ensure we're in the project root directory +if [ ! -f "project.yml" ] || [ ! -d "ora" ]; then + echo "❌ Not in project root directory!" + echo "Current directory: $(pwd)" + echo "Expected to find project.yml and ora/ directory" + exit 1 +fi + +if [ -f "$BUILD_SCRIPT" ]; then + chmod +x "$BUILD_SCRIPT" + echo "πŸ“‚ Running build script from: $(pwd)" + "$BUILD_SCRIPT" +else + echo "❌ build-release.sh not found at $BUILD_SCRIPT!" + echo "Current directory: $(pwd)" + ls -la build-release.sh 2>/dev/null || echo "build-release.sh not found in current directory" + exit 1 +fi # Check if DMG was created -if [ ! -f "Ora-Browser.dmg" ]; then - echo "❌ DMG not found. Build may have failed." +DMG_FILE="build/Ora-Browser-${VERSION}.dmg" +if [ ! -f "$DMG_FILE" ]; then + echo "❌ DMG not found at $DMG_FILE. Build may have failed." exit 1 fi +# Recreate the private key file (build script may have cleaned it) +if [ -n "$PRIVATE_KEY_CONTENT" ]; then + echo "πŸ”‘ Recreating private key file after build..." + mkdir -p build + echo "$PRIVATE_KEY_CONTENT" > build/temp_private_key.pem + PRIVATE_KEY="build/temp_private_key.pem" +fi + # Sign the release with Sparkle echo "πŸ” Signing release with Sparkle..." -if [ -f "$PRIVATE_KEY" ]; then - if command -v sign_update &> /dev/null; then - SIGNATURE=$(sign_update -f "Ora-Browser.dmg" -k "$PRIVATE_KEY") - echo "βœ… Release signed: $SIGNATURE" +if [ -f "$PRIVATE_KEY" ] && [ -r "$PRIVATE_KEY" ] && [ -s "$PRIVATE_KEY" ]; then + echo "πŸ“ Signing DMG with private key..." + SIGNATURE_OUTPUT=$(sign_update --ed-key-file "$PRIVATE_KEY" "$DMG_FILE" 2>&1) + echo "Raw signature output: $SIGNATURE_OUTPUT" + + # Check if signing was successful + if echo "$SIGNATURE_OUTPUT" | grep -q "sparkle:edSignature="; then + SIGNATURE=$(echo "$SIGNATURE_OUTPUT" | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/') + echo "βœ… Release signed successfully: $SIGNATURE" + elif echo "$SIGNATURE_OUTPUT" | grep -q "edSignature="; then + SIGNATURE=$(echo "$SIGNATURE_OUTPUT" | sed 's/.*edSignature="\([^"]*\)".*/\1/') + echo "βœ… Release signed successfully: $SIGNATURE" else - echo "⚠️ sign_update not found. Install Sparkle tools: brew install sparkle" - SIGNATURE="SIGNATURE_PLACEHOLDER" + echo "❌ Failed to sign release - invalid output" + echo "Output was: $SIGNATURE_OUTPUT" + echo "Make sure the private key is valid and the DMG exists" + exit 1 fi else - echo "⚠️ Private key not found at $PRIVATE_KEY" - SIGNATURE="SIGNATURE_PLACEHOLDER" + echo "❌ Private key not found, empty, or not readable at $PRIVATE_KEY" + echo "Make sure your .env file contains a valid ORA_PRIVATE_KEY" + exit 1 fi -# Update appcast.xml +# Update appcast.xml with signature and file size echo "πŸ“ Updating appcast.xml..." -sed -i.bak "s/0\.0\.1/$VERSION/g" appcast.xml -sed -i.bak "s/YOUR_DSA_SIGNATURE_HERE/$SIGNATURE/g" appcast.xml -sed -i.bak "s/v0\.0\.1/v$VERSION/g" appcast.xml # Get file size -FILE_SIZE=$(stat -f%z "Ora-Browser.dmg") -sed -i.bak "s/length=\"0\"/length=\"$FILE_SIZE\"/g" appcast.xml +FILE_SIZE=$(stat -f%z "$DMG_FILE") +echo "πŸ“ DMG file size: $FILE_SIZE bytes" -# Update pubDate -PUB_DATE=$(date -u +"%a, %d %b %Y %H:%M:%S %z") -sed -i.bak "s/.*<\/pubDate>/$PUB_DATE<\/pubDate>/g" appcast.xml +# Update the signature (escape special characters in signature) +ESCAPED_SIGNATURE=$(echo "$SIGNATURE" | sed 's/\//\\\//g') +sed -i.bak "s/YOUR_DSA_SIGNATURE_HERE/$ESCAPED_SIGNATURE/g" appcast.xml + +# Update file size +sed -i.bak "s/length=\"33592320\"/length=\"$FILE_SIZE\"/g" appcast.xml + +echo "βœ… Appcast.xml updated with signature and file size" + +# Commit changes before deployment +echo "πŸ“ Committing changes for v$VERSION..." +git add project.yml appcast.xml +git commit -m "Update to v$VERSION" + +# Backup appcast.xml before deployment +cp appcast.xml /tmp/appcast_backup.xml + +# Deploy appcast.xml to GitHub Pages +echo "🌐 Deploying appcast.xml to GitHub Pages..." +deploy_to_github_pages() { + # Check if we're in a git repository + if ! git rev-parse --git-dir > /dev/null 2>&1; then + echo "⚠️ Not in a git repository, skipping GitHub Pages deployment" + return 1 + fi + + local current_branch=$(git branch --show-current) + echo "πŸ“‹ Current branch: $current_branch" + + # Check if gh-pages branch exists remotely + if git ls-remote --heads origin gh-pages | grep -q gh-pages; then + echo "πŸ“‹ gh-pages branch exists remotely, updating..." + git fetch origin gh-pages + else + echo "πŸ“‹ Creating gh-pages branch..." + git checkout -b gh-pages + git rm -rf . + echo "# Ora Browser Updates" > README.md + echo "This branch hosts the appcast.xml for automatic updates." >> README.md + git add README.md + git commit -m "Initialize gh-pages branch" + fi + + # Stash any uncommitted changes before switching + git stash push -m "Stash before deploying appcast v$VERSION" + + # Switch to gh-pages branch + git checkout gh-pages + + # Copy appcast.xml from the release branch + cp /tmp/appcast_backup.xml appcast.xml + rm /tmp/appcast_backup.xml + echo "βœ… Copied appcast.xml to gh-pages branch" + + # Show the version in the appcast + echo "πŸ“‹ Appcast version: $(grep -o 'Version [0-9.]*' appcast.xml | head -1)" + + # Commit and push + git add -f appcast.xml + if git diff --staged --quiet; then + echo "πŸ“‹ No changes to commit for appcast v$VERSION" + else + git commit -m "Deploy appcast v$VERSION" + echo "πŸ“‹ Committed appcast v$VERSION" + fi + + # Push to remote with error handling + echo "πŸ“€ Pushing to remote gh-pages branch..." + if git push origin gh-pages; then + echo "βœ… Successfully pushed appcast v$VERSION to gh-pages branch" + echo "πŸ”— Appcast URL: https://raw.githubusercontent.com/the-ora/browser/refs/heads/gh-pages/appcast.xml" + else + echo "❌ Failed to push to remote gh-pages branch" + echo " Check your git remote and permissions" + return 1 + fi + + # Switch back to original branch + git checkout "$current_branch" + echo "βœ… Switched back to $current_branch branch" + + # Restore stashed changes + git stash pop +} echo "βœ… Release v$VERSION created!" echo "πŸ“ Files ready for upload:" -echo " - Ora-Browser.dmg (signed)" -echo " - appcast.xml (updated)" +echo " - $DMG_FILE (signed)" +echo " - appcast.xml (will be deployed after upload)" +echo " - $PUBLIC_KEY_FILE (public key - committed to git)" +echo " - $PRIVATE_KEY_FILE (private key - DO NOT commit!)" +echo "" +# Upload DMG to GitHub releases +echo "πŸ“€ Uploading DMG to GitHub releases..." +if [ -f "upload-dmg.sh" ]; then + chmod +x upload-dmg.sh + ./upload-dmg.sh "$VERSION" "$DMG_FILE" +else + echo "⚠️ upload-dmg.sh not found, skipping automatic upload" +fi + +# Run deployment after upload +if deploy_to_github_pages; then + echo "πŸŽ‰ Appcast deployed to GitHub Pages!" + echo " URL: https://the-ora.github.io/browser/appcast.xml" +else + echo "⚠️ Appcast deployment failed, but release is still complete" + echo " You can manually deploy appcast.xml to GitHub Pages later" +fi + +# Clean up temporary files +if [ -f "build/temp_private_key.pem" ]; then + rm -f build/temp_private_key.pem + echo "🧹 Cleaned up temporary private key file" +fi + +echo "πŸš€ Next steps:" +echo "1. βœ… DMG uploaded to GitHub releases" +echo "2. Enable GitHub Pages in repository settings (if not already enabled)" +echo " - Go to Settings β†’ Pages" +echo " - Set source to 'Deploy from a branch'" +echo " - Set branch to 'gh-pages'" +echo "3. βœ… Public key is already configured in project.yml" +echo "4. βœ… SUFeedURL is already configured in project.yml" +echo "" + +# Security check - ensure sensitive files are not committed +echo "πŸ”’ Security Check:" +if git ls-files 2>/dev/null | grep -q "\.env$"; then + echo "❌ SECURITY VIOLATION: .env file is tracked by git!" + echo " This contains your private key! Run:" + echo " git rm --cached .env" + echo " git commit -m 'Remove .env from tracking'" + exit 1 +fi + +if git ls-files 2>/dev/null | grep -q "temp_private_key.pem"; then + echo "❌ SECURITY VIOLATION: Temporary private key file is tracked by git!" + echo " Run: git rm --cached build/temp_private_key.pem" + exit 1 +fi + +echo "βœ… Security check passed - sensitive files not committed" echo "" -echo "πŸš€ Next: Upload Ora-Browser.dmg to GitHub releases" -echo "🌐 Host appcast.xml at: https://your-domain.com/appcast.xml" -echo "βš™οΈ Update SUFeedURL in Info.plist to point to your appcast.xml" \ No newline at end of file +echo "πŸ”‘ Key Management:" +echo " - Public key: $PUBLIC_KEY_FILE (committed to git)" +echo " - Private key: $PRIVATE_KEY_FILE (NEVER commit this!)" +echo " - Share $PRIVATE_KEY_FILE when setting up new machines" \ No newline at end of file diff --git a/create-release.sh.bak b/create-release.sh.bak new file mode 100755 index 00000000..f81217a0 --- /dev/null +++ b/create-release.sh.bak @@ -0,0 +1,437 @@ +#!/bin/bash +set -e + +# Create Release Script for Ora Browser +# This script creates a signed release and updates the appcast +# +# Key Management: +# - Public key: Stored in ora_public_key.pem (committed to git) +# - Private key: Stored in .env file (NEVER commit this!) +# - If keys don't exist, new Ed25519 keys are generated +# - If private key missing, build fails with clear instructions +# +# Usage: $0 [version] +# If no version is provided, it will auto-increment the patch version from project.yml +# Example: $0 0.0.36 + +# Handle version argument +if [ $# -lt 1 ]; then + # Auto-increment version + if [ -f "project.yml" ]; then + CURRENT_VERSION=$(grep "MARKETING_VERSION:" project.yml | sed 's/.*MARKETING_VERSION: //' | tr -d ' ') + if [ -n "$CURRENT_VERSION" ]; then + # Increment the last number + VERSION=$(echo "$CURRENT_VERSION" | awk -F. '{print $1"."$2"."($3+1)}') + echo "Auto-incrementing version from $CURRENT_VERSION to $VERSION" + else + echo "Could not find MARKETING_VERSION in project.yml" + exit 1 + fi + else + echo "project.yml not found for auto-increment" + exit 1 + fi +else + VERSION=$1 +fi +# Private key will be determined by the key management section above +# PRIVATE_KEY is set dynamically based on available keys + +# Save original directory +ORIGINAL_DIR="$(pwd)" + +echo "πŸš€ Creating Ora Browser Release v$VERSION..." + +# Update project.yml with the release version and public key +echo "πŸ“ Updating project.yml with version $VERSION..." +if [ -f "project.yml" ]; then + # Update MARKETING_VERSION + sed -i.bak "s/MARKETING_VERSION: .*/MARKETING_VERSION: $VERSION/" project.yml + + # Update CURRENT_PROJECT_VERSION (use the numeric part after last dot, or increment) + BUILD_VERSION=$(echo $VERSION | awk -F. '{print $NF + 0}') + sed -i.bak "s/CURRENT_PROJECT_VERSION: .*/CURRENT_PROJECT_VERSION: $BUILD_VERSION/" project.yml + + # Update SUPublicEDKey with the current public key + sed -i.bak "s/SUPublicEDKey: .*/SUPublicEDKey: \"$PUBLIC_KEY\"/" project.yml + + echo "βœ… Updated project.yml: MARKETING_VERSION=$VERSION, CURRENT_PROJECT_VERSION=$BUILD_VERSION" + echo "βœ… Updated SUPublicEDKey: ${PUBLIC_KEY:0:20}..." +else + echo "⚠️ project.yml not found, skipping version update" +fi + +# Clean build directory for fresh build +echo "🧹 Cleaning build directory..." +rm -rf build/ +mkdir -p build + +# Setup Sparkle (generate DSA keys and install tools) +echo "πŸ” Setting up Sparkle for Ora Browser..." + +# Setup Sparkle tools PATH +echo "πŸ”§ Setting up Sparkle tools..." + +# Check if Sparkle is installed via Homebrew +if command -v brew &> /dev/null && brew list sparkle &> /dev/null; then + echo "βœ… Sparkle found via Homebrew" + SPARKLE_BIN_PATH="/opt/homebrew/Caskroom/sparkle/2.7.1/bin" + export PATH="$SPARKLE_BIN_PATH:$PATH" +elif [ -d "/opt/homebrew/Caskroom/sparkle" ]; then + # Find the latest version + SPARKLE_VERSION=$(ls /opt/homebrew/Caskroom/sparkle/ | sort -V | tail -1) + SPARKLE_BIN_PATH="/opt/homebrew/Caskroom/sparkle/$SPARKLE_VERSION/bin" + export PATH="$SPARKLE_BIN_PATH:$PATH" +else + echo "❌ Sparkle not found. Installing via Homebrew..." + if command -v brew &> /dev/null; then + brew install sparkle + SPARKLE_BIN_PATH="/opt/homebrew/Caskroom/sparkle/2.7.1/bin" + export PATH="$SPARKLE_BIN_PATH:$PATH" + else + echo "❌ Homebrew not found. Please install Homebrew first." + exit 1 + fi +fi + +echo "πŸ”§ Sparkle tools path: $SPARKLE_BIN_PATH" + +# Verify tools are available +if ! command -v generate_keys &> /dev/null; then + echo "❌ generate_keys command not found in PATH" + echo "Current PATH: $PATH" + exit 1 +fi + +if ! command -v sign_update &> /dev/null; then + echo "❌ sign_update command not found in PATH" + echo "Current PATH: $PATH" + exit 1 +fi + +echo "βœ… Sparkle tools ready!" + +# Ensure build directory exists +mkdir -p build + +# Key management: Public key in root (committed), Private key in .env (not committed) +PUBLIC_KEY_FILE="ora_public_key.pem" +PRIVATE_KEY_FILE=".env" +PRIVATE_KEY_CONTENT="" # Global variable to store private key content + +# Check if public key exists in root directory +if [ -f "$PUBLIC_KEY_FILE" ]; then + echo "πŸ”‘ Found existing public key in $PUBLIC_KEY_FILE" + PUBLIC_KEY=$(cat "$PUBLIC_KEY_FILE") + echo "βœ… Using existing public key: ${PUBLIC_KEY:0:20}..." + + # Check if private key exists in .env file + if [ -f "$PRIVATE_KEY_FILE" ]; then + echo "πŸ”‘ Found private key in $PRIVATE_KEY_FILE" + PRIVATE_KEY_CONTENT=$(grep "ORA_PRIVATE_KEY=" "$PRIVATE_KEY_FILE" | cut -d'=' -f2-) + if [ -n "$PRIVATE_KEY_CONTENT" ]; then + echo "βœ… Private key found in .env file" + # Store content globally and create temporary private key file for now + echo "$PRIVATE_KEY_CONTENT" > build/temp_private_key.pem + PRIVATE_KEY="build/temp_private_key.pem" + else + echo "❌ Private key not found in .env file!" + echo " Please add your private key to $PRIVATE_KEY_FILE in this format:" + echo " ORA_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----..." + exit 1 + fi + else + echo "❌ Private key file (.env) not found!" + echo " Please create a .env file with your private key:" + echo " ORA_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----..." + echo " Or run this script from a machine that has the keys." + exit 1 + fi +else + echo "πŸ”‘ No public key found. Generating new Ed25519 keypair..." + + # Generate new Ed25519 keys + if generate_keys --account "ora-browser-ed25519"; then + echo "βœ… New Ed25519 keys generated" + + # Get public key and save to root directory (will be committed) + PUBLIC_KEY=$(generate_keys --account "ora-browser-ed25519" -p) + echo "$PUBLIC_KEY" > "$PUBLIC_KEY_FILE" + echo "βœ… Public key saved to $PUBLIC_KEY_FILE (will be committed to git)" + + # Export private key and save to .env file (will NOT be committed) + if generate_keys --account "ora-browser-ed25519" -x build/temp_private_key.pem 2>/dev/null; then + PRIVATE_KEY_CONTENT=$(cat build/temp_private_key.pem) + echo "ORA_PRIVATE_KEY=$PRIVATE_KEY_CONTENT" > "$PRIVATE_KEY_FILE" + echo "βœ… Private key saved to $PRIVATE_KEY_FILE (DO NOT commit this file!)" + echo "⚠️ IMPORTANT: Add .env to your .gitignore if not already there" + PRIVATE_KEY="build/temp_private_key.pem" + else + echo "❌ Failed to export private key" + exit 1 + fi + else + echo "❌ Failed to generate Ed25519 keys" + exit 1 + fi +fi + +# Ensure build directory exists before creating appcast +mkdir -p build + +# Create appcast.xml with current version +echo "πŸ“ Creating appcast.xml for version $VERSION..." +PUB_DATE=$(date -u +"%a, %d %b %Y %H:%M:%S %z") +cat > appcast.xml << EOF + + + + Ora Browser Changelog + Most recent changes with links to updates. + en + + Version $VERSION + Ora Browser v$VERSION +

Latest release of Ora Browser with the following features:

+
    +
  • Modern web browsing experience
  • +
  • Tabbed interface with sidebar
  • +
  • Built-in ad blocking
  • +
  • Privacy-focused design
  • +
  • Automatic update system
  • +
+

This release includes bug fixes and performance improvements. Enjoy browsing with Ora!

+ ]]>
+ $PUB_DATE + +
+
+
+EOF + +echo "βœ… Sparkle setup complete!" + +# Build the release +echo "πŸ”¨ Building release..." +BUILD_SCRIPT="./build-release.sh" + +# Ensure we're in the project root directory +if [ ! -f "project.yml" ] || [ ! -d "ora" ]; then + echo "❌ Not in project root directory!" + echo "Current directory: $(pwd)" + echo "Expected to find project.yml and ora/ directory" + exit 1 +fi + +if [ -f "$BUILD_SCRIPT" ]; then + chmod +x "$BUILD_SCRIPT" + echo "πŸ“‚ Running build script from: $(pwd)" + "$BUILD_SCRIPT" +else + echo "❌ build-release.sh not found at $BUILD_SCRIPT!" + echo "Current directory: $(pwd)" + ls -la build-release.sh 2>/dev/null || echo "build-release.sh not found in current directory" + exit 1 +fi + +# Check if DMG was created +if [ ! -f "build/Ora-Browser.dmg" ]; then + echo "❌ DMG not found in build/ directory. Build may have failed." + exit 1 +fi + +# Recreate the private key file (build script may have cleaned it) +if [ -n "$PRIVATE_KEY_CONTENT" ]; then + echo "πŸ”‘ Recreating private key file after build..." + mkdir -p build + echo "$PRIVATE_KEY_CONTENT" > build/temp_private_key.pem + PRIVATE_KEY="build/temp_private_key.pem" +fi + +# Sign the release with Sparkle +echo "πŸ” Signing release with Sparkle..." +if [ -f "$PRIVATE_KEY" ] && [ -r "$PRIVATE_KEY" ] && [ -s "$PRIVATE_KEY" ]; then + echo "πŸ“ Signing DMG with private key..." + SIGNATURE_OUTPUT=$(sign_update --ed-key-file "$PRIVATE_KEY" "build/Ora-Browser.dmg" 2>&1) + echo "Raw signature output: $SIGNATURE_OUTPUT" + + # Check if signing was successful + if echo "$SIGNATURE_OUTPUT" | grep -q "sparkle:edSignature="; then + SIGNATURE=$(echo "$SIGNATURE_OUTPUT" | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/') + echo "βœ… Release signed successfully: $SIGNATURE" + elif echo "$SIGNATURE_OUTPUT" | grep -q "edSignature="; then + SIGNATURE=$(echo "$SIGNATURE_OUTPUT" | sed 's/.*edSignature="\([^"]*\)".*/\1/') + echo "βœ… Release signed successfully: $SIGNATURE" + else + echo "❌ Failed to sign release - invalid output" + echo "Output was: $SIGNATURE_OUTPUT" + echo "Make sure the private key is valid and the DMG exists" + exit 1 + fi +else + echo "❌ Private key not found, empty, or not readable at $PRIVATE_KEY" + echo "Make sure your .env file contains a valid ORA_PRIVATE_KEY" + exit 1 +fi + +# Update appcast.xml with signature and file size +echo "πŸ“ Updating appcast.xml..." + +# Get file size +FILE_SIZE=$(stat -f%z "build/Ora-Browser.dmg") +echo "πŸ“ DMG file size: $FILE_SIZE bytes" + +# Update the signature (escape special characters in signature) +ESCAPED_SIGNATURE=$(echo "$SIGNATURE" | sed 's/\//\\\//g') +sed -i.bak "s/YOUR_DSA_SIGNATURE_HERE/$ESCAPED_SIGNATURE/g" appcast.xml + +# Update file size +sed -i.bak "s/length=\"33592320\"/length=\"$FILE_SIZE\"/g" appcast.xml + +echo "βœ… Appcast.xml updated with signature and file size" + +# Commit changes before deployment +echo "πŸ“ Committing changes for v$VERSION..." +git add project.yml appcast.xml +git commit -m "Update to v$VERSION" + +# Backup appcast.xml before deployment +cp appcast.xml /tmp/appcast_backup.xml + +# Deploy appcast.xml to GitHub Pages +echo "🌐 Deploying appcast.xml to GitHub Pages..." +deploy_to_github_pages() { + # Check if we're in a git repository + if ! git rev-parse --git-dir > /dev/null 2>&1; then + echo "⚠️ Not in a git repository, skipping GitHub Pages deployment" + return 1 + fi + + local current_branch=$(git branch --show-current) + echo "πŸ“‹ Current branch: $current_branch" + + # Check if gh-pages branch exists remotely + if git ls-remote --heads origin gh-pages | grep -q gh-pages; then + echo "πŸ“‹ gh-pages branch exists remotely, updating..." + git fetch origin gh-pages + else + echo "πŸ“‹ Creating gh-pages branch..." + git checkout -b gh-pages + git rm -rf . + echo "# Ora Browser Updates" > README.md + echo "This branch hosts the appcast.xml for automatic updates." >> README.md + git add README.md + git commit -m "Initialize gh-pages branch" + fi + + # Stash any uncommitted changes before switching + git stash push -m "Stash before deploying appcast v$VERSION" + + # Switch to gh-pages branch + git checkout gh-pages + + # Copy appcast.xml from the release branch + cp /tmp/appcast_backup.xml appcast.xml + rm /tmp/appcast_backup.xml + echo "βœ… Copied appcast.xml to gh-pages branch" + + # Show the version in the appcast + echo "πŸ“‹ Appcast version: $(grep -o 'Version [0-9.]*' appcast.xml | head -1)" + + # Commit and push + git add -f appcast.xml + if git diff --staged --quiet; then + echo "πŸ“‹ No changes to commit for appcast v$VERSION" + else + git commit -m "Deploy appcast v$VERSION" + echo "πŸ“‹ Committed appcast v$VERSION" + fi + + # Push to remote with error handling + echo "πŸ“€ Pushing to remote gh-pages branch..." + if git push origin gh-pages; then + echo "βœ… Successfully pushed appcast v$VERSION to gh-pages branch" + echo "πŸ”— Appcast URL: https://raw.githubusercontent.com/the-ora/browser/refs/heads/gh-pages/appcast.xml" + else + echo "❌ Failed to push to remote gh-pages branch" + echo " Check your git remote and permissions" + return 1 + fi + + # Switch back to original branch + git checkout "$current_branch" + echo "βœ… Switched back to $current_branch branch" + + # Restore stashed changes + git stash pop +} + +echo "βœ… Release v$VERSION created!" +echo "πŸ“ Files ready for upload:" +echo " - build/Ora-Browser.dmg (signed)" +echo " - appcast.xml (will be deployed after upload)" +echo " - $PUBLIC_KEY_FILE (public key - committed to git)" +echo " - $PRIVATE_KEY_FILE (private key - DO NOT commit!)" +echo "" +# Upload DMG to GitHub releases +echo "πŸ“€ Uploading DMG to GitHub releases..." +if [ -f "upload-dmg.sh" ]; then + chmod +x upload-dmg.sh + ./upload-dmg.sh "$VERSION" "build/Ora-Browser.dmg" +else + echo "⚠️ upload-dmg.sh not found, skipping automatic upload" +fi + +# Run deployment after upload +if deploy_to_github_pages; then + echo "πŸŽ‰ Appcast deployed to GitHub Pages!" + echo " URL: https://the-ora.github.io/browser/appcast.xml" +else + echo "⚠️ Appcast deployment failed, but release is still complete" + echo " You can manually deploy appcast.xml to GitHub Pages later" +fi + +# Clean up temporary files +if [ -f "build/temp_private_key.pem" ]; then + rm -f build/temp_private_key.pem + echo "🧹 Cleaned up temporary private key file" +fi + +echo "πŸš€ Next steps:" +echo "1. βœ… DMG uploaded to GitHub releases" +echo "2. Enable GitHub Pages in repository settings (if not already enabled)" +echo " - Go to Settings β†’ Pages" +echo " - Set source to 'Deploy from a branch'" +echo " - Set branch to 'gh-pages'" +echo "3. βœ… Public key is already configured in project.yml" +echo "4. βœ… SUFeedURL is already configured in project.yml" +echo "" + +# Security check - ensure sensitive files are not committed +echo "πŸ”’ Security Check:" +if git ls-files 2>/dev/null | grep -q "\.env$"; then + echo "❌ SECURITY VIOLATION: .env file is tracked by git!" + echo " This contains your private key! Run:" + echo " git rm --cached .env" + echo " git commit -m 'Remove .env from tracking'" + exit 1 +fi + +if git ls-files 2>/dev/null | grep -q "temp_private_key.pem"; then + echo "❌ SECURITY VIOLATION: Temporary private key file is tracked by git!" + echo " Run: git rm --cached build/temp_private_key.pem" + exit 1 +fi + +echo "βœ… Security check passed - sensitive files not committed" +echo "" +echo "πŸ”‘ Key Management:" +echo " - Public key: $PUBLIC_KEY_FILE (committed to git)" +echo " - Private key: $PRIVATE_KEY_FILE (NEVER commit this!)" +echo " - Share $PRIVATE_KEY_FILE when setting up new machines" \ No newline at end of file diff --git a/docs/HOSTING_SETUP.md b/docs/HOSTING_SETUP.md new file mode 100644 index 00000000..3818cc0c --- /dev/null +++ b/docs/HOSTING_SETUP.md @@ -0,0 +1,286 @@ +# Ora Browser Hosting Setup Guide + +This guide explains how to set up hosting for Ora Browser's automatic update system using Sparkle. + +## Overview + +Ora Browser uses [Sparkle](https://sparkle-project.org/) for automatic updates. The system requires: + +1. **Appcast Feed** (`appcast.xml`) - Tells Sparkle about available updates +2. **App Distribution** (`Ora-Browser.dmg`) - The actual app download +3. **Digital Signatures** - Ensures update integrity and security + +## What Needs to Be Hosted + +### 1. Appcast XML File +**File:** `appcast.xml` +**Purpose:** Update feed that Sparkle reads to check for new versions +**Content:** Release information, download URLs, version numbers, and digital signatures + +### 2. App DMG File +**File:** `Ora-Browser.dmg` +**Purpose:** The actual application installer that users download +**Content:** Signed and notarized macOS application bundle + +## Hosting Options + +### Option A: GitHub Pages (Recommended) + +#### Step 1: Create GitHub Pages Branch +```bash +# Create and switch to gh-pages branch +git checkout -b gh-pages + +# Remove all files except what we need for hosting +git rm -rf . +git reset -- docs/ # Keep docs if you want + +# Copy appcast.xml +cp ../appcast.xml . + +# Commit and push +git add appcast.xml +git commit -m "Add appcast.xml for Sparkle updates" +git push origin gh-pages +``` + +#### Step 2: Enable GitHub Pages +1. Go to your GitHub repository +2. Navigate to **Settings** β†’ **Pages** +3. Set **Source** to "Deploy from a branch" +4. Set **Branch** to `gh-pages` and folder to `/ (root)` +5. Click **Save** + +#### Step 3: Get Your URLs +- **Appcast URL:** `https://the-ora.github.io/browser/appcast.xml` +- **DMG URL:** `https://github.com/the-ora/browser/releases/download/v0.0.1/Ora-Browser.dmg` + +### Option B: Your Own Web Server + +#### Requirements +- Public web server with HTTPS +- Ability to upload files +- CORS headers configured (if needed) + +#### Setup Steps +1. Upload `appcast.xml` to your web server +2. Ensure it's accessible via HTTPS +3. Update the enclosure URL in `appcast.xml` to point to your DMG location + +#### Example URLs +- **Appcast URL:** `https://updates.yourdomain.com/appcast.xml` +- **DMG URL:** `https://downloads.yourdomain.com/Ora-Browser.dmg` + +### Option C: GitHub Releases Only + +#### Setup Steps +1. Upload both `appcast.xml` and `Ora-Browser.dmg` to GitHub Releases +2. Use raw GitHub URLs for both files + +#### URLs +- **Appcast URL:** `https://raw.githubusercontent.com/the-ora/browser/main/appcast.xml` +- **DMG URL:** `https://github.com/the-ora/browser/releases/download/v0.0.1/Ora-Browser.dmg` + +## Digital Signature Setup + +### Generate DSA Keys +```bash +# Install Sparkle +brew install --cask sparkle + +# Setup command-line tools +./setup-sparkle-tools.sh + +# Generate keys (run once) +./setup-sparkle.sh + +# This creates in build/: +# - build/dsa_priv.pem (private key - keep secure!) +# - build/dsa_pub.pem (public key - safe to share) +``` + +### Sign Your Release +```bash +# Sign the DMG with your private key +sign_update -f Ora-Browser.dmg -k dsa_priv.pem + +# Copy the signature output +``` + +### Update Appcast with Real Signature +Replace the placeholder in `appcast.xml`: +```xml + +sparkle:dsaSignature="PLACEHOLDER_SIGNATURE_REPLACE_WITH_ACTUAL_SIGNATURE" + + +``` + +## App Configuration + +### Update Info.plist +Add these keys to your `Info.plist` or `project.yml`: + +```xml +SUFeedURL +https://the-ora.github.io/browser/appcast.xml + +SUPublicEDKey +YOUR_PUBLIC_KEY_HERE +``` + +### XcodeGen Configuration +If using XcodeGen, update `project.yml`: +```yaml +settings: + base: + SUFeedURL: https://the-ora.github.io/browser/appcast.xml + SUPublicEDKey: YOUR_PUBLIC_KEY_HERE +``` + +## Release Process + +### Step 1: Build Release +```bash +# Build and package the app +./build-release.sh + +# Or use the comprehensive release script (auto-increments version) +./create-release.sh +``` + +### Step 2: Sign Release +```bash +# Sign with Sparkle +sign_update -f Ora-Browser.dmg -k dsa_priv.pem +``` + +### Step 3: Update Appcast +```bash +# Update version numbers, dates, and signature in appcast.xml +# Update enclosure URL to point to your hosted DMG +``` + +### Step 4: Host Files +1. Upload `appcast.xml` to your chosen hosting location +2. Upload `Ora-Browser.dmg` to GitHub Releases +3. Update your app's `SUFeedURL` if needed + +### Step 5: Test Updates +1. Build and run your app +2. Go to Settings β†’ General β†’ Updates +3. Click "Check for Updates" +4. Verify the update notification appears + +## File Structure + +``` +your-project/ +β”œβ”€β”€ build/ # Build artifacts directory +β”‚ β”œβ”€β”€ appcast.xml # Update feed (public) +β”‚ β”œβ”€β”€ Ora-Browser.dmg # App installer +β”‚ β”œβ”€β”€ dsa_priv.pem # Private key (keep secure!) +β”‚ └── dsa_pub.pem # Public key +└── docs/ # Documentation + β”œβ”€β”€ HOSTING_SETUP.md # This guide + └── QUICK_START.md # Quick setup guide +``` + +## Security Considerations + +### Private Key Security +- **Never commit `dsa_priv.pem`** to version control +- Store securely (password manager, secure server) +- Use different keys for different environments if needed + +### HTTPS Requirement +- Always host `appcast.xml` over HTTPS +- GitHub Pages automatically provides HTTPS +- Custom servers must have valid SSL certificates + +### Signature Verification +- Sparkle automatically verifies signatures +- Users cannot install updates without valid signatures +- Invalid signatures will be rejected by macOS Gatekeeper + +## Troubleshooting + +### Update Not Detected +1. Check `SUFeedURL` in Info.plist is correct +2. Verify `appcast.xml` is accessible via browser +3. Check signature is valid (not placeholder) +4. Verify version numbers are incrementing + +### Download Fails +1. Check DMG URL in `appcast.xml` is correct +2. Verify DMG is publicly accessible +3. Check file permissions on hosting server +4. Ensure DMG is properly signed and notarized + +### Signature Invalid +1. Verify you're using the correct private key +2. Check the signature was copied correctly +3. Ensure no extra whitespace in signature +4. Test with a fresh signature generation + +## Maintenance + +### Regular Updates +1. Build new release with incremented version +2. Sign with private key +3. Update `appcast.xml` with new version info +4. Upload new DMG to releases +5. Update appcast on hosting server + +### Version Numbering +- Use semantic versioning (e.g., 1.0.0, 1.0.1, 1.1.0) +- Update both `CFBundleShortVersionString` and `CFBundleVersion` +- Ensure version numbers increment for each release + +## Support + +For issues with: +- **Sparkle framework:** https://sparkle-project.org/documentation/ +- **GitHub Pages:** https://docs.github.com/en/pages +- **App signing:** https://developer.apple.com/support/code-signing/ + +## Example Appcast + +```xml + + + + Ora Browser Changelog + Most recent changes with links to updates. + en + + Ora Browser 0.0.1 + + Initial Release +

Ora Browser is a fast, secure, and beautiful browser built for macOS.

+
    +
  • Native macOS UI built with SwiftUI
  • +
  • Fast browsing powered by WebKit
  • +
  • Privacy-first with built-in content blocker
  • +
+ ]]> +
+ Thu, 04 Sep 2025 14:51:08 +0000 + +
+
+
+``` + +--- + +**Last Updated:** September 4, 2025 +**Ora Browser Version:** 0.0.1 + +/Users/keni/code/ora/browser/docs/HOSTING_SETUP.md \ No newline at end of file diff --git a/docs/QUICK_START.md b/docs/QUICK_START.md new file mode 100644 index 00000000..7055b6f9 --- /dev/null +++ b/docs/QUICK_START.md @@ -0,0 +1,92 @@ +# Ora Browser Quick Start - Hosting Setup + +## πŸš€ Quick Setup (5 minutes) + +**Note:** The release script now auto-increments version numbers. Just run `./create-release.sh` without arguments for patch releases. + +### 1. Setup Sparkle Tools +```bash +brew install --cask sparkle +./setup-sparkle-tools.sh +./setup-sparkle.sh +``` + +### 2. Build & Sign Release +```bash +# Auto-increment version (recommended) +./create-release.sh + +# Or specify version manually +./create-release.sh 0.0.1 +``` + +### 3. Host Files + +#### Option A: GitHub Pages (Easiest) +```bash +# Create gh-pages branch +git checkout -b gh-pages +git rm -rf . +cp ../appcast.xml . +git add appcast.xml +git commit -m "Add appcast for updates" +git push origin gh-pages + +# Enable in GitHub: Settings β†’ Pages β†’ Source: gh-pages +``` + +#### Option B: GitHub Releases +- Upload `Ora-Browser.dmg` to GitHub Releases +- Upload `appcast.xml` to any web host + +### 4. Update App Config +Edit `project.yml`: +```yaml +settings: + base: + SUFeedURL: https://the-ora.github.io/browser/appcast.xml + SUPublicEDKey: YOUR_PUBLIC_KEY_HERE +``` + +### 5. Test +```bash +xcodegen +# Build and run app +# Go to Settings β†’ General β†’ Check for Updates +``` + +## πŸ“‹ What Gets Hosted Where + +| File | Location | Purpose | +|------|----------|---------| +| `build/appcast.xml` | Public web server | Update feed for Sparkle | +| `build/Ora-Browser.dmg` | GitHub Releases | App installer download | +| `build/dsa_pub.pem` | App bundle | Public key for verification | + +## πŸ”— URLs You'll Need + +- **Appcast:** `https://the-ora.github.io/browser/appcast.xml` +- **DMG:** `https://github.com/the-ora/browser/releases/download/v{VERSION}/Ora-Browser.dmg` +- **Public Key:** Copy from `dsa_pub.pem` + +## βœ… Checklist + +- [ ] DSA keys generated (`dsa_priv.pem`, `dsa_pub.pem`) +- [ ] Release built and signed (`Ora-Browser.dmg`) +- [ ] Appcast updated with real signature +- [ ] Appcast hosted at public URL +- [ ] DMG uploaded to GitHub Releases +- [ ] `SUFeedURL` updated in app +- [ ] `SUPublicEDKey` added to app +- [ ] Xcode project regenerated + +## πŸ†˜ Need Help? + +- **Keys not working:** Run `./setup-sparkle.sh` again +- **Signature invalid:** Use `sign_update` command output exactly +- **Update not found:** Check `SUFeedURL` in Info.plist +- **DMG won't download:** Verify GitHub release is public + +See `docs/HOSTING_SETUP.md` for detailed instructions. + +/Users/keni/code/ora/browser/docs/QUICK_START.md \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..15448643 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,63 @@ +# Ora Browser Documentation + +This directory contains documentation for Ora Browser development, deployment, and maintenance. + +## πŸ“š Documentation Index + +### πŸš€ Getting Started +- **[QUICK_START.md](QUICK_START.md)** - 5-minute setup guide for hosting and updates +- **[../README.md](../README.md)** - Main project README with installation instructions + +### πŸ“¦ Hosting & Deployment +- **[HOSTING_SETUP.md](HOSTING_SETUP.md)** - Complete guide for setting up update hosting +- **[../.github/workflows/release.yml](../.github/workflows/release.yml)** - GitHub Actions for automated releases + +### πŸ”§ Development +- **[../ora/Services/UpdateService.swift](../ora/Services/UpdateService.swift)** - Update service implementation +- **[../ora/Modules/Settings/Sections/GeneralSettingsView.swift](../ora/Modules/Settings/Sections/GeneralSettingsView.swift)** - Settings UI with update controls +- **[../project.yml](../project.yml)** - XcodeGen project configuration + +### πŸ—οΈ Build & Release +- **[../build-release.sh](../build-release.sh)** - Release build script +- **[../create-release.sh](../create-release.sh)** - Complete release creation script (auto-increments versions) +- **[../setup-sparkle.sh](../setup-sparkle.sh)** - Sparkle key generation setup + +## 🎯 Key Files for Updates + +### Configuration Files +- `appcast.xml` - Update feed (host publicly) +- `dsa_priv.pem` - Private key (keep secure!) +- `dsa_pub.pem` - Public key (add to app) + +### App Files +- `Ora-Browser.dmg` - Release installer (upload to GitHub Releases) +- `ora/Services/UpdateService.swift` - Update checking logic +- `ora/Modules/Settings/Sections/GeneralSettingsView.swift` - Update UI + +## πŸ”„ Update Flow + +1. **Build:** `./create-release.sh` (auto-increments version) or `./create-release.sh 1.0.0` (manual version) +2. **Sign:** Use `dsa_priv.pem` to sign DMG +3. **Host:** Upload `appcast.xml` to public web server +4. **Release:** Upload DMG to GitHub Releases +5. **Configure:** Update `SUFeedURL` in app +6. **Test:** Users get automatic update notifications + +## πŸ“ž Support + +- **Sparkle Documentation:** https://sparkle-project.org/documentation/ +- **GitHub Pages:** https://docs.github.com/en/pages +- **Code Signing:** https://developer.apple.com/support/code-signing/ + +## πŸ“ Contributing + +When updating documentation: +1. Keep `QUICK_START.md` for 5-minute setup +2. Use `HOSTING_SETUP.md` for detailed procedures +3. Update this README when adding new docs + +--- + +**Ora Browser** | *Fast, secure, and beautiful browser for macOS* + +/Users/keni/code/ora/browser/docs/README.md \ No newline at end of file diff --git a/exportOptions.plist b/exportOptions.plist deleted file mode 100644 index 2ed41615..00000000 --- a/exportOptions.plist +++ /dev/null @@ -1,16 +0,0 @@ - - - - - method - developer-id - teamID - YOUR_TEAM_ID - destination - export - signingStyle - automatic - stripSwiftSymbols - - - \ No newline at end of file diff --git a/ora/Common/Constants/Theme.swift b/ora/Common/Constants/Theme.swift index 9a4a1260..30b9f57a 100644 --- a/ora/Common/Constants/Theme.swift +++ b/ora/Common/Constants/Theme.swift @@ -1,16 +1,65 @@ import AppKit import SwiftUI -// swiftlint:disable identifier_name +extension Notification.Name { + static let colorThemeChanged = Notification.Name("colorThemeChanged") +} + +struct ThemeConstants { + static let colorTransitionDuration: Double = 0.3 + static let colorThemeUserDefaultsKey = "ColorTheme" +} + +enum ColorTheme: String, CaseIterable, Identifiable { + case orange = "Orange" + case blue = "Blue" + case green = "Green" + case purple = "Purple" + case red = "Red" + case teal = "Teal" + case pink = "Pink" + case gray = "Gray" + + var id: String { rawValue } + + var primaryLight: Color { + switch self { + case .orange: return Color(hex: "f3e5d6") + case .blue: return Color(hex: "d6e5f3") + case .green: return Color(hex: "d6f3e5") + case .purple: return Color(hex: "e5d6f3") + case .red: return Color(hex: "f3d6d6") + case .teal: return Color(hex: "d6f3f0") + case .pink: return Color(hex: "f3d6e8") + case .gray: return Color(hex: "e5e5e5") + } + } + + var primaryDark: Color { + switch self { + case .orange: return Color(hex: "63411D") + case .blue: return Color(hex: "1D4163") + case .green: return Color(hex: "1D6341") + case .purple: return Color(hex: "411D63") + case .red: return Color(hex: "631D1D") + case .teal: return Color(hex: "1D6360") + case .pink: return Color(hex: "631D48") + case .gray: return Color(hex: "414141") + } + } +} + +// swiftlint:disable:next identifier_name struct Theme: Equatable { let colorScheme: ColorScheme + let colorTheme: ColorTheme var primary: Color { - Color(hex: "f3e5d6") + colorTheme.primaryLight } var primaryDark: Color { - Color(hex: "63411D") + colorTheme.primaryDark } var accent: Color { @@ -128,7 +177,7 @@ struct Theme: Equatable { } private struct ThemeKey: EnvironmentKey { - static let defaultValue = Theme(colorScheme: .light) // fallback + static let defaultValue = Theme(colorScheme: .light, colorTheme: .orange) // fallback } extension EnvironmentValues { @@ -140,8 +189,21 @@ extension EnvironmentValues { struct ThemeProvider: ViewModifier { @Environment(\.colorScheme) private var colorScheme + @State private var colorTheme: ColorTheme = { + // Load initial value synchronously + let saved = UserDefaults.standard.string(forKey: ThemeConstants.colorThemeUserDefaultsKey) + return ColorTheme(rawValue: saved ?? "") ?? .orange + }() func body(content: Content) -> some View { - content.environment(\.theme, Theme(colorScheme: colorScheme)) + content + .environment(\.theme, Theme(colorScheme: colorScheme, colorTheme: colorTheme)) + .onReceive(NotificationCenter.default.publisher(for: .colorThemeChanged)) { notification in + if let newTheme = notification.object as? ColorTheme { + withAnimation(.easeInOut(duration: ThemeConstants.colorTransitionDuration)) { + colorTheme = newTheme + } + } + } } } diff --git a/ora/Common/Utils/Utils.swift b/ora/Common/Utils/Utils.swift index 49be48ad..63ca8df4 100644 --- a/ora/Common/Utils/Utils.swift +++ b/ora/Common/Utils/Utils.swift @@ -1,18 +1,38 @@ -import SwiftUI +import Foundation -func isDomainOrIP(_ text: String) -> Bool { - let cleanText = text.replacingOccurrences(of: "https://", with: "") - .replacingOccurrences(of: "http://", with: "") - .replacingOccurrences(of: "www.", with: "") +func extractDomainOrIP(from text: String) -> String? { + guard let url = URL(string: text.hasPrefix("http") ? text : "https://\(text)") else { + return nil + } + + guard let host = url.host else { + return nil + } + + return host +} + +func isValidURL(_ text: String) -> Bool { + guard let host = extractDomainOrIP(from: text) else { return false } let ipPattern = #"^(\d{1,3}\.){3}\d{1,3}$"# - if cleanText.range(of: ipPattern, options: .regularExpression) != nil { + if host.range(of: ipPattern, options: .regularExpression) != nil { return true } let domainPattern = - #"^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$"# + #"^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)+$"# + + return host.range(of: domainPattern, options: .regularExpression) != nil +} - return cleanText.range(of: domainPattern, options: .regularExpression) != nil - && cleanText.contains(".") +func constructURL(from text: String) -> URL? { + let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed.hasPrefix("http://") || trimmed.hasPrefix("https://") { + return URL(string: trimmed) + } + if isValidURL(trimmed) { + return URL(string: "https://\(trimmed)") + } + return nil } diff --git a/ora/Modules/Browser/BrowserView.swift b/ora/Modules/Browser/BrowserView.swift index 9984ef98..3c506db5 100644 --- a/ora/Modules/Browser/BrowserView.swift +++ b/ora/Modules/Browser/BrowserView.swift @@ -12,6 +12,11 @@ struct BrowserView: View { @StateObject var hide = SideHolder() + private func getAppVersion() -> String { + let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "?" + return "Ora \(version)" + } + var body: some View { ZStack(alignment: .leading) { HSplit( @@ -172,24 +177,36 @@ struct BrowserView: View { HStack { Text(hovered) .font(.system(size: 12, weight: .regular)) - .foregroundStyle(Color.white) + .foregroundStyle(theme.foreground) .padding(.horizontal, 10) .padding(.vertical, 6) .background( RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(Color.black.opacity(0.3)) + .fill(theme.background) ) .background(BlurEffectView( material: .popover, blendingMode: .withinWindow )) - .cornerRadius(8) + .cornerRadius(99) .overlay( - RoundedRectangle(cornerRadius: 8, style: .continuous) + RoundedRectangle(cornerRadius: 99, style: .continuous) .stroke(Color(.separatorColor), lineWidth: 1) ) .padding(.leading, 12) Spacer() + + // Version indicator (bottom-right) + Text(getAppVersion()) + .font(.system(size: 10, weight: .regular)) + .foregroundStyle(Color.white.opacity(0.6)) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(Color.black.opacity(0.2)) + ) + .padding(.trailing, 12) } .padding(.bottom, 12) } diff --git a/ora/Modules/Importer/ImportDataButton.swift b/ora/Modules/Importer/ImportDataButton.swift index 1aaded93..7abad9e0 100644 --- a/ora/Modules/Importer/ImportDataButton.swift +++ b/ora/Modules/Importer/ImportDataButton.swift @@ -88,7 +88,7 @@ struct ImportDataButton: View { } var body: some View { - Group { + Menu("Import Data") { Button("Arc") { importArc() } diff --git a/ora/Modules/Launcher/Main/LauncherMain.swift b/ora/Modules/Launcher/Main/LauncherMain.swift index fc91570c..0c5971cf 100644 --- a/ora/Modules/Launcher/Main/LauncherMain.swift +++ b/ora/Modules/Launcher/Main/LauncherMain.swift @@ -148,8 +148,8 @@ struct LauncherMain: View { guard let candidateURL = URL(string: text) else { return } let finalURL: URL? = if candidateURL.scheme != nil { candidateURL - } else if isValidHostname(text) { - URL(string: "https://\(text)") + } else if isValidURL(text) { + constructURL(from: text) } else { nil } @@ -388,7 +388,7 @@ struct LauncherMain: View { if match != nil { return "magnifyingglass" } - return isDomainOrIP(text) ? "globe" : "magnifyingglass" + return isValidURL(text) ? "globe" : "magnifyingglass" } } diff --git a/ora/Modules/Settings/Sections/GeneralSettingsView.swift b/ora/Modules/Settings/Sections/GeneralSettingsView.swift index b4dd872d..505d3e91 100644 --- a/ora/Modules/Settings/Sections/GeneralSettingsView.swift +++ b/ora/Modules/Settings/Sections/GeneralSettingsView.swift @@ -11,6 +11,25 @@ struct GeneralSettingsView: View { SettingsContainer(maxContentWidth: 760) { Form { VStack(alignment: .leading, spacing: 16) { + // App Version Info + VStack(alignment: .leading, spacing: 8) { + HStack { + Text("Ora Browser") + .font(.headline) + Spacer() + Text(getAppVersion()) + .font(.subheadline) + .foregroundColor(.secondary) + } + + Text("Fast, secure, and beautiful browser built for macOS") + .font(.caption) + .foregroundColor(.secondary) + } + .padding(12) + .background(theme.solidWindowBackgroundColor) + .cornerRadius(8) + HStack { Text("Born for your Mac. Make Ora your default browser.") Spacer() @@ -22,6 +41,52 @@ struct GeneralSettingsView: View { .cornerRadius(8) AppearanceSelector(selection: $appearanceManager.appearance) + + // Color Scheme Selector + VStack(alignment: .leading, spacing: 8) { + Text("Color Scheme").foregroundStyle(.secondary) + + LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 12), count: 4), spacing: 12) { + ForEach(ColorTheme.allCases) { colorTheme in + let isSelected = appearanceManager.colorTheme == colorTheme + + Button { + withAnimation(.easeInOut(duration: ThemeConstants.colorTransitionDuration)) { + appearanceManager.colorTheme = colorTheme + } + } label: { + VStack(spacing: 6) { + ZStack { + Circle() + .fill( + LinearGradient( + colors: [ + colorTheme.primaryLight, + colorTheme.primaryDark + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .frame(width: 32, height: 32) + + if isSelected { + Circle() + .stroke(theme.foreground, lineWidth: 2) + .frame(width: 38, height: 38) + } + } + + Text(colorTheme.rawValue) + .font(.caption) + .fontWeight(isSelected ? .semibold : .regular) + .foregroundColor(isSelected ? theme.foreground : .secondary) + } + } + .buttonStyle(.plain) + } + } + } VStack(alignment: .leading, spacing: 12) { Text("Updates") @@ -29,22 +94,43 @@ struct GeneralSettingsView: View { Toggle("Automatically check for updates", isOn: $settings.autoUpdateEnabled) - HStack { - Button("Check for Updates") { - updateService.checkForUpdates() - } - .disabled(!updateService.canCheckForUpdates || updateService.isCheckingForUpdates) + VStack(alignment: .leading, spacing: 8) { + HStack { + Button("Check for Updates") { + updateService.checkForUpdates() + } + + if updateService.isCheckingForUpdates { + ProgressView() + .scaleEffect(0.5) + .frame(width: 16, height: 16) + } - if updateService.isCheckingForUpdates { - ProgressView() - .scaleEffect(0.5) - .frame(width: 16, height: 16) + if updateService.updateAvailable { + Text("Update available!") + .foregroundColor(.green) + .font(.caption) + } } - if updateService.updateAvailable { - Text("Update available!") - .foregroundColor(.green) + if let result = updateService.lastCheckResult { + Text(result) .font(.caption) + .foregroundColor(updateService.updateAvailable ? .green : .secondary) + } + + // Show current app version + if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { + Text("Current version: \(appVersion)") + .font(.caption2) + .foregroundColor(.secondary) + } + + // Show last check time + if let lastCheck = updateService.lastCheckDate { + Text("Last checked: \(lastCheck.formatted(date: .abbreviated, time: .shortened))") + .font(.caption2) + .foregroundColor(.secondary) } } } @@ -62,4 +148,10 @@ struct GeneralSettingsView: View { else { return } NSWorkspace.shared.open(url) } + + private func getAppVersion() -> String { + let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" + let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown" + return "v\(version) (\(build))" + } } diff --git a/ora/Services/AppearanceManager.swift b/ora/Services/AppearanceManager.swift index 280ddbc1..8880ad79 100644 --- a/ora/Services/AppearanceManager.swift +++ b/ora/Services/AppearanceManager.swift @@ -16,9 +16,20 @@ class AppearanceManager: ObservableObject { } } + @Published var colorTheme: ColorTheme { + didSet { + UserDefaults.standard.set(colorTheme.rawValue, forKey: ThemeConstants.colorThemeUserDefaultsKey) + NotificationCenter.default.post(name: .colorThemeChanged, object: colorTheme) + } + } + init() { let saved = UserDefaults.standard.string(forKey: "AppAppearance") self.appearance = AppAppearance(rawValue: saved ?? "") ?? .system + + let savedColorTheme = UserDefaults.standard.string(forKey: ThemeConstants.colorThemeUserDefaultsKey) + self.colorTheme = ColorTheme(rawValue: savedColorTheme ?? "") ?? .orange + updateAppearance() } diff --git a/ora/Services/UpdateService.swift b/ora/Services/UpdateService.swift index ef1ddf9a..f7a0d218 100644 --- a/ora/Services/UpdateService.swift +++ b/ora/Services/UpdateService.swift @@ -1,11 +1,13 @@ -import SwiftUI import Sparkle +import SwiftUI class UpdateService: NSObject, ObservableObject { @Published var canCheckForUpdates = false @Published var updateProgress: Double = 0.0 @Published var isCheckingForUpdates = false @Published var updateAvailable = false + @Published var lastCheckResult: String? + @Published var lastCheckDate: Date? private var updater: SPUUpdater? private var userDriver: SPUStandardUserDriver? @@ -16,51 +18,138 @@ class UpdateService: NSObject, ObservableObject { } private func setupUpdater() { + print("πŸ”§ UpdateService: Setting up updater") + + // Log app information + let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown" + let bundleId = Bundle.main.bundleIdentifier ?? "unknown" + print("πŸ“± UpdateService: App Info - Version: \(currentVersion), Bundle ID: \(bundleId)") + let hostBundle = Bundle.main let applicationBundle = hostBundle let userDriver = SPUStandardUserDriver(hostBundle: hostBundle, delegate: nil) - let updater = SPUUpdater(hostBundle: hostBundle, applicationBundle: applicationBundle, userDriver: userDriver, delegate: self) + let updater = SPUUpdater( + hostBundle: hostBundle, + applicationBundle: applicationBundle, + userDriver: userDriver, + delegate: self + ) self.updater = updater self.userDriver = userDriver - self.canCheckForUpdates = updater.canCheckForUpdates + + // Log Sparkle configuration + print("πŸ”‘ UpdateService: Sparkle Config - Feed URL: \(updater.feedURL?.absoluteString ?? "none")") + + // Start the updater + do { + try updater.start() + print("βœ… UpdateService: Updater started successfully") + print("πŸ”„ UpdateService: Automatic checks enabled: \(updater.automaticallyChecksForUpdates)") + print("⏰ UpdateService: Update check interval: \(updater.updateCheckInterval) seconds") + } catch { + print("❌ UpdateService: Failed to start updater - Error: \(error.localizedDescription)") + print("❌ UpdateService: Error details: \(error)") + } + + self.canCheckForUpdates = true // Force enable for development + print("βœ… UpdateService: Updater setup complete - canCheckForUpdates: \(self.canCheckForUpdates)") } func checkForUpdates() { - guard let updater = updater, canCheckForUpdates else { return } + print("πŸ”„ UpdateService: checkForUpdates called") + guard let updater, canCheckForUpdates else { + print( + "❌ UpdateService: Update checking not available - updater: \(updater != nil), canCheck: \(canCheckForUpdates)" + ) + lastCheckResult = "Update checking is not available" + lastCheckDate = Date() + isCheckingForUpdates = false + return + } + print("βœ… UpdateService: Starting update check") isCheckingForUpdates = true + lastCheckResult = "Checking for updates..." + lastCheckDate = Date() + + DispatchQueue.main.asyncAfter(deadline: .now() + 30) { [weak self] in + if self?.isCheckingForUpdates == true { + print("⏰ UpdateService: Update check timed out after 30 seconds") + self?.isCheckingForUpdates = false + self?.lastCheckResult = "Update check timed out" + self?.lastCheckDate = Date() + } + } + + print("πŸ“‘ UpdateService: Calling updater.checkForUpdates()") + print("🌐 UpdateService: Network check - Feed URL: \(updater.feedURL?.absoluteString ?? "none")") + updater.checkForUpdates() } func checkForUpdatesInBackground() { - guard let updater = updater else { return } + guard let updater else { return } updater.checkForUpdatesInBackground() } } extension UpdateService: SPUUpdaterDelegate { + func feedURLString(for updater: SPUUpdater) -> String? { + let feedURL = "https://the-ora.github.io/browser/appcast.xml" + print("πŸ”— UpdateService: Providing feed URL: \(feedURL)") + print("πŸ”— UpdateService: Feed URL requested by Sparkle updater") + return feedURL + } + func updater(_ updater: SPUUpdater, didFindValidUpdate item: SUAppcastItem) { + print("βœ… UpdateService: Found valid update!") + print("πŸ“¦ UpdateService: Update details:") + print(" - Version: \(item.displayVersionString ?? item.versionString)") + print(" - File URL: \(item.fileURL?.absoluteString ?? "none")") + print(" - Info URL: \(item.infoURL?.absoluteString ?? "none")") + print(" - Release notes: \(item.itemDescription ?? "none")") + print(" - Minimum OS: \(item.minimumSystemVersion ?? "none")") + print(" - File size: \(item.contentLength) bytes") + DispatchQueue.main.async { self.updateAvailable = true self.isCheckingForUpdates = false + self.lastCheckResult = "Update available: \(item.displayVersionString ?? item.versionString)" + self.lastCheckDate = Date() } } func updaterDidNotFindUpdate(_ updater: SPUUpdater, error: Error) { + print("ℹ️ UpdateService: No update found") + print("❌ UpdateService: Error details: \(error.localizedDescription)") + print("πŸ” UpdateService: Error code: \((error as NSError).code)") + print("πŸ” UpdateService: Error domain: \((error as NSError).domain)") + + let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown" + print("πŸ“± UpdateService: Current app version: \(currentVersion)") + + // Log more details about the error + let nsError = error as NSError + print("πŸ” UpdateService: Sparkle error userInfo: \(nsError.userInfo)") + DispatchQueue.main.async { self.updateAvailable = false self.isCheckingForUpdates = false + self.lastCheckResult = "No updates available (current: \(currentVersion))" + self.lastCheckDate = Date() } } func updater(_ updater: SPUUpdater, willDownloadUpdate item: SUAppcastItem, with request: NSMutableURLRequest) { + print("⬇️ UpdateService: Starting download - URL: \(request.url?.absoluteString ?? "unknown")") DispatchQueue.main.async { self.updateProgress = 0.0 } } func updater(_ updater: SPUUpdater, didDownloadUpdate item: SUAppcastItem) { + print("βœ… UpdateService: Update downloaded successfully") DispatchQueue.main.async { self.updateProgress = 1.0 } @@ -71,12 +160,53 @@ extension UpdateService: SPUUpdaterDelegate { } func updater(_ updater: SPUUpdater, didFinishLoading appcast: SUAppcast) { - // Appcast loaded successfully + print("πŸ“„ UpdateService: Appcast loaded successfully") + print("πŸ“Š UpdateService: Appcast details:") + print(" - Total items: \(appcast.items.count)") + + // Log details of each item + for (index, item) in appcast.items.enumerated() { + print("πŸ“¦ UpdateService: Item \(index + 1):") + print(" - Version: \(item.displayVersionString ?? item.versionString)") + print(" - File URL: \(item.fileURL?.absoluteString ?? "none")") + print(" - Info URL: \(item.infoURL?.absoluteString ?? "none")") + print(" - File size: \(item.contentLength) bytes") + print(" - Minimum OS: \(item.minimumSystemVersion ?? "none")") + print(" - Release date: \(item.dateString ?? "none")") + } + } + + func updater(_ updater: SPUUpdater, failedToLoadAppcastWithError error: Error) { + print("❌ UpdateService: Failed to load appcast") + print("❌ UpdateService: Error: \(error.localizedDescription)") + + let nsError = error as NSError + print("πŸ” UpdateService: Error code: \(nsError.code)") + print("πŸ” UpdateService: Error domain: \(nsError.domain)") + print("πŸ” UpdateService: Error userInfo: \(nsError.userInfo)") + + DispatchQueue.main.async { + self.isCheckingForUpdates = false + self.lastCheckResult = "Failed to load appcast: \(error.localizedDescription)" + self.lastCheckDate = Date() + } } func updater(_ updater: SPUUpdater, failedToDownloadUpdate item: SUAppcastItem, error: Error) { + print("❌ UpdateService: Failed to download update") + print("❌ UpdateService: Error: \(error.localizedDescription)") + print("❌ UpdateService: Item version: \(item.displayVersionString ?? item.versionString)") + print("❌ UpdateService: Download URL: \(item.fileURL?.absoluteString ?? "none")") + + let nsError = error as NSError + print("πŸ” UpdateService: Error code: \(nsError.code)") + print("πŸ” UpdateService: Error domain: \(nsError.domain)") + print("πŸ” UpdateService: Error userInfo: \(nsError.userInfo)") + DispatchQueue.main.async { self.isCheckingForUpdates = false + self.lastCheckResult = "Download failed: \(error.localizedDescription)" + self.lastCheckDate = Date() } } -} \ No newline at end of file +} diff --git a/ora/UI/URLBar.swift b/ora/UI/URLBar.swift index 83102c27..91c40a92 100644 --- a/ora/UI/URLBar.swift +++ b/ora/UI/URLBar.swift @@ -118,6 +118,10 @@ struct URLBar: View { .onTapGesture { editingURLString = tab.url.absoluteString } + .onKeyPress(.escape) { + isEditing = false + return .handled + } .overlay( Group { if !isEditing, editingURLString.isEmpty { @@ -142,6 +146,14 @@ struct URLBar: View { .stroke(isEditing ? getUrlFieldColor(tab).opacity(0.5) : Color.clear, lineWidth: 1) ) ) + .overlay( + // Hidden button for keyboard shortcut + Button("") { + isEditing = true + } + .keyboardShortcut(KeyboardShortcuts.Address.focus) + .opacity(0) + ) Spacer() diff --git a/ora/oraApp.swift b/ora/oraApp.swift index 4095f9b3..71e3f961 100644 --- a/ora/oraApp.swift +++ b/ora/oraApp.swift @@ -86,6 +86,9 @@ struct OraApp: App { var body: some Scene { WindowGroup { BrowserView() + .modelContext(tabContext) + .modelContext(historyContext) + .modelContext(downloadContext) .environmentObject(appState) .environmentObject(tabManager) .environmentObject(historyManager) @@ -93,8 +96,7 @@ struct OraApp: App { .environmentObject(appearanceManager) .environmentObject(downloadManager) .environmentObject(updateService) - .modelContext(tabContext) - .modelContext(historyContext) + .withTheme() .onAppear { keyModifierListener.registerKeyDownHandler { event in guard !appState.isFloatingTabSwitchVisible else { return false } @@ -117,8 +119,6 @@ struct OraApp: App { } } } - .modelContext(downloadContext) - .withTheme() } .defaultSize(width: 1440, height: 900) .windowStyle(.hiddenTitleBar) @@ -136,6 +136,8 @@ struct OraApp: App { .keyboardShortcut( KeyboardShortcuts.Tabs.close ) + + ImportDataButton() } CommandGroup(after: .pasteboard) { @@ -163,6 +165,12 @@ struct OraApp: App { } } + CommandGroup(replacing: .appInfo) { + Button("About Ora") { + showAboutWindow() + } + } + // CommandGroup(replacing: .appSettings) { // Button("Settings…") { // SettingsWindowController.shared.show() @@ -231,4 +239,25 @@ struct OraApp: App { .withTheme() } } + + private func showAboutWindow() { + let alert = NSAlert() + alert.messageText = "Ora Browser" + alert.informativeText = """ + Version \(getAppVersion()) + + Fast, secure, and beautiful browser built for macOS. + + Β© 2025 Ora Browser + """ + alert.alertStyle = .informational + alert.addButton(withTitle: "OK") + alert.runModal() + } + + private func getAppVersion() -> String { + let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" + let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown" + return "\(version) (\(build))" + } } diff --git a/project.yml b/project.yml index 27f19f6f..0ddc05cf 100644 --- a/project.yml +++ b/project.yml @@ -1,26 +1,34 @@ name: Ora options: bundleIdPrefix: com.orabrowser +packages: + Sparkle: + url: https://github.com/sparkle-project/Sparkle + from: 2.6.0 targets: ora: - type: application - platform: macOS - deploymentTarget: "14.0" - sources: - - path: ora - resources: - - path: ora/Modules/EmojiPicker/emoji-set.json - - path: ora/Resources - optional: true + type: application + platform: "macOS" + deploymentTarget: "14.0" + sources: + - path: ora + resources: + - path: ora/Modules/EmojiPicker/emoji-set.json + - path: ora/Resources + optional: true + dependencies: + - package: Sparkle + + settings: + base: + SWIFT_VERSION: 5.9 + CODE_SIGN_STYLE: Automatic + MARKETING_VERSION: 0.0.62 + CURRENT_PROJECT_VERSION: 62 + PRODUCT_NAME: Ora + PRODUCT_BUNDLE_IDENTIFIER: com.orabrowser.ora + GENERATE_INFOPLIST_FILE: YES + SUFeedURL: "https://the-ora.github.io/browser/appcast.xml" + SUPublicEDKey: "Ozj+rezzbJAD76RfajtfQ7rFojJbpFSCl/0DcFSBCTI=" + SUEnableAutomaticChecks: YES - settings: - base: - SWIFT_VERSION: 5.9 - CODE_SIGN_STYLE: Automatic - MARKETING_VERSION: 0.0.1 - CURRENT_PROJECT_VERSION: 1 - PRODUCT_NAME: Ora - PRODUCT_BUNDLE_IDENTIFIER: com.orabrowser.ora - GENERATE_INFOPLIST_FILE: YES - SUFeedURL: https://raw.githubusercontent.com/the-ora/browser/main/appcast.xml - SUPublicEDKey: "" # Will be set after generating keys diff --git a/project.yml.bak b/project.yml.bak new file mode 100644 index 00000000..0ddc05cf --- /dev/null +++ b/project.yml.bak @@ -0,0 +1,34 @@ +name: Ora +options: + bundleIdPrefix: com.orabrowser +packages: + Sparkle: + url: https://github.com/sparkle-project/Sparkle + from: 2.6.0 +targets: + ora: + type: application + platform: "macOS" + deploymentTarget: "14.0" + sources: + - path: ora + resources: + - path: ora/Modules/EmojiPicker/emoji-set.json + - path: ora/Resources + optional: true + dependencies: + - package: Sparkle + + settings: + base: + SWIFT_VERSION: 5.9 + CODE_SIGN_STYLE: Automatic + MARKETING_VERSION: 0.0.62 + CURRENT_PROJECT_VERSION: 62 + PRODUCT_NAME: Ora + PRODUCT_BUNDLE_IDENTIFIER: com.orabrowser.ora + GENERATE_INFOPLIST_FILE: YES + SUFeedURL: "https://the-ora.github.io/browser/appcast.xml" + SUPublicEDKey: "Ozj+rezzbJAD76RfajtfQ7rFojJbpFSCl/0DcFSBCTI=" + SUEnableAutomaticChecks: YES + diff --git a/setup-sparkle.sh b/setup-sparkle.sh index 5c6bf352..9cb70a5b 100755 --- a/setup-sparkle.sh +++ b/setup-sparkle.sh @@ -8,8 +8,69 @@ echo "πŸ” Setting up Sparkle for Ora Browser..." # Check if generate_keys is available if ! command -v generate_keys &> /dev/null; then - echo "❌ generate_keys not found. Please install Sparkle tools:" - echo " brew install sparkle" + echo "πŸ“¦ Installing Sparkle tools..." + + # Try Homebrew first + if command -v brew &> /dev/null; then + echo "🍺 Installing via Homebrew..." + brew install sparkle + else + echo "❌ Homebrew not found. Please install Homebrew first:" + echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" + exit 1 + fi + + # Check again after installation + if ! command -v generate_keys &> /dev/null; then + echo "❌ generate_keys still not found after installation." + echo "πŸ”§ Trying alternative installation method..." + + # Try downloading Sparkle tools directly + SPARKLE_URL="https://github.com/sparkle-project/Sparkle/releases/download/2.7.1/Sparkle-2.7.1.tar.xz" + SPARKLE_DIR="$HOME/.sparkle-tools" + + echo "⬇️ Downloading Sparkle tools..." + mkdir -p "$SPARKLE_DIR" + cd "$SPARKLE_DIR" + + if command -v curl &> /dev/null; then + curl -L "$SPARKLE_URL" -o sparkle.tar.xz + elif command -v wget &> /dev/null; then + wget "$SPARKLE_URL" -O sparkle.tar.xz + else + echo "❌ Neither curl nor wget found. Please install one of them." + exit 1 + fi + + echo "πŸ“¦ Extracting Sparkle tools..." + tar -xf sparkle.tar.xz + + # Find the generate_keys binary + GENERATE_KEYS_PATH=$(find . -name "generate_keys" -type f 2>/dev/null | head -1) + + if [ -z "$GENERATE_KEYS_PATH" ]; then + echo "❌ generate_keys binary not found in downloaded Sparkle tools." + echo "πŸ” Contents of Sparkle directory:" + find . -type f -name "*" | head -10 + exit 1 + fi + + echo "βœ… Found generate_keys at: $GENERATE_KEYS_PATH" + + # Add to PATH for this session + export PATH="$SPARKLE_DIR/bin:$PATH" + + # Create symlink for future use + mkdir -p "$HOME/bin" + ln -sf "$GENERATE_KEYS_PATH" "$HOME/bin/generate_keys" + export PATH="$HOME/bin:$PATH" + fi +fi + +# Verify generate_keys is now available +if ! command -v generate_keys &> /dev/null; then + echo "❌ generate_keys command still not available." + echo "πŸ”§ Please check your Sparkle installation or PATH." exit 1 fi @@ -17,12 +78,26 @@ fi echo "πŸ”‘ Generating DSA keys..." generate_keys +# Move keys to build directory +if [ -f "dsa_priv.pem" ]; then + mv dsa_priv.pem build/ +fi +if [ -f "dsa_pub.pem" ]; then + mv dsa_pub.pem build/ +fi + +# Copy appcast template to build directory +if [ -f "appcast.xml" ]; then + cp appcast.xml build/ +fi + echo "βœ… DSA keys generated!" echo "" echo "πŸ“‹ Next steps:" -echo "1. Copy the public key from dsa_pub.pem" +echo "1. Copy the public key from build/dsa_pub.pem" echo "2. Add it to your Info.plist as SUPublicEDKey" -echo "3. Keep dsa_priv.pem secure for signing releases" -echo "4. Update the appcast.xml template with your GitHub repo URL" +echo "3. Keep build/dsa_priv.pem secure for signing releases" +echo "4. Update build/appcast.xml template with your GitHub repo URL" echo "" -echo "πŸ”’ IMPORTANT: Keep dsa_priv.pem secure and never commit it to version control!" \ No newline at end of file +echo "πŸ”’ IMPORTANT: Keep build/dsa_priv.pem secure and never commit it to version control!" +echo "πŸ“ All build files are now organized in the build/ directory" \ No newline at end of file diff --git a/upload-dmg.sh b/upload-dmg.sh new file mode 100755 index 00000000..ea9c6e2c --- /dev/null +++ b/upload-dmg.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -e + +# Upload DMG to GitHub Releases Script +# This script uploads the built DMG to GitHub releases + +if [ $# -lt 1 ]; then + echo "Usage: $0 [dmg_file]" + echo "Example: $0 0.0.18 build/Ora-Browser.dmg" + exit 1 +fi + +VERSION=$1 +DMG_FILE=${2:-"build/Ora-Browser.dmg"} +REPO="the-ora/browser" + +echo "πŸ“€ Uploading Ora Browser v$VERSION DMG to GitHub..." + +# Check if gh CLI is installed +if ! command -v gh &> /dev/null; then + echo "❌ GitHub CLI (gh) not found. Please install it first:" + echo " brew install gh" + echo " gh auth login" + exit 1 +fi + +# Check if DMG file exists +if [ ! -f "$DMG_FILE" ]; then + echo "❌ DMG file not found: $DMG_FILE" + exit 1 +fi + +# Check if release already exists +if gh release view "v$VERSION" --repo "$REPO" &> /dev/null; then + echo "πŸ“‹ Release v$VERSION already exists. Uploading DMG to existing release..." + gh release upload "v$VERSION" "$DMG_FILE" --repo "$REPO" --clobber +else + echo "πŸ“‹ Creating new release v$VERSION..." + gh release create "v$VERSION" "$DMG_FILE" \ + --repo "$REPO" \ + --title "Ora Browser v$VERSION" \ + --notes "Release v$VERSION of Ora Browser" \ + --generate-notes +fi + +echo "βœ… Successfully uploaded $DMG_FILE to GitHub release v$VERSION" +echo "πŸ”— Release URL: https://github.com/$REPO/releases/tag/v$VERSION" \ No newline at end of file