@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 +28 -0
- package/doc/GPG_RELEASE_HOWTO.md +474 -0
- package/doc/SSH_RELEASE_HOWTO.md +203 -0
- package/package.json +3 -5
- package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
- /package/{C++_REVIEW_TODO.md → doc/C++_REVIEW_TODO.md} +0 -0
- /package/{WINDOWS_DEBUG_GUIDE.md → doc/WINDOWS_DEBUG_GUIDE.md} +0 -0
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.
|
|
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": "
|
|
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",
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|