Skip to content

Commit 54e3430

Browse files
authored
Feature/improve update and signing (#24)
* The building process with a certificate is correct * docs: update release process documentation and add signing key requirements * fix: update publication date for Circle Camera 0.3.1 release
1 parent 48c27ee commit 54e3430

9 files changed

Lines changed: 219 additions & 33 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ dist-ssr
2727
*.sln
2828
*.sw?
2929
docs/.vitepress/cache
30-
.env
30+
.env
31+
scripts/generate-update-json.js
32+
.tauri/

dev-docs/release-process.md

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ This document details the process for creating and publishing new releases of th
77
Circle Camera uses a shell script (`scripts/release.sh`) to facilitate the release process locally. This script handles:
88

99
1. Validating the code.
10-
2. Building the universal macOS DMG installer.
11-
3. Creating a draft GitHub release with the installer attached.
10+
2. Verifying signing keys are in place.
11+
3. Building the universal macOS DMG installer with proper code signing.
12+
4. Generating the update information with signatures.
13+
5. Creating a draft GitHub release with the installer and update file attached.
1214

1315
## Local Release Process
1416

@@ -21,6 +23,29 @@ Before running the release script, ensure you have the following installed and c
2123
* **jq**: For parsing the version number from `package.json`.
2224
* **GitHub CLI (`gh`)**: For creating the draft release and uploading assets. You need to be authenticated (`gh auth login`).
2325
* **Rust toolchain**: With the `x86_64-apple-darwin` and `aarch64-apple-darwin` targets installed (`rustup target add x86_64-apple-darwin`).
26+
* **Tauri signing keys**: Required for the update system (see "Setting up Signing Keys" section below).
27+
28+
### Setting up Signing Keys
29+
30+
The update system requires cryptographic keys for signing and verifying app updates:
31+
32+
1. **Generate the key pair** (only once):
33+
```
34+
npx @tauri-apps/cli signer generate -w ~/.tauri/circle-camera.key
35+
```
36+
37+
2. **Verify the keys exist**:
38+
```
39+
ls -la ~/.tauri/circle-camera.key*
40+
```
41+
You should see both `circle-camera.key` (private key) and `circle-camera.key.pub` (public key).
42+
43+
3. **Configure environment variables**: Add to your `.env` file or shell profile:
44+
```
45+
export TAURI_SIGNING_PRIVATE_KEY=~/.tauri/circle-camera.key
46+
```
47+
48+
4. **Verify the public key in config**: Ensure `src-tauri/tauri.conf.json` contains the correct public key from `~/.tauri/circle-camera.key.pub` in the `plugins.updater.pubkey` field.
2449

2550
### Triggering a New Release
2651

@@ -51,9 +76,29 @@ The script performs the following steps:
5176
1. **Branch Check**: Verifies you are on the `main` branch (can be overridden for testing: `./scripts/release.sh <branch-name>`).
5277
2. **Get Version**: Extracts the version from `package.json` to create the tag (e.g., `v0.3.1`).
5378
3. **Validate**: Runs `pnpm validate` (checks TypeScript and Rust code).
54-
4. **Build**: Runs `pnpm tauri build --target universal-apple-darwin` to create the universal DMG.
55-
5. **Create Draft Release**: Uses `gh release create` to make a draft release on GitHub with the extracted tag.
56-
6. **Upload Asset**: Uses `gh release upload` to attach the built DMG to the draft release.
79+
4. **Signing Key Check**: Verifies that the required signing keys exist.
80+
5. **Environment Variable Setup**: Ensures the `TAURI_SIGNING_PRIVATE_KEY` environment variable is set.
81+
6. **Build**: Runs `pnpm tauri build --target universal-apple-darwin` to create the universal DMG with embedded public key.
82+
7. **Generate Update Information**: Runs `pnpm generate:update-json` to create the `latest.json` file with signatures for the update system.
83+
8. **Create Draft Release**: Uses `gh release create` to make a draft release on GitHub with the extracted tag.
84+
9. **Upload Assets**: Uploads both the built DMG and `latest.json` to the draft release.
85+
86+
## Auto-Update System
87+
88+
The Circle Camera app includes an auto-update system that checks for new releases and can download and install them automatically. This system requires:
89+
90+
1. **Proper Signing**: Each release must be signed using the private key.
91+
2. **Update Information**: The `latest.json` file must be generated and uploaded to the release.
92+
3. **Public Key Distribution**: The app must be built with the public key embedded.
93+
94+
The release script handles all these requirements automatically, but it's important to understand how they work if you need to troubleshoot issues.
95+
96+
### Update Flow
97+
98+
1. When the app starts, it checks the URL specified in `tauri.conf.json` (`plugins.updater.endpoints`) for the `latest.json` file.
99+
2. If a newer version is found, the app downloads the update package.
100+
3. The signature in the `latest.json` file is verified using the embedded public key.
101+
4. If verification succeeds, the app offers to install the update.
57102

58103
## Manual Steps After Script Completion
59104

@@ -66,6 +111,7 @@ Once the script finishes successfully:
66111
* List new features, improvements, and bug fixes.
67112
4. **Review the attached assets**:
68113
* Verify the universal DMG is present.
114+
* Verify the `latest.json` file is present with correct signatures.
69115
5. **Publish the release** when ready.
70116

71117
## Testing Before Publishing
@@ -77,7 +123,19 @@ Before publishing the release:
77123
* macOS with Apple Silicon (M1/M2/M3)
78124
* macOS with Intel processor
79125
3. Verify that all new features/fixes work correctly.
80-
4. Test the auto-update mechanism by generating the `latest.json` (this is currently a separate manual step if needed, see `scripts/generate-update-json.js`).
126+
4. Test the auto-update mechanism:
127+
* Install an older version of the app.
128+
* Publish the new release.
129+
* Verify that the older version detects and can update to the new version.
130+
131+
## Troubleshooting the Update System
132+
133+
If updates aren't working correctly:
134+
135+
1. **Check the public key**: Ensure the public key in `tauri.conf.json` matches the actual public key at `~/.tauri/circle-camera.key.pub`.
136+
2. **Verify signatures**: Check that the `latest.json` file contains valid signatures.
137+
3. **Validate endpoints**: Make sure the update endpoint in `tauri.conf.json` points to the correct location where `latest.json` is hosted.
138+
4. **Debug logging**: Enable verbose logging in the app to see the update process in detail.
81139

82140
## Platform Support
83141

@@ -106,6 +164,17 @@ If the release script fails:
106164
* Rust build errors (check `pnpm validate` output).
107165
* Incorrect Git branch.
108166
* Network issues during GitHub API calls.
167+
* Missing or incorrect signing keys.
168+
* Environment variables not set correctly.
169+
170+
## Signing Key Security
171+
172+
The private signing key is critical for the security of your update system:
173+
174+
1. **Never commit the private key** to version control.
175+
2. **Limit access** to only trusted release maintainers.
176+
3. **Back up the private key** securely - losing it means you'll need to create a new key pair and users will need to reinstall the app.
177+
4. **Consider password protection** for the private key when generating it.
109178

110179
## Release Announcements
111180

latest.json

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
{
2-
"version": "0.2.0",
3-
"notes": "Circle Camera 0.2.0 release",
4-
"pub_date": "2025-04-17T14:09:31.335Z",
2+
"version": "0.3.1",
3+
"notes": "Circle Camera 0.3.1 release",
4+
"pub_date": "2025-04-20T08:30:36.719Z",
55
"platforms": {
6-
"darwin-x86_64": {
7-
"signature": "",
8-
"url": "https://github.com/devbyray/circle-camera/releases/download/0.2.0/Circle.Camera_0.2.0_x64.dmg"
9-
},
106
"darwin-aarch64": {
11-
"signature": "",
12-
"url": "https://github.com/devbyray/circle-camera/releases/download/0.2.0/Circle.Camera_0.2.0_aarch64.dmg"
7+
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVTNmd6eTc4VG5RRlJlZTZzeEpJZC9pRGp4TzNGQTE5amdqN2dTWUVRYTdMVzJoYUFIZU5udmlIUStSYnNMS1Z6Y3JhdW5xaEc1cnBESzd4c2lnS0l5SllUd2R0MDM4emdnPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzQ1MTM2NjY1CWZpbGU6Q2lyY2xlIENhbWVyYS5hcHAudGFyLmd6CmFiK1NQU3JUeHBPTzU2bTRmbXVqZUJrbTBLYUpKaWxCblIxTUpyMWh4RisvUndWdlRpMGxxU2pjdzhmZUUwdWdKUFFOc1hSam1iVTNpUk05cWltakNRPT0K",
8+
"url": "https://github.com/devbyray/circle-camera/releases/download/v0.3.1/Circle.Camera_0.3.1_aarch64.dmg"
139
}
1410
}
1511
}

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"docs:dev": "vitepress dev docs",
1919
"docs:build": "vitepress build docs",
2020
"docs:preview": "vitepress preview docs",
21-
"release": "./scripts/release.sh"
21+
"release": "./scripts/release.sh",
22+
"tauri:build": "dotenv -- tauri build"
2223
},
2324
"dependencies": {
2425
"@iconify-icons/icomoon-free": "^1.2.3",
@@ -36,6 +37,8 @@
3637
"devDependencies": {
3738
"@tauri-apps/cli": "^2",
3839
"@vitejs/plugin-vue": "^5.2.1",
40+
"dotenv": "^16.5.0",
41+
"dotenv-cli": "^8.0.0",
3942
"release-it": "^18.1.2",
4043
"typescript": "~5.8.3",
4144
"vite": "^6.0.3",

pnpm-lock.yaml

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/generate-update-json.js

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import fs from 'fs';
44
import path from 'path';
55
import { fileURLToPath } from 'url';
66
import { dirname } from 'path';
7+
import os from 'os';
8+
import dotenv from 'dotenv';
79

10+
// Load environment variables from .env file
811
const __filename = fileURLToPath(import.meta.url);
912
const __dirname = dirname(__filename);
1013
const rootDir = path.resolve(__dirname, '..');
14+
dotenv.config({ path: path.join(rootDir, '.env') });
1115

1216
// Read the version from package.json
1317
const packageJsonPath = path.join(rootDir, 'package.json');
@@ -21,24 +25,40 @@ const pubDate = new Date().toISOString();
2125
const repoOwner = 'devbyray';
2226
const repoName = 'circle-camera';
2327

28+
// Get the path to the DMG file
29+
const aarch64DmgPath = path.join(rootDir, 'src-tauri', 'target', 'release', 'bundle', 'dmg', `Circle Camera_${version}_aarch64.dmg`);
30+
console.log(`Looking for DMG file at: ${aarch64DmgPath}`);
31+
32+
// Get the path to the signature file that Tauri auto-generated
33+
const signaturePath = path.join(rootDir, 'src-tauri', 'target', 'release', 'bundle', 'macos', `Circle Camera.app.tar.gz.sig`);
34+
console.log(`Looking for signature file at: ${signaturePath}`);
35+
36+
// Check if the DMG file exists
37+
if (!fs.existsSync(aarch64DmgPath)) {
38+
console.error(`❌ ARM64 DMG not found at: ${aarch64DmgPath}`);
39+
process.exit(1);
40+
}
41+
42+
// Check if the signature file exists
43+
if (!fs.existsSync(signaturePath)) {
44+
console.error(`❌ Signature file not found at: ${signaturePath}`);
45+
process.exit(1);
46+
}
47+
48+
// Read the signature file
49+
const signature = fs.readFileSync(signaturePath, 'utf8').trim();
50+
console.log(`✅ Found signature for update package`);
51+
2452
// Create the update JSON structure
2553
const updateJson = {
2654
version,
2755
notes: `Circle Camera ${version} release`,
2856
pub_date: pubDate,
2957
platforms: {
30-
'darwin-x86_64': {
31-
signature: '',
32-
url: `https://github.com/${repoOwner}/${repoName}/releases/download/${version}/Circle.Camera_${version}_x64.dmg`
33-
},
3458
'darwin-aarch64': {
35-
signature: '',
36-
url: `https://github.com/${repoOwner}/${repoName}/releases/download/${version}/Circle.Camera_${version}_aarch64.dmg`
37-
},
38-
// 'windows-x86_64': {
39-
// signature: '',
40-
// url: `https://github.com/${repoOwner}/${repoName}/releases/download/${version}/Circle.Camera_${version}_x64-setup.exe`
41-
// }
59+
signature,
60+
url: `https://github.com/${repoOwner}/${repoName}/releases/download/v${version}/Circle.Camera_${version}_aarch64.dmg`
61+
}
4262
}
4363
};
4464

@@ -49,9 +69,6 @@ fs.writeFileSync(outputPath, JSON.stringify(updateJson, null, 2), 'utf8');
4969
console.log(`🔄 Created latest.json for version ${version}`);
5070
console.log(`📝 File saved at: ${outputPath}`);
5171
console.log('');
52-
console.log('⚠️ Important: For secure updates, add signatures to the JSON file.');
53-
console.log(' See: https://tauri.app/v2/guides/distribution/updater');
54-
console.log('');
5572
console.log('📋 Release checklist:');
5673
console.log(' 1. Build your app for all platforms');
5774
console.log(' 2. Upload this latest.json file to your GitHub release');

scripts/release.sh

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,27 @@ TAG="v$VERSION"
2929
echo "Running validation..."
3030
pnpm validate
3131

32+
# --- Check for signing keys ---
33+
PRIVATE_KEY_PATH="$HOME/.tauri/circle-camera.key"
34+
PUBLIC_KEY_PATH="$HOME/.tauri/circle-camera.key.pub"
35+
36+
if [ ! -f "$PRIVATE_KEY_PATH" ] || [ ! -f "$PUBLIC_KEY_PATH" ]; then
37+
echo "Error: Signing keys not found. They should be at:"
38+
echo " - $PRIVATE_KEY_PATH"
39+
echo " - $PUBLIC_KEY_PATH"
40+
echo ""
41+
echo "Generate them with: npx @tauri-apps/cli signer generate -w $HOME/.tauri/circle-camera.key"
42+
exit 1
43+
fi
44+
echo "✅ Signing keys found"
45+
46+
# --- Verify environment variables ---
47+
if [ -z "$TAURI_SIGNING_PRIVATE_KEY" ]; then
48+
echo "Warning: TAURI_SIGNING_PRIVATE_KEY environment variable not set."
49+
echo "Setting it now to $PRIVATE_KEY_PATH for this session."
50+
export TAURI_SIGNING_PRIVATE_KEY="$PRIVATE_KEY_PATH"
51+
fi
52+
3253
# --- Build ---
3354
echo "Building universal macOS DMG..."
3455
# Using --target universal-apple-darwin explicitly for clarity
@@ -47,6 +68,16 @@ if [ ! -f "$DMG_PATH" ]; then
4768
fi
4869
echo "DMG file found: $DMG_PATH"
4970

71+
# --- Generate update.json file with signatures ---
72+
echo "Generating latest.json update file with signatures..."
73+
pnpm generate:update-json
74+
75+
if [ ! -f "latest.json" ]; then
76+
echo "Error: latest.json file was not generated"
77+
exit 1
78+
fi
79+
echo "✅ latest.json generated successfully"
80+
5081
# --- Create GitHub Draft Release ---
5182
echo "Creating draft release $TAG on GitHub..."
5283

@@ -66,7 +97,10 @@ fi
6697
echo "Uploading asset: $DMG_PATH"
6798
gh release upload "$TAG" "$DMG_PATH" --clobber
6899

69-
echo "Successfully created draft release $TAG and uploaded $DMG_NAME."
100+
echo "Uploading update information: latest.json"
101+
gh release upload "$TAG" "latest.json" --clobber
102+
103+
echo "Successfully created draft release $TAG and uploaded $DMG_NAME and latest.json."
70104
# Use gh repo view to reliably get owner/repo
71105
REPO_FULL_NAME=$(gh repo view --json owner,name --jq '.owner.login + "/" + .name')
72106
echo "Please review the draft release on GitHub: https://github.com/$REPO_FULL_NAME/releases/edit/$TAG"

0 commit comments

Comments
 (0)