diff --git a/.cursorignore b/.cursorignore index 1761c01..b8e022b 100644 --- a/.cursorignore +++ b/.cursorignore @@ -1 +1,2 @@ -.envrc \ No newline at end of file +.envrc +gpg-secrets/* diff --git a/.gitignore b/.gitignore index 6995804..9862a22 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ volumes + +# GPG secrets directory - only track .gpg files +gpg-secrets/* +!gpg-secrets/*.gpg + +# GPG key IDs file +.localmart-github-key-ids diff --git a/Makefile b/Makefile index 0addab2..15bcd98 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ clean: clean-data: rm -rf ./volumes + rm -rf frontend/.next deploy-frontend: cd frontend && fly deploy @@ -42,3 +43,17 @@ deploy-search-prod: cd search && fly deploy --config fly.prod.toml deploy-all-prod: deploy-frontend-prod deploy-backend-prod deploy-pocketbase-prod deploy-meilisearch-prod deploy-search-prod + +# GPG key management +GITHUB_USERS := azlyth + +import-gpg-keys: + @GITHUB_USERS="$(GITHUB_USERS)" ./utils/gpg_manager.sh import-keys + +encrypt-file: + @if [ -z "$(FILE)" ]; then echo "Usage: make encrypt-file FILE=path/to/file"; exit 1; fi + @GITHUB_USERS="$(GITHUB_USERS)" ./utils/gpg_manager.sh encrypt "$(FILE)" + +decrypt-file: + @if [ -z "$(FILE)" ]; then echo "Usage: make decrypt-file FILE=gpg-secrets/file.gpg"; exit 1; fi + @./utils/gpg_manager.sh decrypt "$(FILE)" diff --git a/README.md b/README.md index 432ca91..3fb0175 100644 --- a/README.md +++ b/README.md @@ -2,32 +2,81 @@ localmart is a platform for local businesses to sell their products and services to local customers. -## Prerequisites -Docker, Docker Compose +## Development -## Usage +### Prerequisites + +For development: +- Docker +- Docker Compose + +### Usage To start the next.js frontend, the Python backend, and the database, run: ```bash -docker compose --build up +make ``` -To clear the database +To clear the database and the frontend next.js cache: ```bash -rm -rf ./volumes +make clean-data ``` -## Debugging Tips -If the app is not loading porperly on your machine, try clear the cache and restart. +### Debugging Tips +1. If the app is not loading porperly on your machine, try: + - clearing the data with `make clean-data` + - restarting with `make` -To rebuild the backend: -```bash -make -``` -To rebuild the frontend, if NextJS is giving errors: -```bash -cd frontend/ -rm -rf .next -make -``` +## Secret Management + +We use GPG encryption to securely store sensitive files. The encrypted files are stored in the `gpg-secrets/` directory and can only be decrypted by authorized team members. + +### Prerequisites + +1. Install GPG on your system: + ```bash + # macOS + brew install gnupg + + # Ubuntu/Debian + sudo apt-get install gnupg + ``` + +2. Have your own GPG key pair and upload the public key to your GitHub profile + ([GitHub guide on adding GPG keys](https://docs.github.com/en/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account)) + +### Managing Secrets + +The following commands are available: + +1. Import team members' GPG keys: + ```bash + # Import team members' GPG keys + make import-gpg-keys + ``` + +2. Encrypt a file: + ```bash + # The file will be encrypted and stored in gpg-secrets/ + make encrypt-file FILE=path/to/your/file + + # Example: Encrypt staging environment variables + make encrypt-file FILE=gpg-secrets/staging.env + ``` + +3. Decrypt a file: + ```bash + # Only works with files in the gpg-secrets/ directory + make decrypt-file FILE=gpg-secrets/your-file.gpg + + # Example: Decrypt staging environment variables + make decrypt-file FILE=gpg-secrets/staging.env.gpg + ``` + +### Notes + +- Only files in the `gpg-secrets/` directory with the `.gpg` extension are tracked in Git +- Decrypted files are automatically placed in the `gpg-secrets/` directory +- You must have your private key to decrypt files +- The list of team members is maintained in the Makefile's `GITHUB_USERS` variable \ No newline at end of file diff --git a/gpg-secrets/staging.env.gpg b/gpg-secrets/staging.env.gpg new file mode 100644 index 0000000..4dd91f2 Binary files /dev/null and b/gpg-secrets/staging.env.gpg differ diff --git a/utils/gpg_manager.sh b/utils/gpg_manager.sh new file mode 100755 index 0000000..1cd2965 --- /dev/null +++ b/utils/gpg_manager.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +# Function to display usage information +show_usage() { + echo "Usage:" + echo " $0 import-keys # Import GPG keys from GITHUB_USERS env var" + echo " $0 encrypt FILE # Encrypt a file using imported GPG keys" + echo " $0 decrypt FILE # Decrypt a GPG encrypted file" + exit 1 +} + +# Function to get key ID from GitHub username +get_key_id() { + local username=$1 + local key_data + key_data=$(curl -s "https://github.com/$username.gpg") + if [ -n "$key_data" ]; then + echo "$key_data" | gpg --batch --import 2>/dev/null + # Get the key ID of the most recently imported key + echo "$key_data" | gpg --batch --with-colons --show-keys 2>/dev/null | grep '^pub' | cut -d: -f5 + fi +} + +# Function to import GPG keys from GitHub +import_keys() { + if [ -z "$GITHUB_USERS" ]; then + echo "Error: GITHUB_USERS environment variable not set" + echo "Please set GITHUB_USERS as a comma-separated list of GitHub usernames" + exit 1 + fi + + # Convert comma-separated list to array + IFS=',' read -ra USERS <<< "$GITHUB_USERS" + + # Create a temporary file to store key IDs + key_ids_file=$(mktemp) + + for username in "${USERS[@]}"; do + # Remove any leading/trailing whitespace and @ symbol + username=$(echo "$username" | tr -d '[:space:]' | sed 's/^@//') + if [ -n "$username" ]; then + echo "Importing GPG key for GitHub user: $username" + key_id=$(get_key_id "$username") + if [ -n "$key_id" ]; then + echo "$key_id" >> "$key_ids_file" + else + echo "Warning: Could not import key for user $username" + fi + fi + done + + # Store the key IDs in the repository + mv "$key_ids_file" .localmart-github-key-ids + echo "Imported keys have been stored in .localmart-github-key-ids" +} + +# Function to encrypt a file +encrypt_file() { + local input_file=$1 + if [ ! -f "$input_file" ]; then + echo "Error: Input file not found: $input_file" + exit 1 + fi + + # Check if we have stored key IDs + if [ ! -f .localmart-github-key-ids ]; then + echo "Error: No GitHub keys found. Please run import-keys first." + exit 1 + fi + + # Build recipient arguments from stored key IDs + recipient_args="" + while IFS= read -r key_id; do + if [ -n "$key_id" ]; then + recipient_args="$recipient_args -r $key_id" + fi + done < .localmart-github-key-ids + + if [ -z "$recipient_args" ]; then + echo "Error: No valid recipient keys found" + exit 1 + fi + + # Create output filename + mkdir -p gpg-secrets + output_file="gpg-secrets/$(basename "$input_file").gpg" + + # Encrypt the file for all recipients + gpg --batch --yes --trust-model always $recipient_args --encrypt --output "$output_file" "$input_file" 2>/dev/null + echo "File encrypted: $output_file" +} + +# Function to decrypt a file +decrypt_file() { + local input_file=$1 + if [ ! -f "$input_file" ]; then + echo "Error: Input file not found: $input_file" + exit 1 + fi + + if [[ ! "$input_file" =~ \.gpg$ ]]; then + echo "Error: Input file must have .gpg extension" + exit 1 + fi + + if [[ ! "$input_file" =~ ^gpg-secrets/ ]]; then + echo "Error: Can only decrypt files from the gpg-secrets directory" + exit 1 + fi + + # Create output filename (remove .gpg extension) + output_file="${input_file%.gpg}" + + # Decrypt the file + gpg --batch --yes --decrypt --output "$output_file" "$input_file" 2>/dev/null + echo "File decrypted: $output_file" +} + +# Main script logic +case "$1" in + "import-keys") + import_keys + ;; + "encrypt") + if [ -z "$2" ]; then + show_usage + fi + encrypt_file "$2" + ;; + "decrypt") + if [ -z "$2" ]; then + show_usage + fi + decrypt_file "$2" + ;; + *) + show_usage + ;; +esac \ No newline at end of file