From 30d477fa4b99274342f15a92338d86898c50a001 Mon Sep 17 00:00:00 2001 From: Thang Le Quoc Date: Wed, 11 Feb 2026 09:10:04 +0700 Subject: [PATCH] feat: Add development document --- development/RELEASE_SETUP.md | 334 ++++++++++++++++++++++++++++++ development/gpg_key_extraction.md | 54 +++++ 2 files changed, 388 insertions(+) create mode 100644 development/RELEASE_SETUP.md create mode 100644 development/gpg_key_extraction.md diff --git a/development/RELEASE_SETUP.md b/development/RELEASE_SETUP.md new file mode 100644 index 0000000..db26984 --- /dev/null +++ b/development/RELEASE_SETUP.md @@ -0,0 +1,334 @@ +# Release Setup Guide for Timer Ninja + +This document explains how to set up release process for publishing Timer Ninja to Maven Central (Sonatype) using JReleaser. + +## Recovery from Previous Mistake + +During a previous session, following critical files were accidentally deleted: +- `public_key.asc` - GPG public key +- `secret_key.asc` - GPG secret key (containing passphrase) +- Sonatype credentials from build.gradle + +This guide documents recovery process and provides instructions for future reference. + +## What Has Been Done + +### 1. Generated New GPG Key Pair +A new GPG key pair has been generated with following details: +- **Key ID**: `799A99750C819FB915ECDBBC144D9369E0328F75` +- **Type**: RSA 4096-bit +- **Owner**: Thang Le Quoc +- **Expiration**: None (permanent) +- **Protection**: No passphrase (for automated CI/CD) + +**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. + +### 2. Exported Keys +- `public_key.asc` - Exported and stored in project root (safe to commit) +- `secret_key.asc` - Exported and stored in project root (DO NOT commit) + +### 3. Updated .gitignore +Added rules to prevent accidental commits of sensitive files: +``` +### Security - Keys & Credentials ### +secret_key.asc +*.asc +!public_key.asc +``` + +### 4. Created JReleaser Configuration +Created `jreleaser.yml` with proper configuration for: +- Maven Central deployment +- Sonatype integration +- GPG signing +- Environment variable-based credentials + +### 5. Updated build.gradle +Added JReleaser DSL configuration to enable signing and deployment features. No hardcoded credentials. + +## Required Setup Steps + +### 1. Publish Your GPG Public Key to a Key Server + +Before you can publish to Maven Central, your public key must be available on a public key server: + +```bash +# Upload to multiple key servers +gpg --keyserver keyserver.ubuntu.com --send-keys 799A99750C819FB915ECDBBC144D9369E0328F75 +gpg --keyserver pgp.mit.edu --send-keys 799A99750C819FB915ECDBBC144D9369E0328F75 +gpg --keyserver keys.openpgp.org --send-keys 799A99750C819FB915ECDBBC144D9369E0328F75 +``` + +**Important**: The key must be propagated to at least one key server before Maven Central will accept signed artifacts. + +### 2. Set Up Sonatype User Token + +You need to obtain Sonatype User Token for publishing to Maven Central: + +1. Go to https://central.sonatype.com/usertoken +2. Sign in with your Sonatype account (or create one) +3. Click "Generate User Token" +4. Save the generated credentials: + - **Token ID** (username): This is just an identifier + - **Token** (password): This is the actual BEARER token that will be used for authentication + +**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. + +### 3. Configure Environment Variables + +#### For Local Development: +Set the following environment variables before running JReleaser: + +```bash +export SONATYPE_USERNAME="your-sonatype-user-token-username" +export SONATYPE_PASSWORD="your-sonatype-user-token-password" +export GPG_PASSPHRASE="" # Empty since key has no passphrase +``` + +You can add these to your shell profile (~/.zshrc or ~/.bashrc): +```bash +# Add to ~/.zshrc or ~/.bashrc +export SONATYPE_USERNAME="your-username" +export SONATYPE_PASSWORD="your-password" +export GPG_PASSPHRASE="" +``` + +#### For GitHub Actions: +Add these as secrets and variables in your repository settings: + +**Variables** (not sensitive - visible to repository members): +- `GPG_PUBLIC_KEY` - The full content of `public_key.asc` file (NOT base64-encoded) + +**Secrets** (sensitive - hidden): +- `GPG_SECRET_KEY` - The full content of `secret_key.asc` file (NOT base64-encoded) +- `SONATYPE_USERNAME` - Your Sonatype User Token username (Token ID) +- `SONATYPE_PASSWORD` - Your Sonatype User Token password (the actual token) + +### 4. Verify GPG Key Configuration + +Ensure that GPG keys are properly configured: + +```bash +# List public keys +gpg --list-keys + +# List secret keys +gpg --list-secret-keys + +# Verify key is exported +cat public_key.asc +cat secret_key.asc +``` + +## Release Process + +### Option 1: Local Release + +1. Set environment variables: +```bash +export SONATYPE_USERNAME="your-username" +export SONATYPE_PASSWORD="your-password" +export GPG_PASSPHRASE="" +``` + +2. Build and publish: +```bash +./gradlew clean build +./gradlew publishToMavenLocal +``` + +3. Deploy to Maven Central: +```bash +./gradlew jreleaserFullRelease +``` + +### Option 2: GitHub Actions (Recommended) + +1. Ensure GitHub Actions are configured in `.github/workflows/` + +2. Add variables and secrets to repository (Settings → Secrets and variables → Actions): + +**Variables**: +- `GPG_PUBLIC_KEY` - Copy the entire content of `public_key.asc` file (including -----BEGIN/END PGP PUBLIC KEY BLOCK-----) + +**Secrets**: +- `GPG_SECRET_KEY` - Copy the entire content of `secret_key.asc` file (including -----BEGIN/END PGP PRIVATE KEY BLOCK-----) +- `SONATYPE_USERNAME` - Your Sonatype User Token username (Token ID) +- `SONATYPE_PASSWORD` - Your Sonatype User Token password (the actual token) + +3. Trigger the workflow manually: + - Go to **Actions** → **Release - Publish to Sonatype Maven Central** + - Click **Run workflow** + - Select branch (usually `master`) + - Click **Run workflow** + +## Publishing Your Public Key + +If you haven't already published your public key to a key server, do this now: + +```bash +# Upload to Ubuntu key server (most commonly used) +gpg --keyserver keyserver.ubuntu.com --send-keys 799A99750C819FB915ECDBBC144D9369E0328F75 + +# Verify it's available +gpg --keyserver keyserver.ubuntu.com --recv-keys 799A99750C819FB915ECDBBC144D9369E0328F75 +``` + +Wait a few minutes for key to propagate across servers before attempting to publish. + +## Troubleshooting + +### Issue: "Key not found on key server" +**Solution**: Upload your public key to a key server and wait for propagation (can take 5-30 minutes) + +### Issue: "GPG signing failed" +**Solution**: Ensure `secret_key.asc` exists in project root and is accessible + +### Issue: "Sonatype authentication failed (401 Unauthorized)" +**Solution**: +- Verify you're using User Token credentials, not your regular Sonatype login +- Ensure BOTH `SONATYPE_USERNAME` and `SONATYPE_PASSWORD` are set in GitHub secrets +- Check that `jreleaser.yml` has `authorization: BEARER` configured +- Verify the token hasn't expired by regenerating it at https://central.sonatype.com/usertoken +- Ensure environment variables in workflow use correct JRELEASER-specific names: `JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_SONATYPE_USERNAME` and `JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_SONATYPE_PASSWORD` + +### Issue: "Artifacts rejected by Maven Central" +**Solution**: +- Verify your GPG public key is on a key server +- Check that pom.xml has all required metadata +- Ensure all required files (javadoc jar, sources jar) are included + +### Issue: "Version mismatch" +**Solution**: +- Ensure that version in `build.gradle` is one you want to release +- Commit and push version change before running workflow +- The workflow uses version directly from `build.gradle` + +### Issue: "Signing is not enabled. Skipping" +**Solution**: This is expected when running locally without all environment variables set. In GitHub Actions with proper secrets, signing will be enabled. + +### Issue: "Deploying is not enabled. Skipping" +**Solution**: This is expected when running locally without `SONATYPE_PASSWORD` set. In GitHub Actions with proper secrets, deployment will be enabled. + +## Security Best Practices + +1. **Never commit `secret_key.asc`** - It's already in .gitignore +2. **Never share Sonatype credentials** - Use environment variables or secrets +3. **Back up your secret key securely** - Store in a password manager or encrypted storage +4. **Keep revocation certificate** - Located at `~/.gnupg/openpgp-revocs.d/799A99750C819FB915ECDBBC144D9369E0328F75.rev` +5. **Consider using a passphrase** - For production, add a passphrase and store it securely + +## Key Revocation Certificate + +The revocation certificate is stored at: +``` +~/.gnupg/openpgp-revocs.d/799A99750C819FB915ECDBBC144D9369E0328F75.rev +``` + +**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. + +## JReleaser Configuration Details + +### Authentication Method +The current configuration uses **BEARER authentication** (Sonatype User Token), not BASIC authentication. This is configured in `jreleaser.yml` with `authorization: BEARER`. + +### Sonatype User Token Authentication +Sonatype Central uses User Token authentication for publishing: +- **Token ID** (username): Identifier for the token +- **Token** (password): The actual BEARER token used for authentication + +For BEARER authentication with JReleaser: +- Set `authorization: BEARER` in `jreleaser.yml` +- Set `username: ${SONATYPE_USERNAME}` and `password: ${SONATYPE_PASSWORD}` in YAML +- JReleaser ignores username for BEARER auth and only uses password as token + +### JReleaser Environment Variables +GitHub Actions workflow sets the following JReleaser-specific environment variables: +- `JRELEASER_GITHUB_TOKEN`: GitHub token for creating releases +- `JRELEASER_GITHUB_USERNAME`: GitHub username +- `JRELEASER_GPG_PUBLIC_KEY`: GPG public key content +- `JRELEASER_GPG_SECRET_KEY`: GPG secret key content +- `JRELEASER_GPG_PASSPHRASE`: GPG passphrase (empty) +- `JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_SONATYPE_USERNAME`: Sonatype User Token username (from `SONATYPE_USERNAME` secret) +- `JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_SONATYPE_PASSWORD`: Sonatype User Token password (from `SONATYPE_PASSWORD` secret) + +**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. + +### Signing Mode +Using MEMORY mode (default) which treats `JRELEASER_GPG_SECRET_KEY` as the actual GPG secret key content (not base64-encoded or file path). + +## Next Steps + +1. Upload your GPG public key to key servers +2. Generate and configure Sonatype User Token credentials +3. Set environment variables for your development environment +4. Configure GitHub Actions secrets and variables (if using CI/CD) +5. Test the release process +6. Create your first official release! + +## GitHub Actions Release (Recommended) + +For detailed setup and troubleshooting, see: +- **[`.github/workflows/README.md`](.github/workflows/README.md)** - Workflow documentation +- **[`development/jreleaser_fix.md`](jreleaser_fix.md)** - Recent fixes and configuration details + +### Quick Setup for GitHub Actions + +1. **Add variables and secrets** (Settings → Secrets and variables → Actions): + + **Variables**: + - `GPG_PUBLIC_KEY`: Copy the entire content of `public_key.asc` file (including -----BEGIN/END PGP PUBLIC KEY BLOCK-----) + + **Secrets**: + - `GPG_SECRET_KEY`: Copy the entire content of `secret_key.asc` file (including -----BEGIN/END PGP PRIVATE KEY BLOCK-----) + - `SONATYPE_USERNAME`: Your Sonatype User Token username (Token ID) + - `SONATYPE_PASSWORD`: Your Sonatype User Token password (the actual token) + +2. **To release**: + - Go to **Actions** → **Release - Publish to Sonatype Maven Central** + - Click **Run workflow** + - Select branch (usually `master`) + - Click **Run workflow** + - The workflow will use the version from `build.gradle` and deploy to Maven Central + +3. **Verify workflow permissions**: Go to Settings → Actions → General and ensure: + - ✅ Read and write permissions + - ✅ Allow GitHub Actions to create and approve pull requests + +## Additional Resources + +- [JReleaser Documentation](https://jreleaser.org/) +- [JReleaser Maven Central Reference](https://jreleaser.org/guide/latest/reference/deploy/maven/maven-central) +- [Sonatype Central Publishing Guide](https://central.sonatype.org/publish/) +- [Maven Central Portal](https://central.sonatype.com/) +- [GPG Documentation](https://gnupg.org/documentation/) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) + +## Summary of Files + +| File | Status | Purpose | +|------|--------|---------| +| `public_key.asc` | ✅ Safe to commit | GPG public key for signature verification | +| `secret_key.asc` | ❌ Do NOT commit | GPG private key for signing artifacts | +| `jreleaser.yml` | ✅ Safe to commit | JReleaser configuration | +| `build.gradle` | ✅ Updated | Build configuration with JReleaser DSL | +| `.gitignore` | ✅ Updated | Prevents accidental commits of secrets | +| `.github/workflows/release.yml` | ✅ Safe to commit | GitHub Actions workflow for releases | + +## Workflow Flow + +The GitHub Actions release workflow follows these steps: + +1. **Run tests** - Ensure code quality +2. **Publish Maven artifacts** - Creates JARs and publishes to `build/staging-deploy` +3. **Validate JReleaser configuration** - Checks configuration before attempting release +4. **Full release with JReleaser**: + - Sign artifacts with GPG + - Deploy to Sonatype Maven Central + - Create GitHub release + +--- + +**Last Updated**: 2026-02-10 +**Key ID**: 799A99750C819FB915ECDBBC144D9369E0328F75 +**Authentication**: BEARER (Sonatype User Token) \ No newline at end of file diff --git a/development/gpg_key_extraction.md b/development/gpg_key_extraction.md new file mode 100644 index 0000000..621e660 --- /dev/null +++ b/development/gpg_key_extraction.md @@ -0,0 +1,54 @@ +# Extracting GPG Keys for GitHub Secrets + +## Commands to Extract GPG Keys + +### 1. Export Secret Key +```bash +# List your GPG keys to find your key ID +gpg --list-secret-keys + +# Export the secret key (replace 799A99750C819FB915ECDBBC144D9369E0328F75 with your key ID) +gpg --export-secret-keys --armor 799A99750C819FB915ECDBBC144D9369E0328F75 > secret_key.asc + +# Copy the content (everything between -----BEGIN PGP PRIVATE KEY BLOCK----- and -----END PGP PRIVATE KEY BLOCK-----) +# This is your JRELEASER_GPG_SECRET_KEY value +cat secret_key.asc +``` + +### 2. Export Public Key +```bash +# Export the public key (replace 799A99750C819FB915ECDBBC144D9369E0328F75 with your key ID) +gpg --export --armor 799A99750C819FB915ECDBBC144D9369E0328F75 > public_key.asc + +# Copy the content (everything between -----BEGIN PGP PUBLIC KEY BLOCK----- and -----END PGP PUBLIC KEY BLOCK-----) +# This is your JRELEASER_GPG_PUBLIC_KEY value +cat public_key.asc +``` + +### 3. Quick One-Liner to Copy to Clipboard (macOS) +```bash +# Copy secret key to clipboard +gpg --export-secret-keys --armor 799A99750C819FB915ECDBBC144D9369E0328F75 | pbcopy + +# Copy public key to clipboard +gpg --export --armor 799A99750C819FB915ECDBBC144D9369E0328F75 | pbcopy +``` + +## Setting GitHub Secrets + +1. Go to your GitHub repository +2. Navigate to **Settings** → **Secrets and variables** → **Actions** +3. Click **New repository secret** +4. For `JRELEASER_GPG_PUBLIC_KEY`: + - Name: `GPG_PUBLIC_KEY` + - Value: Paste the entire content of `public_key.asc` (including the BEGIN/END lines) +5. For `JRELEASER_GPG_SECRET_KEY`: + - Name: `GPG_SECRET_KEY` + - Value: Paste the entire content of `secret_key.asc` (including the BEGIN/END lines) + +## Notes + +- **MEMORY mode** (which we're using) expects the actual key content, NOT base64-encoded +- The key content includes the header and footer lines (-----BEGIN/END PGP KEY BLOCK-----) +- Copy the entire file content, not just the key itself +- If your GPG key has a passphrase, set `JRELEASER_GPG_PASSPHRASE` to that passphrase instead of empty string \ No newline at end of file