Skip to content

Commit a6047f8

Browse files
authored
Merge pull request #53 from thanglequoc/develop
feat: Add development document
2 parents c2b723e + 30d477f commit a6047f8

2 files changed

Lines changed: 388 additions & 0 deletions

File tree

development/RELEASE_SETUP.md

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
# Release Setup Guide for Timer Ninja
2+
3+
This document explains how to set up release process for publishing Timer Ninja to Maven Central (Sonatype) using JReleaser.
4+
5+
## Recovery from Previous Mistake
6+
7+
During a previous session, following critical files were accidentally deleted:
8+
- `public_key.asc` - GPG public key
9+
- `secret_key.asc` - GPG secret key (containing passphrase)
10+
- Sonatype credentials from build.gradle
11+
12+
This guide documents recovery process and provides instructions for future reference.
13+
14+
## What Has Been Done
15+
16+
### 1. Generated New GPG Key Pair
17+
A new GPG key pair has been generated with following details:
18+
- **Key ID**: `799A99750C819FB915ECDBBC144D9369E0328F75`
19+
- **Type**: RSA 4096-bit
20+
- **Owner**: Thang Le Quoc <thanglequoc.it@gmail.com>
21+
- **Expiration**: None (permanent)
22+
- **Protection**: No passphrase (for automated CI/CD)
23+
24+
**Note**: The key was generated without a passphrase to support automated releases in CI/CD pipelines. This is a common practice but requires careful handling of secret key file.
25+
26+
### 2. Exported Keys
27+
- `public_key.asc` - Exported and stored in project root (safe to commit)
28+
- `secret_key.asc` - Exported and stored in project root (DO NOT commit)
29+
30+
### 3. Updated .gitignore
31+
Added rules to prevent accidental commits of sensitive files:
32+
```
33+
### Security - Keys & Credentials ###
34+
secret_key.asc
35+
*.asc
36+
!public_key.asc
37+
```
38+
39+
### 4. Created JReleaser Configuration
40+
Created `jreleaser.yml` with proper configuration for:
41+
- Maven Central deployment
42+
- Sonatype integration
43+
- GPG signing
44+
- Environment variable-based credentials
45+
46+
### 5. Updated build.gradle
47+
Added JReleaser DSL configuration to enable signing and deployment features. No hardcoded credentials.
48+
49+
## Required Setup Steps
50+
51+
### 1. Publish Your GPG Public Key to a Key Server
52+
53+
Before you can publish to Maven Central, your public key must be available on a public key server:
54+
55+
```bash
56+
# Upload to multiple key servers
57+
gpg --keyserver keyserver.ubuntu.com --send-keys 799A99750C819FB915ECDBBC144D9369E0328F75
58+
gpg --keyserver pgp.mit.edu --send-keys 799A99750C819FB915ECDBBC144D9369E0328F75
59+
gpg --keyserver keys.openpgp.org --send-keys 799A99750C819FB915ECDBBC144D9369E0328F75
60+
```
61+
62+
**Important**: The key must be propagated to at least one key server before Maven Central will accept signed artifacts.
63+
64+
### 2. Set Up Sonatype User Token
65+
66+
You need to obtain Sonatype User Token for publishing to Maven Central:
67+
68+
1. Go to https://central.sonatype.com/usertoken
69+
2. Sign in with your Sonatype account (or create one)
70+
3. Click "Generate User Token"
71+
4. Save the generated credentials:
72+
- **Token ID** (username): This is just an identifier
73+
- **Token** (password): This is the actual BEARER token that will be used for authentication
74+
75+
**Note**: Sonatype User Token uses BEARER token authentication. Both username and password are required in configuration, but for BEARER auth, JReleaser only uses the password field as the actual token.
76+
77+
### 3. Configure Environment Variables
78+
79+
#### For Local Development:
80+
Set the following environment variables before running JReleaser:
81+
82+
```bash
83+
export SONATYPE_USERNAME="your-sonatype-user-token-username"
84+
export SONATYPE_PASSWORD="your-sonatype-user-token-password"
85+
export GPG_PASSPHRASE="" # Empty since key has no passphrase
86+
```
87+
88+
You can add these to your shell profile (~/.zshrc or ~/.bashrc):
89+
```bash
90+
# Add to ~/.zshrc or ~/.bashrc
91+
export SONATYPE_USERNAME="your-username"
92+
export SONATYPE_PASSWORD="your-password"
93+
export GPG_PASSPHRASE=""
94+
```
95+
96+
#### For GitHub Actions:
97+
Add these as secrets and variables in your repository settings:
98+
99+
**Variables** (not sensitive - visible to repository members):
100+
- `GPG_PUBLIC_KEY` - The full content of `public_key.asc` file (NOT base64-encoded)
101+
102+
**Secrets** (sensitive - hidden):
103+
- `GPG_SECRET_KEY` - The full content of `secret_key.asc` file (NOT base64-encoded)
104+
- `SONATYPE_USERNAME` - Your Sonatype User Token username (Token ID)
105+
- `SONATYPE_PASSWORD` - Your Sonatype User Token password (the actual token)
106+
107+
### 4. Verify GPG Key Configuration
108+
109+
Ensure that GPG keys are properly configured:
110+
111+
```bash
112+
# List public keys
113+
gpg --list-keys
114+
115+
# List secret keys
116+
gpg --list-secret-keys
117+
118+
# Verify key is exported
119+
cat public_key.asc
120+
cat secret_key.asc
121+
```
122+
123+
## Release Process
124+
125+
### Option 1: Local Release
126+
127+
1. Set environment variables:
128+
```bash
129+
export SONATYPE_USERNAME="your-username"
130+
export SONATYPE_PASSWORD="your-password"
131+
export GPG_PASSPHRASE=""
132+
```
133+
134+
2. Build and publish:
135+
```bash
136+
./gradlew clean build
137+
./gradlew publishToMavenLocal
138+
```
139+
140+
3. Deploy to Maven Central:
141+
```bash
142+
./gradlew jreleaserFullRelease
143+
```
144+
145+
### Option 2: GitHub Actions (Recommended)
146+
147+
1. Ensure GitHub Actions are configured in `.github/workflows/`
148+
149+
2. Add variables and secrets to repository (Settings → Secrets and variables → Actions):
150+
151+
**Variables**:
152+
- `GPG_PUBLIC_KEY` - Copy the entire content of `public_key.asc` file (including -----BEGIN/END PGP PUBLIC KEY BLOCK-----)
153+
154+
**Secrets**:
155+
- `GPG_SECRET_KEY` - Copy the entire content of `secret_key.asc` file (including -----BEGIN/END PGP PRIVATE KEY BLOCK-----)
156+
- `SONATYPE_USERNAME` - Your Sonatype User Token username (Token ID)
157+
- `SONATYPE_PASSWORD` - Your Sonatype User Token password (the actual token)
158+
159+
3. Trigger the workflow manually:
160+
- Go to **Actions****Release - Publish to Sonatype Maven Central**
161+
- Click **Run workflow**
162+
- Select branch (usually `master`)
163+
- Click **Run workflow**
164+
165+
## Publishing Your Public Key
166+
167+
If you haven't already published your public key to a key server, do this now:
168+
169+
```bash
170+
# Upload to Ubuntu key server (most commonly used)
171+
gpg --keyserver keyserver.ubuntu.com --send-keys 799A99750C819FB915ECDBBC144D9369E0328F75
172+
173+
# Verify it's available
174+
gpg --keyserver keyserver.ubuntu.com --recv-keys 799A99750C819FB915ECDBBC144D9369E0328F75
175+
```
176+
177+
Wait a few minutes for key to propagate across servers before attempting to publish.
178+
179+
## Troubleshooting
180+
181+
### Issue: "Key not found on key server"
182+
**Solution**: Upload your public key to a key server and wait for propagation (can take 5-30 minutes)
183+
184+
### Issue: "GPG signing failed"
185+
**Solution**: Ensure `secret_key.asc` exists in project root and is accessible
186+
187+
### Issue: "Sonatype authentication failed (401 Unauthorized)"
188+
**Solution**:
189+
- Verify you're using User Token credentials, not your regular Sonatype login
190+
- Ensure BOTH `SONATYPE_USERNAME` and `SONATYPE_PASSWORD` are set in GitHub secrets
191+
- Check that `jreleaser.yml` has `authorization: BEARER` configured
192+
- Verify the token hasn't expired by regenerating it at https://central.sonatype.com/usertoken
193+
- Ensure environment variables in workflow use correct JRELEASER-specific names: `JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_SONATYPE_USERNAME` and `JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_SONATYPE_PASSWORD`
194+
195+
### Issue: "Artifacts rejected by Maven Central"
196+
**Solution**:
197+
- Verify your GPG public key is on a key server
198+
- Check that pom.xml has all required metadata
199+
- Ensure all required files (javadoc jar, sources jar) are included
200+
201+
### Issue: "Version mismatch"
202+
**Solution**:
203+
- Ensure that version in `build.gradle` is one you want to release
204+
- Commit and push version change before running workflow
205+
- The workflow uses version directly from `build.gradle`
206+
207+
### Issue: "Signing is not enabled. Skipping"
208+
**Solution**: This is expected when running locally without all environment variables set. In GitHub Actions with proper secrets, signing will be enabled.
209+
210+
### Issue: "Deploying is not enabled. Skipping"
211+
**Solution**: This is expected when running locally without `SONATYPE_PASSWORD` set. In GitHub Actions with proper secrets, deployment will be enabled.
212+
213+
## Security Best Practices
214+
215+
1. **Never commit `secret_key.asc`** - It's already in .gitignore
216+
2. **Never share Sonatype credentials** - Use environment variables or secrets
217+
3. **Back up your secret key securely** - Store in a password manager or encrypted storage
218+
4. **Keep revocation certificate** - Located at `~/.gnupg/openpgp-revocs.d/799A99750C819FB915ECDBBC144D9369E0328F75.rev`
219+
5. **Consider using a passphrase** - For production, add a passphrase and store it securely
220+
221+
## Key Revocation Certificate
222+
223+
The revocation certificate is stored at:
224+
```
225+
~/.gnupg/openpgp-revocs.d/799A99750C819FB915ECDBBC144D9369E0328F75.rev
226+
```
227+
228+
**IMPORTANT**: Back up this certificate in a secure location. If your private key is ever compromised, you'll need it to revoke the public key.
229+
230+
## JReleaser Configuration Details
231+
232+
### Authentication Method
233+
The current configuration uses **BEARER authentication** (Sonatype User Token), not BASIC authentication. This is configured in `jreleaser.yml` with `authorization: BEARER`.
234+
235+
### Sonatype User Token Authentication
236+
Sonatype Central uses User Token authentication for publishing:
237+
- **Token ID** (username): Identifier for the token
238+
- **Token** (password): The actual BEARER token used for authentication
239+
240+
For BEARER authentication with JReleaser:
241+
- Set `authorization: BEARER` in `jreleaser.yml`
242+
- Set `username: ${SONATYPE_USERNAME}` and `password: ${SONATYPE_PASSWORD}` in YAML
243+
- JReleaser ignores username for BEARER auth and only uses password as token
244+
245+
### JReleaser Environment Variables
246+
GitHub Actions workflow sets the following JReleaser-specific environment variables:
247+
- `JRELEASER_GITHUB_TOKEN`: GitHub token for creating releases
248+
- `JRELEASER_GITHUB_USERNAME`: GitHub username
249+
- `JRELEASER_GPG_PUBLIC_KEY`: GPG public key content
250+
- `JRELEASER_GPG_SECRET_KEY`: GPG secret key content
251+
- `JRELEASER_GPG_PASSPHRASE`: GPG passphrase (empty)
252+
- `JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_SONATYPE_USERNAME`: Sonatype User Token username (from `SONATYPE_USERNAME` secret)
253+
- `JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_SONATYPE_PASSWORD`: Sonatype User Token password (from `SONATYPE_PASSWORD` secret)
254+
255+
**Important**: JReleaser requires specific environment variable names with prefix `JRELEASER_`. The YAML file uses short names like `${SONATYPE_USERNAME}`, and JReleaser automatically looks for the corresponding full environment variable name.
256+
257+
### Signing Mode
258+
Using MEMORY mode (default) which treats `JRELEASER_GPG_SECRET_KEY` as the actual GPG secret key content (not base64-encoded or file path).
259+
260+
## Next Steps
261+
262+
1. Upload your GPG public key to key servers
263+
2. Generate and configure Sonatype User Token credentials
264+
3. Set environment variables for your development environment
265+
4. Configure GitHub Actions secrets and variables (if using CI/CD)
266+
5. Test the release process
267+
6. Create your first official release!
268+
269+
## GitHub Actions Release (Recommended)
270+
271+
For detailed setup and troubleshooting, see:
272+
- **[`.github/workflows/README.md`](.github/workflows/README.md)** - Workflow documentation
273+
- **[`development/jreleaser_fix.md`](jreleaser_fix.md)** - Recent fixes and configuration details
274+
275+
### Quick Setup for GitHub Actions
276+
277+
1. **Add variables and secrets** (Settings → Secrets and variables → Actions):
278+
279+
**Variables**:
280+
- `GPG_PUBLIC_KEY`: Copy the entire content of `public_key.asc` file (including -----BEGIN/END PGP PUBLIC KEY BLOCK-----)
281+
282+
**Secrets**:
283+
- `GPG_SECRET_KEY`: Copy the entire content of `secret_key.asc` file (including -----BEGIN/END PGP PRIVATE KEY BLOCK-----)
284+
- `SONATYPE_USERNAME`: Your Sonatype User Token username (Token ID)
285+
- `SONATYPE_PASSWORD`: Your Sonatype User Token password (the actual token)
286+
287+
2. **To release**:
288+
- Go to **Actions****Release - Publish to Sonatype Maven Central**
289+
- Click **Run workflow**
290+
- Select branch (usually `master`)
291+
- Click **Run workflow**
292+
- The workflow will use the version from `build.gradle` and deploy to Maven Central
293+
294+
3. **Verify workflow permissions**: Go to Settings → Actions → General and ensure:
295+
- ✅ Read and write permissions
296+
- ✅ Allow GitHub Actions to create and approve pull requests
297+
298+
## Additional Resources
299+
300+
- [JReleaser Documentation](https://jreleaser.org/)
301+
- [JReleaser Maven Central Reference](https://jreleaser.org/guide/latest/reference/deploy/maven/maven-central)
302+
- [Sonatype Central Publishing Guide](https://central.sonatype.org/publish/)
303+
- [Maven Central Portal](https://central.sonatype.com/)
304+
- [GPG Documentation](https://gnupg.org/documentation/)
305+
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
306+
307+
## Summary of Files
308+
309+
| File | Status | Purpose |
310+
|------|--------|---------|
311+
| `public_key.asc` | ✅ Safe to commit | GPG public key for signature verification |
312+
| `secret_key.asc` | ❌ Do NOT commit | GPG private key for signing artifacts |
313+
| `jreleaser.yml` | ✅ Safe to commit | JReleaser configuration |
314+
| `build.gradle` | ✅ Updated | Build configuration with JReleaser DSL |
315+
| `.gitignore` | ✅ Updated | Prevents accidental commits of secrets |
316+
| `.github/workflows/release.yml` | ✅ Safe to commit | GitHub Actions workflow for releases |
317+
318+
## Workflow Flow
319+
320+
The GitHub Actions release workflow follows these steps:
321+
322+
1. **Run tests** - Ensure code quality
323+
2. **Publish Maven artifacts** - Creates JARs and publishes to `build/staging-deploy`
324+
3. **Validate JReleaser configuration** - Checks configuration before attempting release
325+
4. **Full release with JReleaser**:
326+
- Sign artifacts with GPG
327+
- Deploy to Sonatype Maven Central
328+
- Create GitHub release
329+
330+
---
331+
332+
**Last Updated**: 2026-02-10
333+
**Key ID**: 799A99750C819FB915ECDBBC144D9369E0328F75
334+
**Authentication**: BEARER (Sonatype User Token)

development/gpg_key_extraction.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Extracting GPG Keys for GitHub Secrets
2+
3+
## Commands to Extract GPG Keys
4+
5+
### 1. Export Secret Key
6+
```bash
7+
# List your GPG keys to find your key ID
8+
gpg --list-secret-keys
9+
10+
# Export the secret key (replace 799A99750C819FB915ECDBBC144D9369E0328F75 with your key ID)
11+
gpg --export-secret-keys --armor 799A99750C819FB915ECDBBC144D9369E0328F75 > secret_key.asc
12+
13+
# Copy the content (everything between -----BEGIN PGP PRIVATE KEY BLOCK----- and -----END PGP PRIVATE KEY BLOCK-----)
14+
# This is your JRELEASER_GPG_SECRET_KEY value
15+
cat secret_key.asc
16+
```
17+
18+
### 2. Export Public Key
19+
```bash
20+
# Export the public key (replace 799A99750C819FB915ECDBBC144D9369E0328F75 with your key ID)
21+
gpg --export --armor 799A99750C819FB915ECDBBC144D9369E0328F75 > public_key.asc
22+
23+
# Copy the content (everything between -----BEGIN PGP PUBLIC KEY BLOCK----- and -----END PGP PUBLIC KEY BLOCK-----)
24+
# This is your JRELEASER_GPG_PUBLIC_KEY value
25+
cat public_key.asc
26+
```
27+
28+
### 3. Quick One-Liner to Copy to Clipboard (macOS)
29+
```bash
30+
# Copy secret key to clipboard
31+
gpg --export-secret-keys --armor 799A99750C819FB915ECDBBC144D9369E0328F75 | pbcopy
32+
33+
# Copy public key to clipboard
34+
gpg --export --armor 799A99750C819FB915ECDBBC144D9369E0328F75 | pbcopy
35+
```
36+
37+
## Setting GitHub Secrets
38+
39+
1. Go to your GitHub repository
40+
2. Navigate to **Settings****Secrets and variables****Actions**
41+
3. Click **New repository secret**
42+
4. For `JRELEASER_GPG_PUBLIC_KEY`:
43+
- Name: `GPG_PUBLIC_KEY`
44+
- Value: Paste the entire content of `public_key.asc` (including the BEGIN/END lines)
45+
5. For `JRELEASER_GPG_SECRET_KEY`:
46+
- Name: `GPG_SECRET_KEY`
47+
- Value: Paste the entire content of `secret_key.asc` (including the BEGIN/END lines)
48+
49+
## Notes
50+
51+
- **MEMORY mode** (which we're using) expects the actual key content, NOT base64-encoded
52+
- The key content includes the header and footer lines (-----BEGIN/END PGP KEY BLOCK-----)
53+
- Copy the entire file content, not just the key itself
54+
- If your GPG key has a passphrase, set `JRELEASER_GPG_PASSPHRASE` to that passphrase instead of empty string

0 commit comments

Comments
 (0)