@photostructure/fs-metadata 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -77,6 +77,34 @@ This is @photostructure/fs-metadata - a cross-platform native Node.js module for
77
77
  - Debug messages from both JavaScript and native code are sent to `stderr`
78
78
  - Uses native Node.js debuglog for determining if logging is enabled
79
79
 
80
+ ## Release Process
81
+
82
+ The project uses a vanilla npm/git release workflow with GPG signed commits for security:
83
+
84
+ ### Prerequisites
85
+ - Repository secrets must be configured:
86
+ - `NPM_TOKEN`: Authentication token for npm publishing
87
+ - `GPG_PRIVATE_KEY`: ASCII-armored GPG private key for signing commits
88
+ - `GPG_PASSPHRASE`: Passphrase for the GPG key (if applicable)
89
+
90
+ ### Automated Release
91
+ 1. Trigger via GitHub Actions workflow dispatch with version type (patch/minor/major)
92
+ 2. Builds all prebuilds for supported platforms
93
+ 3. Runs comprehensive test suite across platforms
94
+ 4. Uses `npm version` to bump version and create signed git tags
95
+ 5. Publishes to npm registry
96
+ 6. Creates GitHub release with auto-generated notes
97
+ 7. All commits and tags are GPG signed for verification
98
+
99
+ ### Manual Release (if needed)
100
+ ```bash
101
+ npm run prepare-release
102
+ git config commit.gpgsign true
103
+ npm version patch|minor|major
104
+ npm publish
105
+ git push origin main --follow-tags
106
+ ```
107
+
80
108
  ## Example Usage
81
109
 
82
110
  ```typescript
@@ -0,0 +1,474 @@
1
+ # GPG Bot Setup for GitHub Actions
2
+
3
+ This document explains how to set up GPG signing for the GitHub Actions release workflow using modern Ed25519 signing-only keys.
4
+
5
+ **Note**: For a simpler alternative, see [SSH_RELEASE_HOWTO.md](./SSH_RELEASE_HOWTO.md) for SSH-based commit signing.
6
+
7
+ ## 0. Create Bot Account (Recommended)
8
+
9
+ For professional projects, create a dedicated bot account rather than using your personal account:
10
+
11
+ ### Create the Bot Account
12
+ 1. Sign out of your personal GitHub account
13
+ 2. Go to https://github.com/join
14
+ 3. Create account with username like `fs-metadata-bot`
15
+ 4. Use email: `fs-metadata-bot@users.noreply.github.com`
16
+ 5. Verify the email address
17
+
18
+ ### Add Bot as Repository Collaborator
19
+ 1. Go to your repo: `https://github.com/photostructure/fs-metadata`
20
+ 2. Click **Settings** tab
21
+ 3. Click **Collaborators** in the left sidebar (or **Manage access** for organizations)
22
+ 4. Click **Add people** (or **Invite a collaborator**)
23
+ 5. Search for your bot account username
24
+ 6. Select **Write** permission level (needed for pushes and releases)
25
+ 7. Send invitation
26
+
27
+ ### Bot Accepts Invitation
28
+ 1. Sign in as the bot account
29
+ 2. Check notifications or email for repository invitation
30
+ 3. Accept the invitation
31
+
32
+ **Benefits of bot account:**
33
+ - Clear separation between human and automated commits
34
+ - Professional appearance in commit history
35
+ - Easier permission management
36
+ - Independent of any individual developer
37
+
38
+ ## 1. Setup Terminal for Password Entry
39
+
40
+ Configure GPG to use terminal-based password entry for easy copy/paste:
41
+
42
+ ```bash
43
+ export GPG_TTY=$(tty)
44
+ echo "pinentry-program /usr/bin/pinentry-curses" >> ~/.gnupg/gpg-agent.conf
45
+ gpgconf --reload gpg-agent
46
+ ```
47
+
48
+ ## 2. Generate Ed25519 Signing-Only GPG Key
49
+
50
+ ### Option A: Quick Command Method (Recommended)
51
+ ```bash
52
+ # Create master key for certification
53
+ gpg --quick-gen-key "fs-metadata-bot <fs-metadata-bot@users.noreply.github.com>" ed25519 default 3y
54
+
55
+ # Get the master key ID from the output, then add signing-only subkey
56
+ gpg --quick-add-key [MASTER_KEY_ID] ed25519 sign 3y
57
+ ```
58
+
59
+ ### Option B: Interactive Expert Mode
60
+ ```bash
61
+ # Generate master key
62
+ gpg --expert --full-generate-key
63
+
64
+ # Follow the prompts:
65
+ # - Key type: (9) ECC and ECC
66
+ # - Curve: (1) Curve 25519 (Ed25519)
67
+ # - Expiration: 3y (3 years recommended)
68
+ # - Real name: fs-metadata-bot
69
+ # - Email: fs-metadata-bot@users.noreply.github.com
70
+ # - Comment: GitHub Actions release bot
71
+
72
+ # Then add signing-only subkey
73
+ gpg --expert --edit-key [YOUR_KEY_ID]
74
+ # At gpg> prompt: addkey
75
+ # Select (10) ECC (sign only)
76
+ # Select (1) Curve 25519
77
+ # Set expiration (3y recommended)
78
+ # At gpg> prompt: save
79
+ ```
80
+
81
+ ## 3. Export the GPG Key
82
+
83
+ ```bash
84
+ # List keys to see the key structure
85
+ gpg --list-secret-keys --keyid-format=long
86
+
87
+ # You'll see output like:
88
+ # sec ed25519/ABC123DEF456 2024-01-01 [SC] [expires: 2027-01-01]
89
+ # ssb ed25519/789GHI012JKL 2024-01-01 [S] [expires: 2027-01-01]
90
+ #
91
+ # Use the master key ID (ABC123DEF456) for export
92
+
93
+ # Export the private key (replace MASTER_KEY_ID with actual master key ID)
94
+ gpg --armor --export-secret-key MASTER_KEY_ID > bot-private-key.asc
95
+
96
+ # Export the public key for verification
97
+ gpg --armor --export MASTER_KEY_ID > bot-public-key.asc
98
+ ```
99
+
100
+ **Note**: Export the master key, which includes both the master key and signing subkey. GitHub Actions will automatically use the signing subkey for commit signing.
101
+
102
+ ## 4. Configure Repository Secrets
103
+
104
+ Add these secrets to your GitHub repository:
105
+
106
+ - `GPG_PRIVATE_KEY`: Content of `bot-private-key.asc`
107
+ - `GPG_PASSPHRASE`: The passphrase you set (or empty string if none)
108
+ - `GIT_USER_NAME`: The bot's display name (e.g., `fs-metadata-bot`)
109
+ - `GIT_USER_EMAIL`: The bot's email (e.g., `fs-metadata-bot@users.noreply.github.com`)
110
+ - `NPM_TOKEN`: Your npm authentication token
111
+
112
+ ## 5. Add Public Key to GitHub
113
+
114
+ **Important**: Add the public key to the **bot account**, not your personal account.
115
+
116
+ 1. Sign in as the bot account (`fs-metadata-bot`)
117
+ 2. Copy the content of `bot-public-key.asc`
118
+ 3. Go to GitHub Settings > SSH and GPG keys
119
+ 4. Click **New GPG key**
120
+ 5. Paste the public key content
121
+ 6. Add the key
122
+
123
+ This ensures that commits signed by the bot show as "Verified" and are attributed to the bot account.
124
+
125
+ ## 6. Security Notes
126
+
127
+ - The bot's private key should only be used for automated releases
128
+ - Store the key securely and never commit it to the repository
129
+ - Ed25519 keys are more secure and performant than RSA
130
+ - The signing subkey (marked with [S]) will be used for Git commits
131
+ - The passphrase (if any) should be strong and unique
132
+ - Consider setting a 3-year expiration and rotating keys before expiry
133
+
134
+ ## 7. Testing
135
+
136
+ After setup, test the workflow:
137
+
138
+ 1. Go to Actions tab in GitHub
139
+ 2. Run the test workflow first: `gh workflow run test-gpg-actions.yml`
140
+ 3. Once test passes, run "Build & Release" workflow manually
141
+ 4. Choose a version bump type (patch recommended for testing)
142
+ 5. Verify the release is created with verified commits
143
+
144
+ ## 8. Pre-Release Checklist
145
+
146
+ Before triggering a release, verify:
147
+
148
+ - [ ] **All required secrets are configured** in repository settings:
149
+ - [ ] `GPG_PRIVATE_KEY` - ASCII-armored GPG private key
150
+ - [ ] `GPG_PASSPHRASE` - Passphrase for the GPG key
151
+ - [ ] `GIT_USER_NAME` - Bot's display name (e.g., `fs-metadata-bot`)
152
+ - [ ] `GIT_USER_EMAIL` - Bot's email (must match GPG key email exactly!)
153
+ - [ ] `NPM_TOKEN` - Valid npm authentication token with publish permissions
154
+ - [ ] **GPG key email exactly matches `GIT_USER_EMAIL`** - This is critical!
155
+ - [ ] **Bot account has write access** to the repository
156
+ - [ ] **GPG public key is added** to the bot's GitHub account (not your personal account)
157
+ - [ ] **Test workflow passes**: Run `gh workflow run test-gpg-actions.yml` first
158
+ - [ ] **No uncommitted changes** in your local repository
159
+ - [ ] **You're on the main branch** with latest changes pulled
160
+
161
+ ### Quick Validation Commands
162
+
163
+ ```bash
164
+ # Test GPG signing locally (optional)
165
+ echo "test" | gpg --clearsign
166
+
167
+ # Verify npm token (run locally)
168
+ npm whoami
169
+
170
+ # Test the GPG actions
171
+ gh workflow run test-gpg-actions.yml
172
+
173
+ # Do a dry-run of npm publish (optional)
174
+ npm publish --dry-run
175
+ ```
176
+
177
+ ## 9. Cleanup
178
+
179
+ After exporting, securely delete the local key files:
180
+
181
+ ```bash
182
+ rm bot-private-key.asc bot-public-key.asc
183
+ ```
184
+
185
+ # HOWTO: Secure GPG Signing in GitHub Actions with Composite Sub-Actions
186
+
187
+ ## Overview
188
+ This guide explains how to use the reusable composite actions for GPG signing in CI/CD workflows, how the sub-actions work internally, and how to configure your GitHub repository for secure, automated signing with proper cleanup.
189
+
190
+ ---
191
+
192
+ ## 1. What Are the Sub-Actions?
193
+
194
+ ### setup-gpg-bot
195
+ Prepares the CI environment for GPG signing by:
196
+ - Importing a GPG private key
197
+ - Setting key trust level
198
+ - Creating a wrapper script for non-interactive signing
199
+ - Configuring git for commit/tag signing
200
+ - Validating email consistency between GPG key and git config
201
+
202
+ ### cleanup-gpg-bot
203
+ Ensures no sensitive data remains after workflow completion by:
204
+ - Removing all GPG keys from the keyring
205
+ - Deleting GPG configuration directories
206
+ - Removing wrapper scripts
207
+
208
+ These actions are located in:
209
+ - `.github/actions/setup-gpg-bot/action.yml`
210
+ - `.github/actions/cleanup-gpg-bot/action.yml`
211
+
212
+ ---
213
+
214
+ ## 2. Required GitHub Secrets
215
+
216
+ Configure these repository secrets in Settings → Secrets and variables → Actions:
217
+
218
+ | Secret | Description | Example |
219
+ |--------|-------------|---------|
220
+ | `GPG_PRIVATE_KEY` | ASCII-armored GPG private key | Output of `gpg --armor --export-secret-key KEYID` |
221
+ | `GPG_PASSPHRASE` | Passphrase for the GPG key | Your secure passphrase |
222
+ | `GIT_USER_NAME` | Git user name for commits | `example-bot` |
223
+ | `GIT_USER_EMAIL` | Git user email (MUST match GPG key email) | `bot@example.com` |
224
+ | `NPM_TOKEN` | npm registry authentication token | `npm_xxx...` |
225
+
226
+ **Critical**: The `GIT_USER_EMAIL` must exactly match the email in your GPG key's UID, or signing will fail.
227
+
228
+ ---
229
+
230
+ ## 3. How to Use the Actions in Your Workflow
231
+
232
+ ### Basic Usage Pattern
233
+
234
+ ```yaml
235
+ jobs:
236
+ publish:
237
+ runs-on: ubuntu-latest
238
+ steps:
239
+ # 1. Checkout code
240
+ - uses: actions/checkout@v4
241
+ with:
242
+ fetch-depth: 0 # Important for version bumps
243
+
244
+ # 2. Setup GPG signing
245
+ - uses: ./.github/actions/setup-gpg-bot
246
+ with:
247
+ gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
248
+ gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
249
+ git-user-name: ${{ secrets.GIT_USER_NAME }}
250
+ git-user-email: ${{ secrets.GIT_USER_EMAIL }}
251
+
252
+ # 3. Your build/test steps here
253
+ - run: npm ci
254
+ - run: npm test
255
+
256
+ # 4. Steps that require GPG signing
257
+ - name: Version and tag release
258
+ env:
259
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # REQUIRED!
260
+ run: |
261
+ npm version patch --message "release: %s"
262
+ git push origin main --follow-tags
263
+
264
+ # 5. Always cleanup
265
+ - uses: ./.github/actions/cleanup-gpg-bot
266
+ if: always()
267
+ ```
268
+
269
+ ### Important Environment Variable Requirements
270
+
271
+ **The `GPG_PASSPHRASE` environment variable MUST be set for any step that performs GPG signing operations.** This includes:
272
+ - `npm version` commands
273
+ - Direct `git commit -S` or `git tag -s` commands
274
+ - Any tool that internally uses git signing
275
+
276
+ **Security Note**: The passphrase is NOT automatically exported to all steps. Each step that needs to sign must explicitly pull `GPG_PASSPHRASE` from secrets:
277
+
278
+ ```yaml
279
+ env:
280
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
281
+ ```
282
+
283
+ This follows the principle of least privilege - only steps that actually perform signing operations have access to the passphrase.
284
+
285
+ Example:
286
+ ```yaml
287
+ - name: Create signed commit
288
+ env:
289
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # Required!
290
+ run: |
291
+ git commit -m "feat: new feature"
292
+ git tag -s v1.0.0 -m "Version 1.0.0"
293
+ ```
294
+
295
+ ---
296
+
297
+ ## 4. How the Sub-Actions Work Internally
298
+
299
+ ### setup-gpg-bot Implementation Details
300
+
301
+ 1. **GPG Directory Setup**
302
+ ```bash
303
+ mkdir -p ~/.gnupg
304
+ chmod 700 ~/.gnupg
305
+ echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf
306
+ echo "allow-loopback-pinentry" >> ~/.gnupg/gpg-agent.conf
307
+ ```
308
+
309
+ 2. **Key Import**
310
+ - Imports the private key from the `GPG_PRIVATE_KEY` secret
311
+ - Restarts gpg-agent to ensure configuration is loaded
312
+
313
+ 3. **Wrapper Script Creation**
314
+ - Creates a unique wrapper script at `/tmp/gpg-wrapper-$$-$RANDOM.sh`
315
+ - The wrapper automatically provides the passphrase to GPG
316
+ - Exports `GPG_WRAPPER_PATH` to the environment for subsequent steps
317
+
318
+ 4. **Trust Configuration**
319
+ - Extracts the key ID and sets trust level to 5 (ultimate)
320
+ - Validates that the key was imported successfully
321
+
322
+ 5. **Email Validation** (NEW!)
323
+ - Extracts email from the GPG key
324
+ - Compares with `GIT_USER_EMAIL`
325
+ - Fails fast if emails don't match, preventing confusing signing failures later
326
+
327
+ 6. **Git Configuration**
328
+ ```bash
329
+ git config --global user.name "$GIT_USER_NAME"
330
+ git config --global user.email "$GIT_USER_EMAIL"
331
+ git config --global user.signingkey "$KEY_ID"
332
+ git config --global gpg.program "$GPG_WRAPPER_PATH"
333
+ git config --global commit.gpgsign true
334
+ git config --global tag.gpgsign true
335
+ npm config set sign-git-tag true
336
+ ```
337
+
338
+ ### cleanup-gpg-bot Implementation Details
339
+
340
+ 1. Removes all secret keys from the keyring
341
+ 2. Removes all public keys
342
+ 3. Deletes the `~/.gnupg` directory
343
+ 4. Removes the GPG wrapper script (if `GPG_WRAPPER_PATH` is set)
344
+ 5. Cleans up any temporary files
345
+
346
+ ---
347
+
348
+ ## 5. Troubleshooting Guide
349
+
350
+ ### Common Issues and Solutions
351
+
352
+ #### "GPG key email does not match Git user.email"
353
+ - **Cause**: The email in your GPG key doesn't match `GIT_USER_EMAIL`
354
+ - **Solution**: Ensure both emails are identical, or generate a new key with the correct email
355
+
356
+ #### "No secret key found after import"
357
+ - **Cause**: The GPG key wasn't imported correctly
358
+ - **Solution**: Verify your `GPG_PRIVATE_KEY` secret contains the full ASCII-armored private key
359
+
360
+ #### "error: gpg failed to sign the data"
361
+ - **Cause**: Usually missing `GPG_PASSPHRASE` environment variable
362
+ - **Solution**: Ensure `GPG_PASSPHRASE` is set in the environment for signing steps
363
+
364
+ #### "cannot allocate memory" or hanging operations
365
+ - **Cause**: GPG trying to use GUI pinentry in headless environment
366
+ - **Solution**: The actions handle this automatically; ensure you're using the setup action
367
+
368
+ ### Debug Commands
369
+
370
+ Add this step to debug GPG configuration:
371
+ ```yaml
372
+ - name: Debug GPG setup
373
+ env:
374
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
375
+ run: |
376
+ echo "=== GPG Configuration ==="
377
+ gpg --version
378
+ "$GPG_WRAPPER_PATH" --list-secret-keys --keyid-format LONG
379
+ git config --list | grep -E "(gpg|user|sign)"
380
+ echo "=== Test signing ==="
381
+ echo "test" | "$GPG_WRAPPER_PATH" --clearsign
382
+ ```
383
+
384
+ ---
385
+
386
+ ## 6. Security Best Practices
387
+
388
+ 1. **Use Ed25519 Keys**: More secure and faster than RSA
389
+ ```bash
390
+ gpg --quick-gen-key "Bot Name <email>" ed25519 sign 3y
391
+ ```
392
+
393
+ 2. **Set Key Expiration**: Rotate keys every 2-3 years
394
+
395
+ 3. **Use Strong Passphrases**: Generate with password manager
396
+
397
+ 4. **Limit Repository Access**: Only give bot accounts write permission
398
+
399
+ 5. **Audit Secret Access**: Regularly review who has access to repository secrets
400
+
401
+ 6. **Never Commit Keys**: Even encrypted keys shouldn't be in the repository
402
+
403
+ ---
404
+
405
+ ## 7. Complete Setup Example
406
+
407
+ ### Step 1: Generate GPG Key
408
+ ```bash
409
+ # Generate Ed25519 signing key
410
+ gpg --quick-gen-key "fs-metadata-bot <fs-metadata-bot@users.noreply.github.com>" ed25519 sign 3y
411
+
412
+ # List keys to get the key ID
413
+ gpg --list-secret-keys --keyid-format=long
414
+
415
+ # Export the private key
416
+ gpg --armor --export-secret-key YOUR_KEY_ID > bot-private.asc
417
+
418
+ # Export the public key
419
+ gpg --armor --export YOUR_KEY_ID > bot-public.asc
420
+ ```
421
+
422
+ ### Step 2: Configure GitHub
423
+ 1. Add secrets to repository (Settings → Secrets → Actions)
424
+ 2. Add public key to bot's GitHub account (Settings → SSH and GPG keys)
425
+
426
+ ### Step 3: Test the Setup
427
+ Use the test workflow:
428
+ ```bash
429
+ gh workflow run test-gpg-actions.yml
430
+ ```
431
+
432
+ ---
433
+
434
+ ## 8. Advanced Topics
435
+
436
+ ### Using Different Wrapper Paths
437
+ The setup action exports `GPG_WRAPPER_PATH` to the environment. You can use this in custom scripts:
438
+
439
+ ```yaml
440
+ - name: Custom signing operation
441
+ env:
442
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
443
+ run: |
444
+ # Use the wrapper path directly
445
+ "$GPG_WRAPPER_PATH" --detach-sign myfile.tar.gz
446
+ ```
447
+
448
+ ### Conditional GPG Setup
449
+ Only setup GPG for release jobs:
450
+ ```yaml
451
+ - uses: ./.github/actions/setup-gpg-bot
452
+ if: github.event_name == 'workflow_dispatch' && github.event.inputs.version
453
+ with:
454
+ # ... parameters ...
455
+ ```
456
+
457
+ ### Multiple Signing Keys
458
+ For different environments:
459
+ ```yaml
460
+ - uses: ./.github/actions/setup-gpg-bot
461
+ with:
462
+ gpg-private-key: ${{ github.ref == 'refs/heads/main' && secrets.PROD_GPG_KEY || secrets.DEV_GPG_KEY }}
463
+ # ... other parameters ...
464
+ ```
465
+
466
+ ---
467
+
468
+ ## 9. References
469
+ - [GitHub Actions: Environment files](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files)
470
+ - [GitHub Actions: Encrypted secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
471
+ - [GitHub Actions: Composite Actions](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action)
472
+ - [GPG Best Practices](https://riseup.net/en/security/message-security/openpgp/best-practices)
473
+ - [npm version](https://docs.npmjs.com/cli/v10/commands/npm-version)
474
+ - [Git GPG signing](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)
@@ -0,0 +1,203 @@
1
+ # SSH Bot Setup for GitHub Actions
2
+
3
+ This document explains how to set up SSH commit signing for the GitHub Actions release workflow. SSH signing is newer and simpler than GPG signing, with full GitHub support.
4
+
5
+ ## 0. Create Bot Account (Recommended)
6
+
7
+ For professional projects, create a dedicated bot account rather than using your personal account:
8
+
9
+ ### Create the Bot Account
10
+ 1. Sign out of your personal GitHub account
11
+ 2. Go to https://github.com/join
12
+ 3. Create account with username like `photostructure-bot`
13
+ 4. Use email: `photostructure-bot@users.noreply.github.com`
14
+ 5. Verify the email address
15
+
16
+ ### Add Bot as Repository Collaborator
17
+ 1. Go to your repo: `https://github.com/photostructure/fs-metadata`
18
+ 2. Click **Settings** tab
19
+ 3. Click **Collaborators** in the left sidebar
20
+ 4. Click **Add people**
21
+ 5. Search for your bot account username
22
+ 6. Select **Write** permission level (needed for pushes and releases)
23
+ 7. Send invitation
24
+
25
+ ### Bot Accepts Invitation
26
+ 1. Sign in as the bot account
27
+ 2. Check notifications or email for repository invitation
28
+ 3. Accept the invitation
29
+
30
+ ## 1. Generate SSH Signing Key
31
+
32
+ Generate an Ed25519 SSH key specifically for commit signing:
33
+
34
+ ```bash
35
+ # Generate the key pair
36
+ ssh-keygen -t ed25519 -f ~/.ssh/photostructure-bot-signing -N "" -C "photostructure-bot"
37
+
38
+ # Display the public key (you'll need this for GitHub)
39
+ cat ~/.ssh/photostructure-bot-signing.pub
40
+ ```
41
+
42
+ ## 2. Add SSH Key to GitHub Bot Account
43
+
44
+ **Important**: Add the key to the **bot account**, not your personal account.
45
+
46
+ 1. Sign in as `photostructure-bot`
47
+ 2. Go to Settings → SSH and GPG keys
48
+ 3. Click **New SSH key**
49
+ 4. **Critical**: For "Key type", select **"Signing Key"** (not "Authentication Key")
50
+ 5. Title: `fs-metadata Release Signing Key`
51
+ 6. Key: Paste the contents of `~/.ssh/photostructure-bot-signing.pub`
52
+ 7. Click **Add SSH key**
53
+
54
+ ## 3. Configure Repository Secrets
55
+
56
+ Add the private key to your repository secrets:
57
+
58
+ ### Copy the Private Key
59
+
60
+ ```bash
61
+ # Copy private key to clipboard (macOS)
62
+ cat ~/.ssh/photostructure-bot-signing | pbcopy
63
+
64
+ # Copy private key to clipboard (Linux with xclip)
65
+ cat ~/.ssh/photostructure-bot-signing | xclip -selection clipboard
66
+
67
+ # Copy private key to clipboard (Windows with clip)
68
+ cat ~/.ssh/photostructure-bot-signing | clip
69
+ ```
70
+
71
+ ### Add Repository Secrets
72
+
73
+ 1. Go to your repository settings
74
+ 2. Navigate to Settings → Secrets and variables → Actions
75
+ 3. Add these secrets:
76
+
77
+ | Secret Name | Value |
78
+ |-------------|-------|
79
+ | `SSH_SIGNING_KEY` | Paste the private key content |
80
+ | `GIT_USER_NAME` | `photostructure-bot` |
81
+ | `GIT_USER_EMAIL` | `bot@photostructure.com` |
82
+ | `NPM_TOKEN` | Your npm authentication token |
83
+
84
+ ## 4. How SSH Signing Works in Actions
85
+
86
+ The SSH signing setup uses two composite actions:
87
+
88
+ ### setup-ssh-bot
89
+ - Installs the SSH private key
90
+ - Configures Git to use SSH signing format
91
+ - Sets up commit and tag signing
92
+ - Creates allowed signers file for verification
93
+
94
+ ### cleanup-ssh-bot
95
+ - Removes SSH keys from the runner
96
+ - Clears Git signing configuration
97
+ - Ensures no secrets remain after workflow
98
+
99
+ ## 5. Using SSH Signing in Workflows
100
+
101
+ ### Basic Workflow Example
102
+
103
+ ```yaml
104
+ jobs:
105
+ publish:
106
+ runs-on: ubuntu-latest
107
+ permissions:
108
+ contents: write
109
+ steps:
110
+ - uses: actions/checkout@v4
111
+ with:
112
+ fetch-depth: 0
113
+
114
+ - uses: ./.github/actions/setup-ssh-bot
115
+ with:
116
+ ssh-signing-key: ${{ secrets.SSH_SIGNING_KEY }}
117
+ git-user-name: ${{ secrets.GIT_USER_NAME }}
118
+ git-user-email: ${{ secrets.GIT_USER_EMAIL }}
119
+
120
+ # Your build and release steps here
121
+ - run: npm ci
122
+ - run: npm version patch
123
+ - run: git push origin main --follow-tags
124
+
125
+ - uses: ./.github/actions/cleanup-ssh-bot
126
+ if: always()
127
+ ```
128
+
129
+ ## 6. Testing SSH Signing
130
+
131
+ Test your setup before using it in production:
132
+
133
+ ```bash
134
+ # Run the SSH signing test workflow
135
+ gh workflow run test-ssh-actions.yml
136
+
137
+ # Check the workflow status
138
+ gh run list --workflow=test-ssh-actions.yml
139
+ ```
140
+
141
+ ## 7. Pre-Release Checklist
142
+
143
+ Before triggering a release:
144
+
145
+ - [ ] **SSH_SIGNING_KEY** secret is configured in repository
146
+ - [ ] **GIT_USER_NAME** and **GIT_USER_EMAIL** secrets are set
147
+ - [ ] **NPM_TOKEN** is valid with publish permissions
148
+ - [ ] SSH public key is added to bot's GitHub account as **Signing Key**
149
+ - [ ] Bot account has **write access** to the repository
150
+ - [ ] Test workflow passes: `gh workflow run test-ssh-actions.yml`
151
+ - [ ] You're on the main branch with latest changes
152
+
153
+ ## 8. Advantages of SSH Signing
154
+
155
+ | Feature | SSH Signing | GPG Signing |
156
+ |---------|-------------|-------------|
157
+ | Setup complexity | Simple | Complex |
158
+ | Key generation | One command | Multiple steps |
159
+ | Passphrase handling | Not required | Required |
160
+ | Wrapper scripts | Not needed | Required |
161
+ | GitHub verification | ✓ Supported | ✓ Supported |
162
+ | Maintenance | Minimal | Higher |
163
+
164
+ ## 9. Security Best Practices
165
+
166
+ 1. **Use Ed25519 keys**: Most secure and efficient algorithm
167
+ 2. **Dedicated signing keys**: Don't reuse authentication keys for signing
168
+ 3. **Bot accounts**: Use dedicated accounts for automation
169
+ 4. **Rotate keys**: Consider rotating every 2-3 years
170
+ 5. **Secure storage**: Never commit private keys to repositories
171
+
172
+ ## 10. Cleanup
173
+
174
+ After setting up, securely remove local key copies:
175
+
176
+ ```bash
177
+ # Remove the local key files
178
+ rm ~/.ssh/photostructure-bot-signing
179
+ rm ~/.ssh/photostructure-bot-signing.pub
180
+
181
+ # Or move to secure backup location
182
+ mv ~/.ssh/photostructure-bot-signing* ~/secure-backup/
183
+ ```
184
+
185
+ ## 11. Troubleshooting
186
+
187
+ ### Commits show as "Unverified"
188
+ - Ensure the SSH key is added as a **Signing Key** (not Authentication Key)
189
+ - Verify the email in Git config matches the GitHub account email
190
+ - Check that the bot account owns the key
191
+
192
+ ### "error: Load key failed"
193
+ - Verify SSH_SIGNING_KEY secret contains the complete private key
194
+ - Check for extra newlines or spaces in the secret
195
+
196
+ ### Permission denied on push
197
+ - Ensure bot account has write access to the repository
198
+
199
+ ## References
200
+
201
+ - [GitHub SSH Commit Verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#ssh-commit-signature-verification)
202
+ - [Git SSH Signing Documentation](https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgformat)
203
+ - [GitHub Actions Encrypted Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@photostructure/fs-metadata",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "Cross-platform native filesystem metadata retrieval for Node.js",
5
5
  "homepage": "https://photostructure.github.io/fs-metadata/",
6
6
  "types": "./dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "repository": {
23
23
  "type": "git",
24
- "url": "git+ssh://git@github.com/photostructure/fs-metadata.git"
24
+ "url": "https://github.com/photostructure/fs-metadata.git"
25
25
  },
26
26
  "license": "MIT",
27
27
  "scripts": {
@@ -57,8 +57,7 @@
57
57
  "fmt:ts": "prettier --write \"**/*.(c|m)?ts\"",
58
58
  "// precommit": "should be manually run by developers before they run `git commit`",
59
59
  "precommit": "tsx scripts/precommit.ts",
60
- "prepare-release": "npm run build:dist",
61
- "release": "release-it"
60
+ "prepare-release": "npm run build:dist"
62
61
  },
63
62
  "gypfile": true,
64
63
  "publishConfig": {
@@ -110,7 +109,6 @@
110
109
  "prebuildify": "^6.0.1",
111
110
  "prettier": "^3.5.3",
112
111
  "prettier-plugin-organize-imports": "4.1.0",
113
- "release-it": "^19.0.3",
114
112
  "snyk": "^1.1297.1",
115
113
  "terser": "^5.42.0",
116
114
  "ts-jest": "^29.3.4",
File without changes