{"id":39700401,"url":"https://github.com/mchamoudadev/headshotpro-frontend","last_synced_at":"2026-01-18T10:23:34.886Z","repository":{"id":331264662,"uuid":"1118457026","full_name":"mchamoudadev/headshotpro-frontend","owner":"mchamoudadev","description":null,"archived":false,"fork":false,"pushed_at":"2026-01-01T12:42:56.000Z","size":2269,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-05T04:55:45.789Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mchamoudadev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-17T19:33:39.000Z","updated_at":"2026-01-01T12:42:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mchamoudadev/headshotpro-frontend","commit_stats":null,"previous_names":["mchamoudadev/headshotpro-frontend"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/mchamoudadev/headshotpro-frontend","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mchamoudadev%2Fheadshotpro-frontend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mchamoudadev%2Fheadshotpro-frontend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mchamoudadev%2Fheadshotpro-frontend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mchamoudadev%2Fheadshotpro-frontend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mchamoudadev","download_url":"https://codeload.github.com/mchamoudadev/headshotpro-frontend/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mchamoudadev%2Fheadshotpro-frontend/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28534339,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T10:13:46.436Z","status":"ssl_error","status_checked_at":"2026-01-18T10:13:11.045Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-18T10:23:33.909Z","updated_at":"2026-01-18T10:23:34.854Z","avatar_url":"https://github.com/mchamoudadev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🚀 Complete Deployment Guide for Full Stack Dugsiiye Mentorship\n\nWelcome! 👋 This friendly guide will walk you through deploying a fullstack application (Next.js + Bun backend) using **GitHub Actions** for automatic deployment. \n\nDon't worry if you're new to deployment - we'll explain everything step by step, and you can always come back to this guide whenever you need help!\n\n---\n\n## 📋 What You'll Deploy\n\n- **Frontend**: Next.js app on `yourdomain.com` (port 3000)\n- **Backend**: Bun API on `api.yourdomain.com` (port 8000)\n- **Server**: Ubuntu VPS (22.04 or 24.04)\n- **Process Manager**: PM2 (keeps apps running)\n- **Web Server**: Nginx (handles traffic and SSL)\n- **SSL**: Let's Encrypt (free HTTPS certificates)\n- **Database**: MongoDB Atlas (cloud database)\n- **Deployment**: GitHub Actions (automatic deployment)\n\n---\n\n## 🎯 Deployment Strategy Overview\n\n**Key Concept**: Your `.env` files (containing API keys and secrets) will **NEVER** be in Git or GitHub.\n\n**How it works**:\n1. Your code lives in GitHub (public or private)\n2. Your secrets live in **GitHub Secrets** (encrypted storage)\n3. When you push code → GitHub Actions automatically:\n   - Builds your app\n   - Connects to your VPS via SSH\n   - Pulls the latest code\n   - **Creates the `.env` file** on your VPS from GitHub Secrets\n   - Restarts your app\n4. Your app reads the `.env` file from the VPS and uses the secrets\n\n---\n\n\n## Part 1: VPS Server Setup (One-Time Setup)\n\n### Step 1: Get Your VPS Ready\n\nWhen you buy a VPS (from providers like DigitalOcean, Hetzner, Linode,Hostinger, etc.), you'll receive:\n- IP address (example: `194.238.22.106`)\n- Root password or SSH key\n\n**Login to your server**:\n```bash\nssh root@YOUR_SERVER_IP\n```\n\n---\n\n### Step 2: Create a Deploy User (Security Best Practice)\n\nNever use root for running apps. Create a dedicated user:\n\n```bash\n# Create new user\nadduser deploy\n\n# Give it sudo privileges\nusermod -aG sudo deploy\n\n# Switch to the new user\nsu - deploy\n```\n\n---\n\n### Step 3: Understanding SSH Keys 🔑\n\nBefore we set up SSH keys, let's understand what they are and why we need them.\n\n**What are SSH Keys?**\n- SSH (Secure Shell) keys are like a special pair of keys that let you securely connect to your server\n- They come in pairs: a **private key** (keep this secret!) and a **public key** (can be shared)\n- Think of it like a lock and key: the public key is the lock on your server, the private key is the key you keep\n\n**🔐 Critical Concept:**\n\u003e **SSH keys are created on YOUR COMPUTER, but stored on the SERVER per user.**\n\u003e \n\u003e This means:\n\u003e - Private key stays on your local machine (NEVER share it!)\n\u003e - Public key goes to the server (~/.ssh/authorized_keys)\n\u003e - Each server user can have different authorized keys\n\n**Why do we need them?**\n- More secure than passwords (can't be guessed or brute-forced)\n- Required for GitHub Actions to automatically deploy your code\n- Industry standard for server access\n\nNow let's set them up! Choose your operating system below:\n\n---\n\n### Step 4: SSH Key Setup for Mac 🍎\n\n**Don't worry, this is easier than it sounds!** Follow along step by step.\n\n#### Step 4a: Open Terminal\n\n1. Press `Cmd + Space` to open Spotlight\n2. Type \"Terminal\" and press Enter\n3. You should see a window with a command prompt\n\n#### Step 4b: Generate Your SSH Key\n\nCopy and paste this command into Terminal (press Enter after):\n\n```bash\nssh-keygen -t ed25519 -C \"github-actions-deploy\" -f ~/.ssh/github_deploy\n```\n\n**Understanding this command (Flag by Flag):**\n\n- **`ssh-keygen`** = The command to generate SSH keys\n- **`-t ed25519`** = Specifies the type of key to create\n  - `-t` means \"type\"\n  - `ed25519` is a modern, fast, and very secure encryption algorithm\n  - Alternative: `-t rsa` (older, still secure but slower)\n- **`-C \"github-actions-deploy\"`** = Adds a comment/label to your key\n  - `-C` means \"comment\"\n  - Helps you identify what this key is used for\n  - You can use any text here (your email, project name, etc.)\n- **`-f ~/.ssh/github_deploy`** = Specifies the filename and location\n  - `-f` means \"filename\"\n  - `~/.ssh/` = Your user's SSH directory (home folder → .ssh folder)\n  - `github_deploy` = The name of your key file\n  - This creates TWO files: `github_deploy` (private) and `github_deploy.pub` (public)\n\n**You'll be asked:**\n1. **\"Enter passphrase\"** → Press Enter (leave it empty for now - this makes automation easier)\n2. **\"Enter same passphrase again\"** → Press Enter again\n\n**Success!** ✅ You'll see:\n```\nYour identification has been saved in /Users/yourname/.ssh/github_deploy\nYour public key has been saved in /Users/yourname/.ssh/github_deploy.pub\n```\n\n#### Step 4c: Copy Your Public Key to the Server\n\nNow we need to put your public key on the server. Run this command (replace `YOUR_SERVER_IP` with your actual server IP):\n\n**⚠️ IMPORTANT: Run this command on your LOCAL Mac, NOT on the VPS!**\n\n```bash\nssh-copy-id -i ~/.ssh/github_deploy.pub deploy@YOUR_SERVER_IP\n```\n\n**Understanding this command:**\n\n- **`ssh-copy-id`** = Command that copies your SSH public key to a remote server\n- **`-i ~/.ssh/github_deploy.pub`** = Specifies which identity (key) file to copy\n  - `-i` means \"identity file\"\n  - Points to your PUBLIC key (the one ending in `.pub`)\n- **`deploy@YOUR_SERVER_IP`** = Where to copy the key\n  - `deploy` = The username on the server\n  - `@` = Separator between user and host\n  - `YOUR_SERVER_IP` = Your server's IP address (e.g., 194.238.22.106)\n\n**What this command does automatically:**\n1. Connects to your server\n2. Creates `~/.ssh` directory if needed\n3. Sets correct permissions (`chmod 700 ~/.ssh`)\n4. Appends your public key to `~/.ssh/authorized_keys`\n5. Sets correct permissions on authorized_keys file (`chmod 600`)\n\n**Example:**\n```bash\nssh-copy-id -i ~/.ssh/github_deploy.pub deploy@194.238.22.106\n```\n\n**You'll be asked:**\n- **\"Are you sure you want to continue connecting?\"** → Type `yes` and press Enter\n  - This is the SSH fingerprint verification (first-time only)\n- **Password for deploy user** → Enter the password you created in Step 2\n\n**Understanding the password prompt:**\n\u003e You will be asked for the `deploy` user password **ONCE ONLY**.\n\u003e This is required so the server can accept your SSH key.\n\u003e After this step, SSH login will **NOT ask for a password again** - it will use your key instead!\n\n**Success!** ✅ You'll see: \"Number of key(s) added: 1\"\n\n#### Step 4d: Test Your SSH Connection\n\nLet's make sure it works! Run this command:\n\n```bash\nssh -i ~/.ssh/github_deploy deploy@YOUR_SERVER_IP\n```\n\n**Understanding this test:**\n- **`-i ~/.ssh/github_deploy`** = Use this specific identity (private key) file\n- If successful: You login **WITHOUT password prompt** 🎉\n- This proves your SSH key is working correctly!\n\n**Example:**\n```bash\nssh -i ~/.ssh/github_deploy deploy@194.238.22.106\n```\n\n**Success looks like:**\n```\nWelcome to Ubuntu 22.04.3 LTS...\ndeploy@yourserver:~$\n```\n**No password prompt = SUCCESS!** ✅\n\n**What should happen:**\n- You should login **without** being asked for a password\n- You should see your server's welcome message\n- Your terminal prompt should change to show you're on the server (e.g., `deploy@yourserver:~$`)\n\n**If it asks for password:**\n- Your key setup didn't work\n- The `ssh-copy-id` command should have set permissions automatically\n- Check the troubleshooting section\n\n**Success!** 🎉 Type `exit` to go back to your local computer.\n\n#### Step 4e: Copy PRIVATE KEY (GitHub Secrets ONLY) 🔐\n\n**🚨 CRITICAL SECURITY WARNING - READ CAREFULLY! 🚨**\n\nThis command shows your **PRIVATE KEY** - treat it like your bank password!\n\n```bash\ncat ~/.ssh/github_deploy\n```\n\nYou'll see something starting with `-----BEGIN OPENSSH PRIVATE KEY-----`\n\n**⚠️ PRIVATE KEY SECURITY RULES - NEVER BREAK THESE:**\n\n1. ✅ **ONLY paste this into GitHub → Settings → Secrets**\n2. ❌ **NEVER send it on WhatsApp, Slack, or any messaging app**\n3. ❌ **NEVER put it in your GitHub repository**\n4. ❌ **NEVER put it in your code**\n5. ❌ **NEVER show it in screenshots or screen recordings**\n6. ❌ **NEVER share it with anyone** (not even your team)\n7. ❌ **NEVER email it or paste it in forums**\n\n**If your private key is leaked:**\n1. Delete it immediately: `rm ~/.ssh/github_deploy*`\n2. Generate a new one (repeat Step 4b)\n3. Remove the old public key from the server\n4. Update GitHub Secrets with the new key\n\n**Why is this so important?**\n- Anyone with your private key can access your server as you\n- They can read your data, delete files, or install malware\n- It's like giving someone your house keys\n\n**What to do now:**\n- Copy the entire output (including `-----BEGIN OPENSSH PRIVATE KEY-----` and `-----END OPENSSH PRIVATE KEY-----`)\n- Save it temporarily in a password manager or keep this Terminal window open\n- You'll paste it into GitHub Secrets in a later step\n- **Do NOT save it in a text file on your desktop!**\n\n---\n\n### Step 5: SSH Key Setup for Windows 🪟\n\n**Two options for Windows users:** Choose the one you prefer!\n\n---\n\n#### **Option A: Using PowerShell (Recommended - Built into Windows 10/11)**\n\nThis is the easiest option for most Windows users!\n\n##### Step 5a-1: Open PowerShell\n\n1. Press `Win + X`\n2. Click \"Windows PowerShell\" or \"Terminal\"\n3. You should see a blue window with a command prompt\n\n##### Step 5a-2: Create .ssh Directory (if it doesn't exist)\n\n```powershell\nmkdir $HOME\\.ssh\n```\n\n(Don't worry if it says \"already exists\" - that's fine!)\n\n##### Step 5a-3: Generate Your SSH Key\n\nCopy and paste this command:\n\n```powershell\nssh-keygen -t ed25519 -C \"github-actions-deploy\" -f $HOME\\.ssh\\github_deploy\n```\n\n**Understanding this command (Windows PowerShell version):**\n\n- **`ssh-keygen`** = The command to generate SSH keys\n- **`-t ed25519`** = Type of encryption algorithm\n  - `-t` = \"type\"\n  - `ed25519` = Modern, secure encryption\n- **`-C \"github-actions-deploy\"`** = Comment/label for your key\n  - `-C` = \"comment\"\n  - Helps identify this key later\n- **`-f $HOME\\.ssh\\github_deploy`** = File location\n  - `-f` = \"filename\"\n  - `$HOME` = PowerShell variable for your user directory (C:\\Users\\YourName)\n  - `\\.ssh\\` = SSH folder (note: backslash on Windows!)\n  - `github_deploy` = Name of your key file\n\n**You'll be asked:**\n1. **\"Enter passphrase\"** → Press Enter (leave empty)\n2. **\"Enter same passphrase again\"** → Press Enter again\n\n**Success!** ✅ You'll see something like:\n```\nYour identification has been saved in C:\\Users\\YourName\\.ssh\\github_deploy\nYour public key has been saved in C:\\Users\\YourName\\.ssh\\github_deploy.pub\n```\n\n##### Step 5a-4: Copy Public Key to Server\n\n**⚠️ IMPORTANT: Run this command on your LOCAL Windows machine, NOT on the VPS!**\n\n```powershell\ntype $HOME\\.ssh\\github_deploy.pub | ssh deploy@YOUR_SERVER_IP \"mkdir -p ~/.ssh \u0026\u0026 chmod 700 ~/.ssh \u0026\u0026 cat \u003e\u003e ~/.ssh/authorized_keys \u0026\u0026 chmod 600 ~/.ssh/authorized_keys\"\n```\n\n**Understanding this command:**\n- **`type`** = Windows command to read file contents (like `cat` on Linux)\n- **`|`** = Pipe - sends the public key to SSH command\n- **`mkdir -p ~/.ssh`** = Create .ssh directory if it doesn't exist\n- **`chmod 700 ~/.ssh`** = Set correct permissions on .ssh directory (REQUIRED for SSH security)\n- **`cat \u003e\u003e ~/.ssh/authorized_keys`** = Append your public key to authorized keys\n- **`chmod 600 ~/.ssh/authorized_keys`** = Set correct permissions on authorized_keys file (REQUIRED!)\n\n**Why permissions matter:**\n- Without correct permissions, SSH will **ignore your key** for security reasons\n- Server will fall back to password login\n- `700` = Only owner can access the directory\n- `600` = Only owner can read/write the file\n\n**Example:**\n```powershell\ntype $HOME\\.ssh\\github_deploy.pub | ssh deploy@194.238.22.106 \"mkdir -p ~/.ssh \u0026\u0026 chmod 700 ~/.ssh \u0026\u0026 cat \u003e\u003e ~/.ssh/authorized_keys \u0026\u0026 chmod 600 ~/.ssh/authorized_keys\"\n```\n\n**You'll be asked:**\n- **Password for deploy user** → Enter the password you created in Step 2\n\n**Understanding the password prompt:**\n\u003e You will be asked for the `deploy` user password **ONCE ONLY**.\n\u003e This is required so the server can accept your SSH key.\n\u003e After this step, SSH login will **NOT ask for a password again** - it will use your key instead!\n\n**Success!** ✅ Your key is now on the server with correct permissions!\n\n##### Step 5a-5: Test Your Connection\n\n```powershell\nssh -i $HOME\\.ssh\\github_deploy deploy@YOUR_SERVER_IP\n```\n\n**Understanding this test:**\n- **`-i $HOME\\.ssh\\github_deploy`** = Use this specific identity (private key) file\n- If successful: You login **WITHOUT password prompt** 🎉\n- This proves your SSH key is working correctly!\n\n**Success looks like:**\n```\nWelcome to Ubuntu 22.04.3 LTS...\ndeploy@yourserver:~$\n```\n**No password prompt = SUCCESS!** ✅\n\n**If it asks for password:**\n- Your key setup didn't work\n- Go back to Step 5a-4 and ensure you ran it with `chmod` commands\n- Check the troubleshooting section\n\n**To exit:** Type `exit` and press Enter to return to your local computer.\n\n**If it doesn't work at all:** Try Option B below (Git Bash).\n\n##### Step 5a-6: Copy PRIVATE KEY (GitHub Secrets ONLY) 🔐\n\n**🚨 CRITICAL SECURITY WARNING - READ CAREFULLY! 🚨******\n\nThis command shows your **PRIVATE KEY** - treat it like your bank password!\n\n```powershell\ntype $HOME\\.ssh\\github_deploy\n```\n\n**⚠️ PRIVATE KEY SECURITY RULES - NEVER BREAK THESE:**\n\n1. ✅ **ONLY paste this into GitHub → Settings → Secrets**\n2. ❌ **NEVER send it on WhatsApp, Slack, or any messaging app**\n3. ❌ **NEVER put it in your GitHub repository**\n4. ❌ **NEVER put it in your code**\n5. ❌ **NEVER show it in screenshots or screen recordings**\n6. ❌ **NEVER share it with anyone** (not even your team)\n7. ❌ **NEVER email it or paste it in forums**\n\n**If your private key is leaked:**\n1. Delete it immediately: `rm $HOME\\.ssh\\github_deploy*`\n2. Generate a new one (repeat Step 5a-3)\n3. Remove the old public key from the server\n4. Update GitHub Secrets with the new key\n\n**Why is this so important?**\n- Anyone with your private key can access your server as you\n- They can read your data, delete files, or install malware\n- It's like giving someone your house keys\n\n**What to do now:**\n- Copy the entire output (including `-----BEGIN OPENSSH PRIVATE KEY-----` and `-----END OPENSSH PRIVATE KEY-----`)\n- Save it temporarily in a password manager or keep this window open\n- You'll paste it into GitHub Secrets in a later step\n- **Do NOT save it in a text file on your desktop!**\n\n---\n\n#### **Option B: Using Git Bash (Alternative for Windows)**\n\nIf you have Git installed, you can use Git Bash which works exactly like Mac/Linux!\n\n##### Step 5b-1: Open Git Bash\n\n1. Press `Win + S` and search for \"Git Bash\"\n2. Click \"Git Bash\" to open it\n3. You should see a window similar to Mac Terminal\n\n##### Step 5b-2: Generate Your SSH Key\n\n```bash\nssh-keygen -t ed25519 -C \"github-actions-deploy\" -f ~/.ssh/github_deploy\n```\n\n**You'll be asked:**\n1. **\"Enter passphrase\"** → Press Enter (leave empty)\n2. **\"Enter same passphrase again\"** → Press Enter again\n\n**Success!** ✅ You'll see:\n```\nYour identification has been saved in /c/Users/YourName/.ssh/github_deploy\n```\n\n##### Step 5b-3: Copy Public Key to Server\n\n**⚠️ IMPORTANT: Run this command on your LOCAL Windows machine (in Git Bash), NOT on the VPS!**\n\n```bash\nssh-copy-id -i ~/.ssh/github_deploy.pub deploy@YOUR_SERVER_IP\n```\n\n**Note:** If `ssh-copy-id` doesn't work in Git Bash, use this alternative with proper permissions:\n\n```bash\ncat ~/.ssh/github_deploy.pub | ssh deploy@YOUR_SERVER_IP \"mkdir -p ~/.ssh \u0026\u0026 chmod 700 ~/.ssh \u0026\u0026 cat \u003e\u003e ~/.ssh/authorized_keys \u0026\u0026 chmod 600 ~/.ssh/authorized_keys\"\n```\n\n**Understanding the alternative command:**\n- Creates .ssh directory if needed\n- Sets correct permissions on directory (`chmod 700`)\n- Appends your public key to authorized_keys\n- Sets correct permissions on file (`chmod 600`) - **REQUIRED for SSH security!**\n\n**You'll be asked:**\n- **Password for deploy user** → Enter the password you created in Step 2\n\n**Understanding the password prompt:**\n\u003e You will be asked for the `deploy` user password **ONCE ONLY**.\n\u003e After this step, SSH login will **NOT ask for a password again** - it will use your key instead!\n\n##### Step 5b-4: Test Your Connection\n\n```bash\nssh -i ~/.ssh/github_deploy deploy@YOUR_SERVER_IP\n```\n\n**Understanding this test:**\n- **`-i ~/.ssh/github_deploy`** = Use this specific identity (private key) file\n- If successful: You login **WITHOUT password prompt** 🎉\n\n**Success looks like:**\n```\nWelcome to Ubuntu 22.04.3 LTS...\ndeploy@yourserver:~$\n```\n**No password prompt = SUCCESS!** ✅\n\n**If it asks for password:**\n- Go back to Step 5b-3 and use the alternative command with `chmod` included\n- Check the troubleshooting section\n\n**Success!** 🎉 Type `exit` to return to your local computer.\n\n##### Step 5b-5: Copy PRIVATE KEY (GitHub Secrets ONLY) 🔐\n\n**🚨 CRITICAL SECURITY WARNING - READ CAREFULLY! 🚨**\n\nThis command shows your **PRIVATE KEY** - treat it like your bank password!\n\n```bash\ncat ~/.ssh/github_deploy\n```\n\n**⚠️ PRIVATE KEY SECURITY RULES - NEVER BREAK THESE:**\n\n1. ✅ **ONLY paste this into GitHub → Settings → Secrets**\n2. ❌ **NEVER send it on WhatsApp, Slack, or any messaging app**\n3. ❌ **NEVER put it in your GitHub repository**\n4. ❌ **NEVER put it in your code**\n5. ❌ **NEVER show it in screenshots or screen recordings**\n6. ❌ **NEVER share it with anyone** (not even your team)\n7. ❌ **NEVER email it or paste it in forums**\n\n**If your private key is leaked:**\n1. Delete it immediately: `rm ~/.ssh/github_deploy*`\n2. Generate a new one (repeat Step 5b-2)\n3. Remove the old public key from the server\n4. Update GitHub Secrets with the new key\n\n**What to do now:**\n- Copy the entire output (including `-----BEGIN OPENSSH PRIVATE KEY-----` and `-----END OPENSSH PRIVATE KEY-----`)\n- Save it temporarily in a password manager\n- You'll paste it into GitHub Secrets in a later step\n- **Do NOT save it in a text file on your desktop!**\n\n---\n\n#### **Option C: Using PuTTY (Traditional Windows SSH Client)**\n\nIf you prefer PuTTY, here's how to set it up:\n\n##### Step 5c-1: Download PuTTY\n\n1. Go to [putty.org](https://www.putty.org/)\n2. Download and install PuTTY (includes PuTTYgen)\n\n##### Step 5c-2: Generate Key with PuTTYgen\n\n1. Open **PuTTYgen** (comes with PuTTY)\n2. Click **\"Generate\"**\n3. Move your mouse around the blank area to create randomness\n4. Click **\"Save private key\"** → Save as `github_deploy.ppk`\n5. Copy the public key from the text box at the top\n\n##### Step 5c-3: Add Public Key to Server\n\n1. Open PuTTY\n2. Enter your server IP and login as `deploy` user\n3. Run these commands:\n\n```bash\nmkdir -p ~/.ssh\nchmod 700 ~/.ssh\nnano ~/.ssh/authorized_keys\n```\n\n4. Paste your public key (right-click to paste in PuTTY)\n5. Press `Ctrl + X`, then `Y`, then Enter to save\n\n```bash\nchmod 600 ~/.ssh/authorized_keys\nexit\n```\n\n##### Step 5c-4: Convert PuTTY Key to OpenSSH Format (for GitHub)\n\nGitHub needs OpenSSH format, not PuTTY format:\n\n1. Open PuTTYgen\n2. Click **\"Load\"** and select your `github_deploy.ppk` file\n3. Go to **\"Conversions\"** → **\"Export OpenSSH key\"**\n4. Save as `github_deploy` (no extension)\n5. Open this file in Notepad - this is what you'll use for GitHub Secrets\n\n##### Step 5c-5: Configure PuTTY to Use Your Key\n\n1. Open PuTTY\n2. Enter your server IP\n3. Go to **Connection → SSH → Auth**\n4. Click **\"Browse\"** and select your `.ppk` file\n5. Go back to **Session**, enter a name (e.g., \"My VPS\"), and click **\"Save\"**\n6. Click **\"Open\"** to connect\n\n---\n\n### 🎯 SSH Keys Summary - What You Just Learned\n\n**Key Concepts:**\n1. ✅ SSH keys are created on **YOUR COMPUTER**\n2. ✅ Public key goes to the **SERVER** (in ~/.ssh/authorized_keys)\n3. ✅ Private key stays **LOCAL** (NEVER share it!)\n4. ✅ Permissions MUST be correct: `700` for .ssh directory, `600` for files\n5. ✅ Password asked **ONCE** when copying key, then never again\n\n**Security Checklist:**\n- 🔐 Private key is safe (not shared, not in repo)\n- 🔓 Public key is on server with correct permissions\n- ✅ Can SSH without password using `-i` flag\n- 🚫 Never copy private key to server\n- 🚫 Never share private key with anyone\n\n**Common Student Mistakes (DON'T DO THESE!):**\n1. ❌ Running commands on VPS when they should be on local machine\n2. ❌ Sharing private key thinking it's \"just for the project\"\n3. ❌ Forgetting `chmod` commands (SSH will silently fail)\n4. ❌ Putting private key in GitHub repo \"temporarily\"\n5. ❌ Copying key with wrong permissions\n\n**If you completed this successfully:**\n- 🎉 You understand public-key cryptography!\n- 🎉 You can now set up CI/CD securely!\n- 🎉 You're using the same method Fortune 500 companies use!\n\n---\n\n### Step 6: SSH Troubleshooting 🔧\n\n**If you're having trouble connecting, try these solutions:**\n\n#### Problem: \"Permission denied (publickey)\"\n\nThis usually means SSH is ignoring your key due to incorrect permissions.\n\n**Solution:**\n```bash\n# On your server (login with password first)\nssh deploy@YOUR_SERVER_IP\n\n# Fix permissions (CRITICAL!)\nchmod 700 ~/.ssh\nchmod 600 ~/.ssh/authorized_keys\n\n# Verify ownership\nls -la ~/.ssh\n\n# Make sure your public key is in the file\ncat ~/.ssh/authorized_keys\n```\n\n**Understanding these commands:**\n\n- **`chmod 700 ~/.ssh`** = Set directory permissions\n  - `700` = Owner can read, write, execute; others cannot access\n  - **SSH REQUIRES this - will ignore keys otherwise!**\n\n- **`chmod 600 ~/.ssh/authorized_keys`** = Set file permissions\n  - `600` = Owner can read/write; nobody else can access\n  - **SSH REQUIRES this for security - non-negotiable!**\n\n- **`ls -la`** = List files with permissions\n  - Verify: `.ssh` should show `drwx------` (700)\n  - Verify: `authorized_keys` should show `-rw-------` (600)\n\n- **`cat filename`** = Display file contents\n  - `cat` = \"concatenate\" (show file content)\n  - Displays entire file on screen\n  - Use to verify your public key is there\n\n**Common causes:**\n1. Forgot to run `chmod` commands when copying key\n2. Edited authorized_keys file (changes permissions sometimes)\n3. Wrong user's .ssh directory\n\n#### Problem: \"Connection refused\"\n\n**Possible causes:**\n1. Wrong IP address - Double-check your VPS IP\n2. Firewall blocking SSH - Make sure port 22 is open\n3. SSH service not running on server\n\n**Solution:**\n```bash\n# On your server\nsudo systemctl status sshd\nsudo systemctl start sshd\n```\n\n#### Problem: SSH key not working on Windows\n\n**Solution:** Make sure you're using the `-i` flag:\n```bash\nssh -i ~/.ssh/github_deploy deploy@YOUR_SERVER_IP\n```\n\nOr use the full path:\n```powershell\nssh -i C:\\Users\\YourName\\.ssh\\github_deploy deploy@YOUR_SERVER_IP\n```\n\n#### Still stuck?\n\n- Make sure you completed Step 2 (creating the deploy user)\n- Verify your server IP is correct: `ping YOUR_SERVER_IP`\n- Try logging in with password first: `ssh deploy@YOUR_SERVER_IP`\n- Check if SSH service is running on your server\n\n---\n\n### Step 7: Harden SSH Security (Disable Password Login) 🔐\n\n**⚠️ CRITICAL SECURITY STEP - Do this AFTER SSH keys work!**\n\nNow that your SSH key is working, let's lock down SSH to prevent password-based attacks.\n\n**Why this matters:**\n- Bots constantly try to guess passwords (brute force attacks)\n- They try root user first (most common target)\n- With keys only, they can't get in even with unlimited tries\n- This blocks 99.9% of automated attacks\n\n#### Step 7a: Test SSH Key Works (IMPORTANT!)\n\n**Before disabling passwords, make sure you can login with your key!**\n\n```bash\n# From your local computer\nssh -i ~/.ssh/github_deploy deploy@YOUR_SERVER_IP\n```\n\n✅ **If you can login without a password, proceed.**  \n❌ **If it asks for password, STOP and fix SSH keys first!**\n\nLocking yourself out means you'll need to use the VPS control panel to regain access.\n\n---\n\n#### Step 7b: Disable Root Login and Password Authentication\n\n**SSH into your server:**\n\n```bash\nssh -i ~/.ssh/github_deploy deploy@YOUR_SERVER_IP\n```\n\n**Edit SSH configuration:**\n\n```bash\nsudo nano /etc/ssh/sshd_config\n```\n\n**Understanding this file:**\n- This is SSH server configuration\n- Controls who can login and how\n- Changes take effect after restarting SSH service\n\n**Find and modify these lines** (use `Ctrl + W` to search in nano):\n\n**1. Disable root login:**\n```bash\n# Find this line (might be commented with #):\n#PermitRootLogin yes\n\n# Change it to:\nPermitRootLogin no\n```\n\n**2. Disable password authentication:**\n```bash\n# Find this line:\n#PasswordAuthentication yes\n\n# Change it to:\nPasswordAuthentication no\n```\n\n**3. Ensure public key authentication is enabled:**\n```bash\n# Find this line:\n#PubkeyAuthentication yes\n\n# Make sure it says (remove # if present):\nPubkeyAuthentication yes\n```\n\n**4. Optional but recommended - Disable challenge-response:**\n```bash\n# Find this line:\n#ChallengeResponseAuthentication yes\n\n# Change it to:\nChallengeResponseAuthentication no\n```\n\n**Save and exit:**\n- Press `Ctrl + X`\n- Press `Y` (yes, save)\n- Press `Enter` (confirm filename)\n\n---\n\n#### Step 7c: Test Configuration Before Applying\n\n**Test the SSH configuration for syntax errors:**\n\n```bash\nsudo sshd -t\n```\n\n**Understanding this command:**\n- `sshd` = SSH daemon (server)\n- `-t` = Test configuration file only (don't start service)\n- If successful: Returns to prompt with no output ✅\n- If error: Shows what's wrong ❌ (fix before proceeding)\n\n**If you see errors:**\n- Open the config file again: `sudo nano /etc/ssh/sshd_config`\n- Fix the syntax\n- Test again: `sudo sshd -t`\n\n---\n\n#### Step 7d: Restart SSH Service\n\n**⚠️ Keep your current SSH session open! Open a NEW terminal for testing!**\n\n```bash\nsudo systemctl restart sshd\n```\n\n**Understanding this:**\n- Restarts SSH service with new configuration\n- Your current connection stays active\n- New connections will use new rules\n\n---\n\n#### Step 7e: Test in a NEW Terminal (Don't Close Old One!)\n\n**In a NEW terminal window on your local computer:**\n\n```bash\n# Test 1: Key-based login should work\nssh -i ~/.ssh/github_deploy deploy@YOUR_SERVER_IP\n# Should work without password ✅\n\n# Exit and test again\nexit\n\n# Test 2: Root login should be blocked\nssh root@YOUR_SERVER_IP\n# Should show: \"Permission denied\" ✅\n\n# Test 3: Login without key should fail\nssh deploy@YOUR_SERVER_IP\n# Should show: \"Permission denied (publickey)\" ✅\n```\n\n**Expected results:**\n1. ✅ With key (`-i`): Login successful\n2. ✅ As root: \"Permission denied\"\n3. ✅ Without key: \"Permission denied (publickey)\"\n\n**If Test 1 fails (can't login with key):**\n1. **DON'T CLOSE your original SSH session!**\n2. In original session, revert changes:\n   ```bash\n   sudo nano /etc/ssh/sshd_config\n   # Change back to:\n   PasswordAuthentication yes\n   # Save and restart: sudo systemctl restart sshd\n   ```\n3. Figure out why keys aren't working (check Step 6 troubleshooting)\n\n**If all tests pass:**\n🎉 **Success!** Your server is now hardened against brute force attacks!\n\n---\n\n#### Step 7f: Understanding What You Just Did\n\n**Security improvements:**\n\n| Before | After |\n|--------|-------|\n| 💀 Root can login directly | ✅ Root login blocked |\n| 💀 Anyone can try passwords | ✅ Keys required |\n| 💀 Bots can brute force | ✅ Immune to password attacks |\n| 💀 Weak passwords = risk | ✅ Cryptographic keys only |\n\n**Real-world impact:**\n- Typical server gets 1000+ login attempts per day\n- All automated attacks now fail instantly\n- You've eliminated the #1 server compromise method\n\n**Why we do each step:**\n1. **PermitRootLogin no**: Forces use of sudo (accountability + security)\n2. **PasswordAuthentication no**: Stops all password guessing attacks\n3. **PubkeyAuthentication yes**: Only SSH keys can login\n4. **ChallengeResponseAuthentication no**: Closes another password-based method\n\n---\n\n### Step 8: Configure Firewall (UFW) 🔒\n\nNow let's add firewall protection! Think of a firewall as a security guard that only lets the right kind of traffic through.\n\n```bash\n# Allow SSH, HTTP, and HTTPS only\nsudo ufw allow OpenSSH\nsudo ufw allow 80/tcp\nsudo ufw allow 443/tcp\n\n# Enable firewall\nsudo ufw enable\n\n# Check status\nsudo ufw status\n```\n\n**Understanding these commands:**\n\n- **`sudo`** = Run command with administrator (root) privileges\n  - \"sudo\" = \"superuser do\"\n  - Required for system-level changes\n  - Will ask for your password the first time\n  \n- **`ufw`** = Uncomplicated Firewall (Ubuntu's firewall manager)\n  - Simple firewall interface\n  - Manages iptables rules behind the scenes\n\n- **`ufw allow OpenSSH`** = Allow SSH connections\n  - `allow` = Open this port\n  - `OpenSSH` = Port 22 (SSH service)\n  - **CRITICAL:** Do this FIRST or you might lock yourself out!\n\n- **`ufw allow 80/tcp`** = Allow HTTP traffic\n  - `80` = Port number for HTTP\n  - `/tcp` = Use TCP protocol (standard for web traffic)\n  - Needed for: Regular website access, Let's Encrypt verification\n\n- **`ufw allow 443/tcp`** = Allow HTTPS traffic\n  - `443` = Port number for HTTPS (secure)\n  - Required for encrypted website traffic\n\n- **`ufw enable`** = Turn on the firewall\n  - Activates all the rules you just set\n  - Firewall will start automatically on reboot\n\n- **`ufw status`** = Show current firewall rules\n  - Lists all allowed/blocked ports\n  - Shows if firewall is active\n\n**When asked** \"Command may disrupt existing ssh connections. Proceed with operation (y|n)?\"\n→ Type `y` and press Enter (don't worry, OpenSSH is already allowed!)\n\n---\n\n### Step 9: Update System and Install Essential Tools 🛠️\n\nLet's make sure your server has all the latest security updates and tools we'll need.\n\n```bash\n# Update system packages\nsudo apt update \u0026\u0026 sudo apt upgrade -y\n\n# Install essential tools\nsudo apt install -y curl git unzip ca-certificates gnupg\n```\n\n**Understanding these commands:**\n\n- **`sudo apt update`** = Update package lists\n  - `apt` = Advanced Package Tool (Ubuntu's package manager)\n  - `update` = Downloads list of available packages and versions\n  - Like checking for updates on your phone, but doesn't install them yet\n\n- **`\u0026\u0026`** = Run next command ONLY if previous command succeeds\n  - Logical AND operator\n  - If `apt update` fails, `apt upgrade` won't run\n\n- **`sudo apt upgrade -y`** = Install available updates\n  - `upgrade` = Actually installs the updates\n  - `-y` = Automatically answer \"yes\" to all prompts\n  - Without `-y`, it would ask for confirmation\n\n- **`sudo apt install -y`** = Install new packages\n  - `install` = Download and install packages\n  - `-y` = Auto-confirm installation\n\n- **Packages being installed:**\n  - **`curl`** = Command-line tool for downloading files from URLs\n    - Used to download Bun, NVM, etc.\n  - **`git`** = Version control system\n    - Needed to clone your repository\n  - **`unzip`** = Extract .zip files\n    - Used by various installers\n  - **`ca-certificates`** = SSL/TLS certificate authorities\n    - Required for HTTPS connections\n  - **`gnupg`** = GNU Privacy Guard\n    - Used for secure communications and verifying downloads\n\n---\n\n### Step 10: Install Node.js (using NVM) 📦\n\nNode.js is needed to run your frontend. We'll use NVM (Node Version Manager) which makes it easy to install and manage Node versions.\n\n```bash\n# Install NVM (Node Version Manager)\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n\n# Reload shell configuration\nsource ~/.bashrc\n\n# Install Node.js LTS\nnvm install --lts\nnvm use --lts\n\n# Verify installation\nnode -v\nnpm -v\n```\n\n**Understanding these commands:**\n\n- **`curl -o- URL | bash`** = Download and execute installer script\n  - `curl` = Download tool\n  - `-o-` = Output to stdout (screen) instead of a file\n    - `-o` means \"output\"\n    - `-` means \"stdout\" (standard output)\n  - `| bash` = Pipe the downloaded script directly to bash to execute it\n    - `|` = \"pipe\" - sends output of one command to another\n    - `bash` = Shell interpreter that runs the script\n\n- **`source ~/.bashrc`** = Reload shell configuration\n  - `source` = Execute commands from a file in current shell\n  - `~/.bashrc` = Your bash configuration file\n  - `~` = Your home directory\n  - Needed so NVM commands become available immediately\n\n- **`nvm install --lts`** = Install Node.js Long Term Support version\n  - `nvm` = Node Version Manager command\n  - `install` = Download and install Node.js\n  - `--lts` = Latest stable \"Long Term Support\" version\n    - `--` prefix means it's a \"long option\" (full word)\n    - LTS versions get updates for 30+ months\n\n- **`nvm use --lts`** = Switch to using the LTS version\n  - `use` = Set active Node.js version\n  - Required if you have multiple Node versions\n\n- **`node -v`** = Show Node.js version\n  - `-v` = \"version\" (short option)\n  - Should show something like `v20.11.0`\n\n- **`npm -v`** = Show npm (Node Package Manager) version\n  - Comes bundled with Node.js\n  - Used for installing JavaScript packages\n\n---\n\n### Step 11: Install Bun ⚡\n\nBun is the JavaScript runtime we're using for both the backend and frontend. It's super fast!\n\n```bash\n# Install Bun\ncurl -fsSL https://bun.sh/install | bash\n\n# Reload shell\nsource ~/.bashrc\n\n# Verify installation\nbun -v\n```\n\n---\n\n### Step 12: Install PM2 (Process Manager) 🔄\n\nPM2 keeps your apps running and restarts them if they crash:\n\n```bash\n# Install PM2 globally\nnpm install -g pm2\n\n# Verify installation\npm2 -v\n```\n\n**Understanding these commands:**\n\n- **`npm install -g pm2`** = Install PM2 globally\n  - `npm` = Node Package Manager\n  - `install` = Download and install a package\n  - `-g` = Install globally (system-wide)\n    - Without `-g`, it installs only in current project\n    - Global packages can be used anywhere on the system\n  - `pm2` = The package name (Process Manager 2)\n\n- **`pm2 -v`** = Show PM2 version\n  - Verifies PM2 was installed correctly\n\n---\n\n### Step 13: Install Nginx (Web Server) 🌐\n\nNginx will act as a \"reverse proxy\" - it receives requests from the internet and forwards them to your apps. It also handles SSL certificates for HTTPS!\n\n```bash\n# Install Nginx\nsudo apt install -y nginx\n\n# Enable Nginx to start on boot\nsudo systemctl enable nginx\n\n# Start Nginx\nsudo systemctl start nginx\n\n# Check status\nsudo systemctl status nginx\n```\n\n**Understanding these commands:**\n\n- **`sudo apt install -y nginx`** = Install Nginx web server\n  - Same as earlier package installation\n  - `nginx` = High-performance web server and reverse proxy\n\n- **`sudo systemctl enable nginx`** = Make Nginx start automatically on boot\n  - `systemctl` = System and service manager for Linux\n  - `enable` = Create startup link (doesn't start it now, but will on reboot)\n  - Alternative: `disable` would remove auto-start\n\n- **`sudo systemctl start nginx`** = Start Nginx right now\n  - `start` = Begin running the service immediately\n  - Other options: `stop`, `restart`, `reload`\n\n- **`sudo systemctl status nginx`** = Check if Nginx is running\n  - `status` = Show current state of the service\n  - Shows: active (running), inactive (stopped), failed, etc.\n  - Also shows recent log messages\n\n**Test:** Open `http://YOUR_SERVER_IP` in browser → Should show \"Welcome to nginx!\"\n\n---\n\n### Step 14: Create Project Directory 📁\n\nLet's create a nice organized place for your application!\n\n```bash\n# Create directory for your apps\nmkdir -p ~/apps\n\n# Navigate to it\ncd ~/apps\n```\n\n**Understanding these commands:**\n\n- **`mkdir -p ~/apps`** = Create a directory\n  - `mkdir` = \"make directory\"\n  - `-p` = \"parents\" - create parent directories if they don't exist\n    - Without `-p`, it would fail if `~` didn't exist\n    - Also, `-p` won't error if directory already exists\n  - `~/apps` = Path to create\n    - `~` = Your home directory (/home/deploy)\n    - `/apps` = Subdirectory named \"apps\"\n\n- **`cd ~/apps`** = Change directory (navigate to folder)\n  - `cd` = \"change directory\"\n  - All subsequent commands will run in this folder\n\n---\n\n## Part 2: Configure Nginx (Web Server)\n\n### Step 15: Create Nginx Configuration 🌐\n\nLet's tell Nginx how to route traffic to your frontend and backend!\n\n```bash\nsudo nano /etc/nginx/sites-available/headshotprobuild\n```\n\nPaste this configuration (replace `yourdomain.com` with your actual domain):\n\n```nginx\n# =========================\n# FRONTEND (HTTP)\n# =========================\nserver {\n    listen 80;\n    listen [::]:80;\n\n    server_name mchamouda.store www.mchamouda.store;\n\n    location / {\n        proxy_pass http://127.0.0.1:3000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    }\n}\n\n# =========================\n# BACKEND API (HTTP)\n# =========================\nserver {\n    listen 80;\n    listen [::]:80;\n\n    server_name api.mchamouda.store;\n\n    location / {\n        proxy_pass http://127.0.0.1:8000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    }\n}\n\n```\n\nSave the file (Ctrl+X, Y, Enter).\n\n---\n\n### Step 16: Enable the Site\n\n```bash\n# Create symbolic link to enable the site\nsudo ln -s /etc/nginx/sites-available/headshotprobuild /etc/nginx/sites-enabled/\n\n# Remove default site\nsudo rm -f /etc/nginx/sites-enabled/default\n\n# Test configuration\nsudo nginx -t\n\n# If test passes, restart Nginx\nsudo systemctl restart nginx\n```\n\n**Understanding these commands:**\n\n- **`ln -s source destination`** = Create symbolic link (like a shortcut)\n  - `ln` = \"link\" command\n  - `-s` = Create symbolic (soft) link instead of hard link\n  - `source` = Original file location (/etc/nginx/sites-available/...)\n  - `destination` = Where to put the link (/etc/nginx/sites-enabled/)\n  - Nginx reads configs from `sites-enabled/`, but we edit in `sites-available/`\n\n- **`rm -f file`** = Remove (delete) file\n  - `rm` = \"remove\"\n  - `-f` = Force (don't ask for confirmation, don't error if file doesn't exist)\n  - Removes default Nginx page so yours is used\n\n- **`nginx -t`** = Test Nginx configuration\n  - `-t` = Test configuration file syntax\n  - Checks for errors BEFORE applying changes\n  - Always run this before restarting Nginx!\n  - Output: \"syntax is ok\" means success\n\n- **`systemctl restart nginx`** = Stop and start Nginx\n  - `restart` = Full stop and start (applies new config)\n  - Alternative: `reload` (smoother, but restart is safer)\n\n---\n\n## Part 3: Prepare Your GitHub Repository\n\n### Step 17: Ensure .env Files Are NOT in Git 🚨\n\n**⚠️ CRITICAL**: Before anything else, make sure your secrets are safe!\n\n**On your local computer**:\n\n```bash\n# Navigate to your project\ncd /path/to/your/project\n\n# Check if .env files are ignored\ncat .gitignore | grep .env\n```\n\n**If `.env` is NOT in `.gitignore`, add it**:\n\n```bash\n# Add to .gitignore\necho \"\" \u003e\u003e .gitignore\necho \"# Environment variables\" \u003e\u003e .gitignore\necho \".env\" \u003e\u003e .gitignore\necho \".env.local\" \u003e\u003e .gitignore\necho \".env*.local\" \u003e\u003e .gitignore\necho \"*.env\" \u003e\u003e .gitignore\n```\n\n**If you already committed .env files to Git, remove them**:\n\n```bash\n# Remove .env files from Git (keeps local copy)\ngit rm --cached frontend/.env.local\ngit rm --cached backend/.env\n\n# Commit the change\ngit add .gitignore\ngit commit -m \"Remove .env files from Git and add to .gitignore\"\n\n# Push to GitHub\ngit push origin main\n```\n\n✅ **Now your secrets are safe!**\n\n---\n\n### Step 18: Understanding Self-Hosted vs Cloud Runners 🤔\n\nBefore we create our workflows, let's understand the two options:\n\n**Option 1: GitHub Cloud Runners (Default)**\n- Runs on GitHub's servers\n- Connects to your VPS via SSH\n- Free for public repos, limited minutes for private repos\n- Good for: Simple deployments, small projects\n\n**Option 2: Self-Hosted Runners (What we'll use!)**\n- Runs directly on your VPS\n- Faster deployments (no SSH needed)\n- Unlimited minutes\n- Direct access to your server\n- Good for: Faster deployments, unlimited builds\n\n**We'll use Self-Hosted Runners because it's faster and gives you unlimited deployments!**\n\n---\n\n### Step 19: Set Up Self-Hosted Runner (GUI Method) 🖱️\n\nLet's set up a self-hosted runner using GitHub's friendly interface!\n\n#### Step 19a: Navigate to GitHub Settings\n\n1. Open your web browser and go to your GitHub repository\n2. Click the **\"Settings\"** tab at the top (you need admin access)\n3. In the left sidebar, scroll down and click **\"Actions\"**\n4. Under Actions, click **\"Runners\"**\n5. Click the green **\"New self-hosted runner\"** button\n\n#### Step 19b: Choose Your Platform\n\nYou'll see a page with different options:\n\n1. **Operating System**: Click **\"Linux\"**\n2. **Architecture**: Click **\"x64\"**\n3. You'll see a list of commands below - keep this page open, we'll need it!\n\n#### Step 19c: SSH Into Your VPS\n\nOpen your terminal (Mac) or PowerShell/Git Bash (Windows):\n\n```bash\n# Use the SSH key you created earlier\nssh -i ~/.ssh/github_deploy deploy@YOUR_SERVER_IP\n```\n\n**Example:**\n```bash\nssh -i ~/.ssh/github_deploy deploy@194.238.22.106\n```\n\n#### Step 19d: Download and Install the Runner\n\n**Copy the commands from the GitHub page and run them on your server.** Here's what they'll look like (your token will be different):\n\n```bash\n# Create a folder for the runner\nmkdir actions-runner \u0026\u0026 cd actions-runner\n\n# Download the latest runner package\ncurl -o actions-runner-linux-x64-2.311.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz\n\n# Optional: Validate the hash (skip if you want)\necho \"29fc8cf2dab4c195bb147384e7e2c94cfd4d4022c793b346a6175435265aa278  actions-runner-linux-x64-2.311.0.tar.gz\" | shasum -a 256 -c\n\n# Extract the installer\ntar xzf ./actions-runner-linux-x64-2.311.0.tar.gz\n```\n\n**Understanding these commands:**\n\n- **`mkdir actions-runner \u0026\u0026 cd actions-runner`** = Create and enter directory\n  - Creates folder, then immediately moves into it\n  - `\u0026\u0026` means \"if first succeeds, do second\"\n\n- **`curl -o filename -L URL`** = Download a file\n  - `-o filename` = Output to this filename (instead of showing on screen)\n  - `-L` = Follow redirects (if URL redirects to another location)\n  - Downloads the GitHub Actions runner\n\n- **`shasum -a 256 -c`** = Verify file integrity (security check)\n  - `shasum` = Calculate/check SHA checksums\n  - `-a 256` = Use SHA-256 algorithm\n  - `-c` = Check the hash matches\n  - Optional but recommended for security\n\n- **`tar xzf filename.tar.gz`** = Extract compressed archive\n  - `tar` = Tape archive utility (packaging tool)\n  - `x` = Extract files\n  - `z` = Decompress using gzip\n  - `f` = Use this file\n  - `.tar.gz` = Compressed archive (like .zip on Windows)\n\n**Tip:** Remember \"**x**tract **z**e **f**ile\" for `tar xzf`!\n\n**Note:** The version number might be different - use the exact commands from your GitHub page!\n\n#### Step 19e: Configure the Runner\n\nNow run the config command (copy from GitHub, it includes your token):\n\n```bash\n./config.sh --url https://github.com/YOUR_USERNAME/YOUR_REPO --token YOUR_TOKEN\n```\n\n**You'll be asked some questions:**\n\n1. **\"Enter the name of the runner group\"** → Press Enter (uses \"Default\")\n2. **\"Enter the name of runner\"** → Type a name like `vps-runner` or press Enter\n3. **\"Enter any additional labels\"** → Press Enter (no additional labels)\n4. **\"Enter name of work folder\"** → Press Enter (uses \"_work\")\n\n**Success!** ✅ You'll see: \"Settings Saved.\"\n\n#### Step 19f: Install as a Service (Runs Automatically)\n\nThis makes sure the runner starts automatically when your server reboots:\n\n```bash\n# Install the service\nsudo ./svc.sh install\n\n# Start the service\nsudo ./svc.sh start\n\n# Check status\nsudo ./svc.sh status\n```\n\n**Success!** ✅ You should see: \"active (running)\"\n\n#### Step 19g: Verify on GitHub\n\nGo back to your GitHub repository:\n1. Settings → Actions → Runners\n2. You should see your runner with a green dot (🟢) and \"Idle\" status\n\n**Congratulations!** 🎉 Your self-hosted runner is ready!\n\n---\n\n### Step 20: Create GitHub Workflow Files ⚙️\n\nNow let's create the workflow files that will use your self-hosted runner!\n\n**On your local computer**, navigate to your project:\n\n```bash\ncd /path/to/your/project\n\n# Create the workflows directory\nmkdir -p .github/workflows\n```\n\n---\n\n#### Workflow 1: Deploy Backend with .env Creation\n\nCreate a new file `.github/workflows/deploy-backend.yml`:\n\n**Using your text editor or VS Code**, create this file with the following content:\n\n```yaml\nname: Deploy Backend to VPS\n\non:\n  push:\n    branches: [\"main\"]\n\n\njobs:\n  build:\n    runs-on: self-hosted\n    \n    strategy:\n      matrix:\n        node-version: [22.x]\n    \n    steps:\n      # Step 1: Check out the repository\n      - name: 📥 Checkout code\n        uses: actions/checkout@v4\n      \n      # Step 2: 🔐 CREATE .env FILE FROM GITHUB SECRETS (THE MAGIC!)\n      - name: 🔐 Create .env file from GitHub Secrets\n        run: |\n          cd ${{ github.workspace }}\n          echo \"${{ secrets.BACKEND_ENV }}\" \u003e .env\n          chmod 600 .env\n          echo \"✅ .env file created successfully\"\n      \n      # Step 3: Install Bun\n      - name: 🔧 Install Bun\n        run: |\n          export PATH=\"$HOME/.bun/bin:$PATH\"\n          if ! command -v bun \u0026\u003e /dev/null; then\n            echo \"📦 Installing Bun...\"\n            curl -fsSL https://bun.sh/install | bash\n            export PATH=\"$HOME/.bun/bin:$PATH\"\n          fi\n          bun --version\n      \n      # Step 4: Install dependencies\n      - name: 📦 Install dependencies with Bun\n        run: |\n          cd ${{ github.workspace }}\n          export PATH=\"$HOME/.bun/bin:$PATH\"\n          bun install\n      \n      # Step 5: Build the project\n      - name: 🏗️ Build the project\n        run: |\n          cd ${{ github.workspace }}\n          export PATH=\"$HOME/.bun/bin:$PATH\"\n          bun run build\n      \n      # Step 6: Restart the application\n      - name: 🚀 Restart Application\n        run: |\n          cd ${{ github.workspace }}\n          pm2 restart backend || pm2 start dist/index.js --name backend\n          pm2 save\n          echo \"✅ Backend deployment completed successfully!\"****\n```\n\n**Key Points to Notice:**\n- 🔥 **Line 17-22**: This is where the `.env` file is created from GitHub Secrets!\n- 🏃 **runs-on: self-hosted**: Uses your VPS runner, not GitHub's servers\n- 📍 **${{ github.workspace }}**: Automatically points to your code on the VPS\n- 🔐 **chmod 600 .env**: Makes the file readable only by you (security!)\n\n---\n\n#### Workflow 2: Deploy Frontend with .env Creation\n\nCreate `.github/workflows/deploy-frontend.yml`:\n\n```yaml\nname: Deploy Next.js Frontend\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\njobs:\n  deploy:\n    runs-on: self-hosted\n    \n    strategy:\n      matrix:\n        node-version: [22.x]\n    \n    steps:\n      # Step 1: Check out the repository\n      - name: 📥 Checkout code\n        uses: actions/checkout@v4\n      \n      # Step 2: 🔐 CREATE .env FILE FROM GITHUB SECRETS (THE MAGIC!)\n      - name: 🔐 Create .env file from GitHub Secrets\n        run: |\n          cd ${{ github.workspace }}/frontend\n          echo \"${{ secrets.FRONTEND_ENV }}\" \u003e .env\n          chmod 600 .env\n          echo \"✅ .env file created successfully\"\n      \n      # Step 3: Check NODE_ENV in .env file\n      - name: ✅ Verify NODE_ENV is production\n        run: |\n          cd ${{ github.workspace }}/frontend\n          if [ ! -f \".env\" ]; then\n            echo \"❌ Error: .env file not found\"\n            exit 1\n          fi\n          if ! grep -q \"^NODE_ENV=production\" .env; then\n            echo \"⚠️ Warning: NODE_ENV should be set to production\"\n          else\n            echo \"✅ NODE_ENV is correctly set to production\"\n          fi\n      \n      # Step 4: Install Bun\n      - name: 🔧 Install Bun\n        run: |\n          export PATH=\"$HOME/.bun/bin:$PATH\"\n          if ! command -v bun \u0026\u003e /dev/null; then\n            echo \"📦 Installing Bun...\"\n            curl -fsSL https://bun.sh/install | bash\n            export PATH=\"$HOME/.bun/bin:$PATH\"\n          fi\n          bun --version\n      \n      # Step 5: Install Dependencies\n      - name: 📦 Install Dependencies\n        run: |\n          cd ${{ github.workspace }}/frontend\n          export PATH=\"$HOME/.bun/bin:$PATH\"\n          bun install\n      \n      # Step 6: Build the Next.js App\n      - name: 🏗️ Build Frontend\n        run: |\n          cd ${{ github.workspace }}/frontend\n          export PATH=\"$HOME/.bun/bin:$PATH\"\n          bun run build\n      \n      # Step 7: Restart Frontend with PM2\n      - name: 🚀 Restart Frontend\n        run: |\n          cd ${{ github.workspace }}/frontend\n          pm2 restart frontend || pm2 start \"bun run start\" --name frontend\n          pm2 save\n          echo \"✅ Frontend deployment completed successfully!\"\n```\n\n**Key Points to Notice:**\n- 🔥 **Line 23-28**: Creates `.env` file from `FRONTEND_ENV` secret\n- 🎨 Same pattern as backend but for your Next.js app\n- 🔄 Uses PM2 to keep your frontend running 24/7\n\n---\n\n### Understanding the .env Creation (Step 2 in both workflows) 🔐\n\nLet's break down what this magical step does:\n\n```yaml\n- name: 🔐 Create .env file from GitHub Secrets\n  run: |\n    cd ${{ github.workspace }}/backend\n    echo \"${{ secrets.BACKEND_ENV }}\" \u003e .env\n    chmod 600 .env\n```\n\n**Line by line explanation:**\n\n1. **cd ${{ github.workspace }}/backend**\n   - Goes to your backend folder on the VPS\n   - `${{ github.workspace }}` = `/home/deploy/actions-runner/_work/your-repo/your-repo`\n\n2. **echo \"${{ secrets.BACKEND_ENV }}\" \u003e .env**\n   - `${{ secrets.BACKEND_ENV }}` = Gets the secret from GitHub (encrypted)\n   - `\u003e .env` = Writes it to a file called `.env`\n   - This file contains ALL your environment variables!\n\n3. **chmod 600 .env**\n   - Makes the file readable/writable only by you\n   - `600` means: Owner can read/write, nobody else can access it\n   - Security best practice! 🔒\n\n**The Result:**\n- ✅ Fresh `.env` file created on every deployment\n- ✅ Never in Git or GitHub repository\n- ✅ Always up-to-date with your GitHub Secrets\n- ✅ Secure permissions (only you can read it)\n\n**Understanding the commands used:**\n\n- **`echo \"text\" \u003e file`** = Write text to a file\n  - `echo` = Print/output text\n  - `\"${{ secrets.BACKEND_ENV }}\"` = The secret from GitHub\n  - `\u003e` = Redirect output to a file (overwrites if exists)\n  - `\u003e\u003e` would append instead of overwrite\n\n- **`chmod 600 .env`** = Change file permissions\n  - `chmod` = \"change mode\" (permissions)\n  - `600` = Permission code (explained below)\n  - `.env` = File to change\n\n**Understanding chmod permission codes:**\n```\nchmod 600 .env\n      │││\n      │││\n      ││└─ Others (everyone else): 0 = no permissions\n      │└── Group (your group): 0 = no permissions  \n      └─── Owner (you): 6 = read (4) + write (2) = 6\n```\n\n**Permission numbers:**\n- `4` = Read (r)\n- `2` = Write (w)\n- `1` = Execute (x)\n- `0` = No permissions (-)\n\n**Common combinations:**\n- `600` = Owner: read+write, Others: none (perfect for secrets!)\n- `644` = Owner: read+write, Others: read only\n- `700` = Owner: read+write+execute, Others: none\n- `755` = Owner: all, Others: read+execute (common for directories)\n\n---\n\n### Step 21: Commit and Push Workflow Files 📤\n\nNow let's save these workflow files to your repository!\n\n**On your local computer:**\n\n```bash\n# Make sure you're in your project directory\ncd /path/to/your/project\n\n# Add the workflow files\ngit add .github/workflows/\n\n# Commit them\ngit commit -m \"Add self-hosted GitHub Actions deployment workflows\"\n\n# Push to GitHub\ngit push origin main\n```\n\n**⚠️ Don't worry if the workflow fails!** We haven't added the secrets yet. Let's do that now!\n\n---\n\n## Part 4: Configure GitHub Secrets (The Magic Part! 🪄)\n\nThis is THE MOST IMPORTANT PART! This is where you securely store your environment variables so GitHub Actions can create your `.env` files automatically.\n\n### Step 22: Add Secrets to GitHub (Using GUI) 🔐\n\nLet's add your secrets using GitHub's friendly web interface!\n\n#### Step 22a: Navigate to GitHub Secrets\n\n1. **Open your web browser** and go to your GitHub repository\n2. Click the **\"Settings\"** tab at the top (between \"Insights\" and \"Security\")\n3. In the left sidebar, find **\"Secrets and variables\"**\n4. Click **\"Actions\"** under \"Secrets and variables\"\n5. You should now see the \"Actions secrets and variables\" page\n\n---\n\n#### Step 22b: Understanding What Secrets We Need\n\nWe need **TWO main secrets** for the self-hosted runner setup:\n\n1. **BACKEND_ENV** = All your backend environment variables\n2. **FRONTEND_ENV** = All your frontend environment variables\n\n**Why these secrets?**\n- They contain ALL your API keys, database passwords, etc.\n- GitHub stores them encrypted (super secure! 🔒)\n- Your workflows use them to create `.env` files on your VPS\n- They NEVER appear in your code or Git history\n\n---\n\n#### Step 22c: Add BACKEND_ENV Secret\n\n**Let's add the backend secret first!**\n\n1. Click the green **\"New repository secret\"** button (top right)\n2. You'll see a form with two fields:\n\n**Field 1: Name**\n- Type: `BACKEND_ENV`\n- ⚠️ Must be EXACTLY this name (case-sensitive!)\n\n**Field 2: Secret**\n- This is where you paste ALL your backend environment variables\n- Open your local backend `.env` file and copy EVERYTHING from it\n\n**Example of what to paste:**\n```env\nPORT=8000\nNODE_ENV=production\n\n# Database\nMONGODB_URI=mongodb+srv://user:password@cluster.mongodb.net/dbname\n\n# JWT\nJWT_SECRET=your-super-secret-jwt-key-change-this\nJWT_EXPIRES_IN=7d\n\n# AWS S3\nAWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE\nAWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\nAWS_REGION=us-east-1\nAWS_S3_BUCKET=your-bucket-name\n\n# Email\nEMAIL_HOST=smtp.zoho.com\nEMAIL_PORT=465\nEMAIL_USER=noreply@yourdomain.com\nEMAIL_PASSWORD=your-email-password\nEMAIL_FROM=noreply@yourdomain.com\n\n# Stripe\nSTRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxxx\nSTRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx\n\n# Replicate AI\nREPLICATE_API_TOKEN=r8_xxxxxxxxxxxxx\n\n# Redis\nREDIS_URL=redis://localhost:6379\n\n# Frontend URL\nFRONTEND_URL=https://yourdomain.com\n```\n\n**⚠️ IMPORTANT NOTES:**\n- Make sure `NODE_ENV=production` is included (our workflow checks for this!)\n- Include ALL variables your backend needs\n- Don't add quotes around values (unless they're part of the actual value)\n- Each variable on its own line\n- No spaces around the `=` sign\n\n3. After pasting your content, click the green **\"Add secret\"** button at the bottom\n\n**Success!** ✅ You should see \"BACKEND_ENV\" in your secrets list!\n\n---\n\n#### Step 22d: Add FRONTEND_ENV Secret\n\nNow let's add the frontend environment variables!\n\n1. Click the green **\"New repository secret\"** button again\n2. Fill in the form:\n\n**Field 1: Name**\n- Type: `FRONTEND_ENV`\n- ⚠️ Must be EXACTLY this name (case-sensitive!)\n\n**Field 2: Secret**\n- This is where you paste ALL your frontend environment variables\n- Open your local frontend `.env` or `.env.local` file and copy EVERYTHING\n\n**Example of what to paste:**\n\n```env\nNODE_ENV=production\nNEXT_PUBLIC_API_URL=https://api.yourdomain.com\nNEXT_PUBLIC_STRIPE_KEY=pk_live_xxxxxxxxxxxxx\nNEXT_PUBLIC_ENVIRONMENT=production\n```\n\n**⚠️ IMPORTANT NOTES:**\n- Make sure `NODE_ENV=production` is included\n- Variables starting with `NEXT_PUBLIC_` are accessible in the browser\n- Other variables are only available during build time\n- Include ALL variables your frontend needs\n\n3. After pasting your content, click the green **\"Add secret\"** button\n\n**Success!** ✅ You should now see both secrets in your list!\n\n---\n\n### Step 22e: Verify Your Secrets ✅\n\nLet's make sure everything is set up correctly!\n\n**Go to:** Settings → Secrets and variables → Actions → Repository secrets\n\n**You should see:**\n1. ✅ `BACKEND_ENV` with \"Updated X seconds/minutes ago\"\n2. ✅ `FRONTEND_ENV` with \"Updated X seconds/minutes ago\"\n\n**Note:** You can't view the secret values after creating them (security feature!). But you can update them anytime by clicking the name and then \"Update secret\".\n\n---\n\n## Part 5: Initial Deployment to VPS 🚀\n\nNow that everything is configured, let's get your app running for the first time!\n\n### Step 23: Clone Your Repository to VPS 📥\n\n**SSH into your VPS:**\n\n```bash\n# On your local computer\nssh -i ~/.ssh/github_deploy deploy@YOUR_SERVER_IP\n```\n\nNow let's get your code onto the server!\n\n**SSH into your VPS**:\n\n```bash\nssh deploy@YOUR_SERVER_IP\n```\n\n**Clone your repository**:\n\n```bash\n# Navigate to apps directory\ncd ~/apps\n\n# Clone your repository (replace with your actual repo URL)\ngit clone https://github.com/yourusername/headshotprobuild.git\n\n# Navigate to the project\ncd headshotprobuild\n```\n\n**Understanding these commands:**\n\n- **`git clone URL`** = Download a copy of a Git repository\n  - `git` = Version control system\n  - `clone` = Copy repository from remote server to local\n  - Creates a new folder with the repository name\n  - Downloads all files, branches, and commit history\n\n**Example with explanation:**\n```bash\ngit clone https://github.com/johndoe/myproject.git\n#         └─────────┬─────────┘ └──┬──┘ └───┬───┘\n#              GitHub server    username  repo name\n```\n\n---\n\n### Step 24: Manually Create .env Files (First Time Only) 📝\n\nFor the very first deployment, we need to manually create the `.env` files on the server. After this, GitHub Actions will handle it automatically every time you push code!\n\n**⚠️ IMPORTANT**: These files will be **overwritten** by GitHub Actions on every deployment, but we need them for the first run.\n\n**Create frontend .env.local**:\n\n```bash\ncd ~/apps/headshotprobuild/frontend\nnano .env.local\n```\n\n**Understanding these commands:**\n\n- **`nano .env.local`** = Open text editor to create/edit file\n  - `nano` = Simple, beginner-friendly text editor\n  - `.env.local` = File name to create/edit\n  - Files starting with `.` are hidden files in Linux\n\n**Using nano editor:**\n\n1. Paste your frontend environment variables (right-click or Ctrl+Shift+V)\n2. **Save the file:**\n   - Press `Ctrl + X` (exit)\n   - Press `Y` (yes, save changes)\n   - Press `Enter` (confirm filename)\n3. You're back to command line!\n\n**Nano keyboard shortcuts:**\n- `Ctrl + X` = Exit\n- `Ctrl + O` = Write Out (save without exiting)\n- `Ctrl + K` = Cut line\n- `Ctrl + U` = Paste line\n- `Ctrl + W` = Search\n\n**Create backend .env**:\n\n```bash\ncd ~/apps/headshotprobuild/backend\nnano .env\n```\n\nPaste your backend environment variables (same content you put in `BACKEND_ENV` secret), then save:\n- Press `Ctrl + X`\n- Press `Y`\n- Press `Enter`\n\n---\n\n### Step 25: Build and Start Frontend 🎨\n\nLet's get your beautiful frontend running!\n\n```bash\ncd ~/apps/headshotprobuild/frontend\n\n# Install dependencies\nbun install\n\n# Build the app\nbun run build\n\n# Start with PM2\npm2 start \"bun run start\" --name frontend\n\n# Check if it's running\npm2 list\n```\n\n---\n\n### Step 26: Build and Start Backend 🔧\n\nNow let's get your backend API up and running!\n\n```bash\ncd ~/apps/headshotprobuild/backend\n\n# Install dependencies\nbun install\n\n# Build the app (if you have a build script)\nbun run build\n\n# Start with PM2\npm2 start \"bun run start\" --name backend\n\n# Check if it's running\npm2 list\n```\n\nYou should see both apps running!\n\n---\n\n### Step 27: Make PM2 Start on Server Reboot 🔄\n\nThis ensures your apps automatically start if your server restarts!\n\n```bash\n# Generate startup script\npm2 startup systemd -u deploy --hp /home/deploy\n\n# This will print a command like:\n# sudo env PATH=$PATH:/home/deploy/.nvm/versions/node/v20.11.0/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup systemd -u deploy --hp /home/deploy\n\n# Copy and run that command (it starts with sudo env...)\n\n# Save current PM2 processes\npm2 save\n```\n\nTest by rebooting:\n\n```bash\nsudo reboot\n```\n\nAfter the server restarts, SSH back in and check:\n\n```bash\npm2 list\n```\n\nBoth apps should still be running!\n\n---\n\n\n## Part 6: DNS Configuration\n\n### Step 28: Configure Your Domain DNS\n\nGo to your domain registrar or DNS provider (like Cloudflare, Namecheap, etc.) and add these DNS records:\n\n**A Records**:\n- **Type**: A\n- **Name**: `@` (root domain)\n- **Value**: Your VPS IP (e.g., `194.238.22.106`)\n- **TTL**: Automatic or 3600\n\n- **Type**: A\n- **Name**: `www`\n- **Value**: Your VPS IP (e.g., `194.238.22.106`)\n- **TTL**: Automatic or 3600\n\n- **Type**: A\n- **Name**: `api`\n- **Value**: Your VPS IP (e.g., `194.238.22.106`)\n- **TTL**: Automatic or 3600\n\n**Important**: DNS changes can take 5 minutes to 48 hours to propagate worldwide. Usually, it's quick (5-30 minutes).\n\n**Test DNS propagation**:\n\n```bash\n# On your local computer\nnslookup yourdomain.com\nnslookup www.yourdomain.com\nnslookup api.yourdomain.com\n```\n\nAll should point to your VPS IP.\n\n---\n\n## Part 7: SSL Certificates (HTTPS)\n\n### Step 29: Install Certbot\n\n```bash\nsudo apt install -y certbot python3-certbot-nginx\n```\n\n---\n\n### Step 30: Get SSL Certificates\n\n```bash\nsudo certbot --nginx -d yourdomain.com -d www.yourdomain.com -d api.yourdomain.com\n```\n\n**Follow the prompts**:\n1. Enter your email address\n2. Agree to Terms of Service (Y)\n3. Choose whether to share email (optional)\n4. Certbot will automatically configure Nginx for HTTPS\n\n**Test automatic renewal**:\n\n```bash\nsudo certbot renew --dry-run\n```\n\nIf successful, your certificates will auto-renew before expiry!\n\n---\n\n## Part 8: Database Configuration\n\n### Step 31: Configure MongoDB Atlas\n\n1. Go to [MongoDB Atlas](https://cloud.mongodb.com/)\n2. Navigate to your cluster\n3. Click **Network Access** (left sidebar)\n4. Click **Add IP Address**\n5. Enter your VPS IP address: `YOUR_VPS_IP/32`\n6. Click **Confirm**\n\n**⚠️ Important**: Remove `0.0.0.0/0` (allow all) if it exists for better security.\n\n---\n\n## Part 9: Testing GitHub Actions Deployment 🎉\n\n### Step 32: Test Automatic Deployment\n\nThis is the moment of truth! Let's see your automatic deployment in action!\n\nNow that everything is set up, let's test the automatic deployment!\n\n**Make a small change to your code**:\n\n```bash\n# On your local computer\ncd /path/to/your/project\n\n# Make a small change (example: update a comment in any file)\necho \"// Test deployment\" \u003e\u003e frontend/app/page.tsx\n\n# Commit and push\ngit add .\ngit commit -m \"Test: Trigger GitHub Actions deployment\"\ngit push origin main\n```\n\n---\n\n### Step 33: Watch GitHub Actions Run (The Exciting Part!) 🎬\n\nLet's watch your deployment happen in real-time!\n\n1. **Open your web browser** and go to your GitHub repository\n2. Click the **\"Actions\"** tab at the top (between \"Pull requests\" and \"Projects\")\n3. You should see your workflows running!\n\n**What you'll see:**\n- 🟡 **Orange dot** = Workflow is currently running\n- 🟢 **Green checkmark** = Workflow completed successfully\n- 🔴 **Red X** = Workflow failed (don't worry, we'll fix it!)\n\n**Click on a workflow run to see details:**\n- You can watch each step execute in real-time\n- See the logs for each command\n- Watch as your `.env` file is created from secrets ✅\n- See your app being built and deployed!\n\n**What's happening (Step by Step):**\n\n1. **📥 Checkout code** - Downloads your latest code\n2. **🔐 Create .env file** - Creates `.env` from GitHub Secrets (THE MAGIC!)\n3. **✅ Verify NODE_ENV** - Makes sure it's set to production\n4. **🔧 Install Bun** - Sets up the Bun runtime\n5. **📦 Install dependencies** - Runs `bun install`\n6. **🏗️ Build** - Compiles your application\n7. **🚀 Restart** - Restarts your app with PM2\n\n**This all happens automatically every time you push code!** 🎉\n\n---\n\n### Step 34: Verify Deployment ✅\n\nLet's make sure everything is working!\n\n**Check if apps are running**:\n\n```bash\nssh deploy@YOUR_SERVER_IP\npm2 list\npm2 logs frontend --lines 50\npm2 logs backend --lines 50\n```\n\n**Test your websites**:\n- Frontend: `https://yourdomain.com`\n- Backend API: `https://api.yourdomain.com/health` (create a health endpoint)\n\n---\n\n## 🎯 Understanding How .env Files Work\n\n### The Complete Flow (Step-by-Step)\n\n**1. You Store Secrets in GitHub**:\n- Go to GitHub → Settings → Secrets\n- Add `FRONTEND_ENV` and `BACKEND_ENV` with all your environment variables\n\n**2. You Push Code to GitHub**:\n```bash\ngit push origin main\n```\n\n**3. GitHub Actions Triggers**:\n- GitHub detects your push\n- Reads `.github/workflows/deploy-frontend.yml` and `.github/workflows/deploy-backend.yml`\n- Starts the deployment process\n\n**4. GitHub Actions Builds Your App**:\n- Runs on GitHub's servers (not your VPS)\n- Installs dependencies\n- Builds your application\n\n**5. GitHub Actions Connects to Your VPS**:\n- Uses SSH with the private key from `VPS_SSH_KEY` secret\n- Connects as the `deploy` user\n\n**6. GitHub Actions Updates Code on VPS**:\n```bash\ncd ~/apps/headshotprobuild/backend\ngit pull origin main\n```\n- This pulls your latest code\n- **NOTE**: `git pull` does NOT touch `.env` because it's in `.gitignore`\n\n**7. GitHub Actions Creates .env File on VPS** ⭐ **THIS IS THE MAGIC**:\n```bash\necho \"${{ secrets.BACKEND_ENV }}\" \u003e .env\n```\n- GitHub takes the content from `BACKEND_ENV` secret\n- Writes it to the `.env` file on your VPS\n- **Location**: `/home/deploy/apps/headshotprobuild/backend/.env`\n\n**Example**: If your `BACKEND_ENV` secret contains:\n```env\nMONGODB_URI=mongodb+srv://user:pass@cluster.mongodb.net/db\nAWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE\nPORT=8000\n```\n\nThen the file `/home/deploy/apps/headshotprobuild/backend/.env` on your VPS will contain:\n```env\nMONGODB_URI=mongodb+srv://user:pass@cluster.mongodb.net/db\nAWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE\nPORT=8000\n```\n\n**8. GitHub Actions Restarts Your App**:\n```bash\npm2 restart backend\n```\n- PM2 stops the app\n- PM2 starts it again\n- Your app runs in the directory `/home/deploy/apps/headshotprobuild/backend`\n- Your app automatically reads `.env` file from that directory\n- Your app now has access to all environment variables\n\n**9. Your App Uses the Secrets**:\n```typescript\n// In your backend code:\nimport mongoose from 'mongoose';\n\n// This reads from .env file automatically\nconst uri = process.env.MONGODB_URI; // ✅ Works!\n\nmongoose.connect(uri);\n```\n\n---\n\n### Where Are the .env Files?\n\n**❌ NOT here**:\n- Not in your Git repository\n- Not in GitHub\n- Not publicly visible anywhere\n\n**✅ Here**:\n- **Frontend**: `/home/deploy/apps/headshotprobuild/frontend/.env.local` on your VPS\n- **Backend**: `/home/deploy/apps/headshotprobuild/backend/.env` on your VPS\n\n**To verify** (SSH into your server):\n```bash\nssh deploy@YOUR_SERVER_IP\n\n# Check backend .env\nls -la ~/apps/headshotprobuild/backend/.env\ncat ~/apps/headshotprobuild/backend/.env\n\n# Check frontend .env.local\nls -la ~/apps/headshotprobuild/frontend/.env.local\ncat ~/apps/headshotprobuild/frontend/.env.local\n```\n\n---\n\n### What Happens on Each Deployment?\n\nEvery time you push code to GitHub:\n\n1. **GitHub Actions runs**\n2. **Pulls latest code**: `git pull origin main`\n   - Your `.env` file is NOT affected (it's in `.gitignore`)\n3. **Overwrites .env file**: `echo \"${{ secrets.BACKEND_ENV }}\" \u003e .env`\n   - Creates a fresh `.env` file from GitHub Secrets\n   - If you updated secrets in GitHub, your VPS gets the new values\n4. **Restarts app**: `pm2 restart backend`\n   - App reads the updated `.env` file\n   - App uses the new/updated environment variables\n\n---\n\n## 📝 Common Deployment Scenarios\n\n### Scenario 1: Update Code Only\n\n```bash\n# Make code changes\ngit add .\ngit commit -m \"Fix bug in user authentication\"\ngit push origin main\n```\n\n**What happens**:\n- GitHub Actions deploys new code\n- `.env` file stays the same (uses existing GitHub Secrets)\n- App restarts with new code\n\n---\n\n### Scenario 2: Update Environment Variables\n\n**To update secrets**:\n1. Go to GitHub → Settings → Secrets and variables → Actions\n2. Find the secret (e.g., `BACKEND_ENV`)\n3. Click **Update**\n4. Change the value (e.g., add a new API key)\n5. Click **Update secret**\n\n**Then trigger a deployment**:\n```bash\ngit commit --allow-empty -m \"Update environment variables\"\ngit push origin main\n```\n\n**What happens**:\n- GitHub Actions pulls code (no changes)\n- GitHub Actions overwrites `.env` file with NEW secrets\n- App restarts and uses new environment variables\n\n---\n\n### Scenario 3: Add New Environment Variable\n\n**1. Update your GitHub Secret**:\n- Go to GitHub → Settings → Secrets\n- Edit `BACKEND_ENV`\n- Add new variable: `NEW_API_KEY=abc123`\n\n**2. Update your code to use it**:\n```typescript\nconst newApiKey = process.env.NEW_API_KEY;\n```\n\n**3. Deploy**:\n```bash\ngit add .\ngit commit -m \"Add support for new API key\"\ngit push origin main\n```\n\n**What happens**:\n- GitHub Actions deploys new code\n- GitHub Actions writes `.env` with new variable\n- App restarts and can use `NEW_API_KEY`\n\n---\n\n## 🔧 Troubleshooting\n\n### Deployment Failed?\n\n**Check GitHub Actions logs**:\n1. Go to GitHub → Actions tab\n2. Click on the failed workflow\n3. Click on the job that failed\n4. Read the error message\n\n**Common issues**:\n- **SSH connection failed**: Check `VPS_SSH_KEY` secret is correct\n- **Permission denied**: Ensure `deploy` user has proper permissions\n- **Build failed**: Check if dependencies are correctly installed\n\n---\n\n### App Not Starting?\n\n**Check PM2 logs**:\n```bash\nssh deploy@YOUR_SERVER_IP\npm2 logs backend --lines 100\npm2 logs frontend --lines 100\n```\n\n**Common issues**:\n- **Port already in use**: Another process is using port 3000 or 8000\n- **Environment variable missing**: Check `.env` file exists and has all required variables\n- **Database connection error**: Verify MongoDB Atlas IP whitelist includes your VPS IP\n\n---\n\n### Can't Access Website?\n\n**Check Nginx**:\n```bash\nsudo nginx -t                    # Test configuration\nsudo systemctl status nginx       # Check if running\nsudo systemctl restart nginx      # Restart Nginx\n```\n\n**Check DNS**:\n```bash\nnslookup yourdomain.com\n```\n\n**Check SSL**:\n```bash\nsudo certbot certificates\n```\n\n---\n\n## 🎓 Teaching Points \n\n### 1. **Why GitHub Actions?**\n- **Automatic**: No manual SSH, no manual deployments\n- **Consistent**: Same process every time\n- **Safe**: Secrets never in code\n- **Auditable**: See history of all deployments\n\n### 2. **Why PM2?**\n- Keeps apps running 24/7\n- Auto-restarts if app crashes\n- Manages logs\n- Starts apps on server reboot\n\n### 3. **Why Nginx?**\n- Handles HTTPS (SSL certificates)\n- Routes traffic (frontend vs backend)\n- Serves static files efficiently\n- Acts as reverse proxy\n\n### 4. **Why Separate .env Files?**\n- **Never in Git**: Prevents accidental exposure\n- **GitHub Secrets**: Encrypted storage\n- **VPS only**: Lives where app runs\n- **Easy updates**: Change secrets without changing code\n\n### 5. **Security Layers**:\n- Firewall (UFW): Only specific ports open\n- SSH Keys: No password login\n- MongoDB Atlas: Only VPS IP allowed\n- GitHub Secrets: Encrypted storage\n- HTTPS: All traffic encrypted\n\n---\n\n## 🚀 Next Steps\n\n### Production Checklist\n\n- [x] VPS set up with firewall\n- [ ] Deploy user created (not using root)\n- [ ] SSH keys configured\n- [ ] Node.js, Bun, PM2 installed\n- [ ] Nginx installed and configured\n- [ ] DNS records pointing to VPS\n- [ ] SSL certificates installed (Certbot)\n- [ ] MongoDB Atlas IP whitelist configured\n- [ ] GitHub Secrets added (all 6 secrets)\n- [ ] GitHub Actions workflows created\n- [ ] Initial deployment successful\n- [ ] PM2 configured to start on reboot\n- [ ] Automatic deployment tested\n\n---\n\n## 📚 Additional Resources\n\n- [GitHub Actions Documentation](https://docs.github.com/en/actions)\n- [PM2 Documentation](https://pm2.keymetrics.io/docs/)\n- [Nginx Documentation](https://nginx.org/en/docs/)\n- [Let's Encrypt Documentation](https://letsencrypt.org/docs/)\n- [MongoDB Atlas Documentation](https://docs.atlas.mongodb.com/)\n\n---\n\n## 🆘 Need Help?\n\nIf you encounter issues:\n\n1. Check the error message carefully\n2. Read the relevant section in this guide\n3. Check logs: `pm2 logs`, `sudo nginx -t`, GitHub Actions logs\n4. Search for the error message online\n5. Ask for help with specific error messages\n\n---\n\n## 🎉 Congratulations! You Did It!\n\nYou've successfully set up a professional deployment system with:\n- ✅ Self-hosted GitHub Actions runner\n- ✅ Automatic deployments on every push\n- ✅ Secure environment variable management\n- ✅ HTTPS with SSL certificates\n- ✅ Process management with PM2\n- ✅ Reverse proxy with Nginx\n\n**Your workflow is now:**\n1. Make code changes on your local computer\n2. Push to GitHub: `git push origin main`\n3. GitHub Actions automatically deploys everything\n4. Your app updates in seconds! 🚀\n\n---\n\n## 📝 Quick Reference Commands\n\n### SSH Connection Commands\n\n**Mac/Linux:**\n```bash\n# Connect to your VPS\nssh -i ~/.ssh/github_deploy deploy@YOUR_SERVER_IP\n\n# Or if using default key\nssh deploy@YOUR_SERVER_IP\n```\n\n**Windows PowerShell:**\n```powershell\n# Connect to your VPS\nssh -i $HOME\\.ssh\\github_deploy deploy@YOUR_SERVER_IP\n```\n\n**Windows Git Bash:**\n```bash\n# Connect to your VPS\nssh -i ~/.ssh/github_deploy deploy@YOUR_SERVER_IP\n```\n\n### PM2 Commands (On your VPS)\n\n```bash\n# View all running apps\npm2 list\n\n# View logs\npm2 logs backend\npm2 logs frontend\n\n# Restart apps\npm2 restart backend\npm2 restart frontend\npm2 restart all\n\n# Stop apps\npm2 stop backend\npm2 stop frontend\n\n# Delete apps\npm2 delete backend\npm2 delete frontend\n\n# Save PM2 configuration\npm2 save\n\n# View detailed info\npm2 show backend\n```\n\n**Understanding PM2 commands:**\n\n- **`pm2 list`** = Show all PM2-managed apps\n  - Shows status, memory usage, CPU usage, restart count\n\n- **`pm2 logs appname`** = View real-time logs\n  - Shows both stdout (normal output) and stderr (errors)\n  - Press `Ctrl + C` to stop viewing\n  - Add `--lines 100` to see last 100 lines\n\n- **`pm2 restart appname`** = Restart a specific app\n  - Stops then starts the app\n  - Reloads code and .env changes\n  - Use `all` to restart all apps\n\n- **`pm2 stop appname`** = Stop app without removing it\n  - App still in PM2 list but not running\n  - Use `pm2 start appname` to start again\n\n- **`pm2 delete appname`** = Remove app from PM2\n  - Completely removes from PM2 management\n  - Must re-add with `pm2 start` command\n\n- **`pm2 save`** = Save current PM2 process list\n  - Important after making changes\n  - Ensures apps restart after server reboot\n\n- **`pm2 show appname`** = Detailed info about one app\n  - Memory usage, uptime, error logs, etc.\n\n### Nginx Commands (On your VPS)\n\n```bash\n# Test configuration\nsudo nginx -t\n\n# Restart Nginx\nsudo systemctl restart nginx\n\n# Check status\nsudo systemctl status nginx\n\n# View error logs\nsudo tail -f /var/log/nginx/error.log\n\n# View access logs\nsudo tail -f /var/log/nginx/access.log\n```\n\n**Understanding these commands:**\n\n- **`tail -f filename`** = View end of file in real-time\n  - `tail` = Show last part of file (default: last 10 lines)\n  - `-f` = \"follow\" - keep watching as new lines are added\n  - Perfect for monitoring live logs\n  - Press `Ctrl + C` to stop\n  - Add `-n 100` to see last 100 lines first\n\n- **Log file locations:**\n  - `/var/log/nginx/error.log` = Nginx error messages\n  - `/var/log/nginx/access.log` = All incoming requests\n\n### SSL Certificate Commands (On your VPS)\n\n```bash\n# Renew certificates manually\nsudo certbot renew\n\n# Test renewal\nsudo certbot renew --dry-run\n\n# View installed certificates\nsudo certbot certificates\n```\n\n### Deployment Commands (On your local computer)\n\n```bash\n# Deploy changes\ngit add .\ngit commit -m \"Your commit message\"\ngit push origin main\n\n# Force trigger deployment (no changes)\ngit commit --allow-empty -m \"Trigger deployment\"\ngit push origin main\n```\n\n**Understanding Git commands:**\n\n- **`git add .`** = Stage all changes for commit\n  - `git add` = Add files to staging area (prepare for commit)\n  - `.` = Current directory (all changed files)\n  - Alternative: `git add filename` for specific file\n\n- **`git commit -m \"message\"`** = Save changes with description\n  - `git commit` = Create a commit (snapshot of changes)\n  - `-m \"message\"` = Include commit message directly\n  - Without `-m`, it opens a text editor for the message\n\n- **`git push origin main`** = Upload commits to GitHub\n  - `git push` = Send commits to remote repository\n  - `origin` = Name of remote repository (usually GitHub)\n  - `main` = Branch name to push to\n\n- **`git commit --allow-empty`** = Create commit with no changes\n  - `--allow-empty` = Create commit even if nothing changed\n  - Useful to trigger GitHub Actions without code changes\n  - `--` prefix means it's a long option (full word)\n\n### Troubleshooting Commands (On your VPS)\n\n```bash\n# Check if runner is working\nsudo systemctl status actions.runner.*\n\n# Restart runner\ncd ~/actions-runner\nsudo ./svc.sh restart\n\n# View GitHub Actions runner logs\ncd ~/actions-runner\ntail -f _diag/Runner_*.log\n\n# Check disk space\ndf -h\n\n# Check memory usage\nfree -h\n\n# Check running processes\nps aux | grep bun\nps aux | grep node\n```\n\n**Understanding these commands:**\n\n- **`systemctl status actions.runner.*`** = Check runner service status\n  - `*` = Wildcard (matches any runner name)\n  - Shows if runner is active, inactive, or failed\n\n- **`./svc.sh restart`** = Restart runner service\n  - `./` = Run script in current directory\n  - `.sh` = Shell script file\n\n- **`df -h`** = Show disk space usage\n  - `df` = \"disk free\" (show available disk space)\n  - `-h` = \"human-readable\" (shows GB instead of bytes)\n  - Look for \"Use%\" column - if above 90%, you're running out of space\n\n- **`free -h`** = Show memory (RAM) usage\n  - `free` = Display memory information\n  - `-h` = Human-readable format\n  - Shows total, used, free, and available memory\n\n- **`ps aux | grep processname`** = Find running processes\n  - `ps` = \"process status\" (list running processes)\n  - `aux` = Show all processes, user-oriented format\n    - `a` = Show all users' processes\n    - `u` = Show user/owner\n    - `x` = Show processes without terminal\n  - `|` = Pipe - send output of `ps` to `grep`\n  - `grep bun` = Filter to only show lines containing \"bun\"\n\n**Example of pipe usage:**\n```bash\nps aux | grep bun\n#  │      │     └── Filter for \"bun\"\n#  │      └──────── Pipe (send output to next command)\n#  └─────────────── List all processes\n```\n\n---\n\n## 🔄 How to Update Environment Variables\n\n**When you need to change API keys, passwords, or any environment variable:**\n\n1. **Go to GitHub:**\n   - Settings → Secrets and variables → Actions\n   - Click on the secret you want to update (e.g., `BACKEND_ENV`)\n   - Click \"Update secret\"\n   - Paste the new content\n   - Click \"Update secret\"\n\n2. **Trigger a deployment:**\n   ```bash\n   git commit --allow-empty -m \"Update environment variables\"\n   git push origin main\n   ```\n\n3. **GitHub Actions will:**\n   - Create a fresh `.env` file with your new secrets\n   - Restart your app\n   - Your app now uses the updated variables!\n\n---\n\n## 🆘 Common Issues and Solutions\n\n### Issue 1: Workflow Fails on \".env file not found\"\n\n**Solution:** Make sure your GitHub Secrets (`BACKEND_ENV` and `FRONTEND_ENV`) are set correctly.\n\n### Issue 2: \"NODE_ENV must be set to production\"\n\n**Solution:** Add `NODE_ENV=production` as the first line in your GitHub Secrets.\n\n### Issue 3: Runner shows as \"Offline\"\n\n**Solution:**\n```bash\nssh deploy@YOUR_SERVER_IP\ncd ~/actions-runner\nsudo ./svc.sh status\nsudo ./svc.sh restart\n```\n\n### Issue 4: App not accessible from browser\n\n**Solution:**\n```bash\n# Check if apps are running\npm2 list\n\n# Check Nginx\nsudo nginx -t\nsudo systemctl restart nginx\n\n# Check firewall\nsudo ufw status\n```\n\n### Issue 5: SSL Certificate Error\n\n**Solution:**\n```bash\n# Renew certificate\nsudo certbot renew --force-renewal\nsudo systemctl restart nginx\n```\n\n---\n\n## 🎓 What You've Learned\n\nThrough this guide, you've learned:\n\n1. **Server Administration**\n   - Setting up and securing a Linux VPS\n   - Managing users and permissions\n   - Configuring firewalls\n\n2. **SSH \u0026 Security**\n   - Generating and using SSH keys\n   - Key-based authentication\n   - Secure file permissions\n\n3. **DevOps Practices**\n   - CI/CD with GitHub Actions\n   - Self-hosted runners\n   - Environment variable management\n   - Secret management\n\n4. **Web Server Configuration**\n   - Nginx reverse proxy setup\n   - SSL/TLS certificates\n   - Domain configuration\n\n5. **Process Management**\n   - PM2 for application lifecycle\n   - Auto-restart on crashes\n   - Log management\n\n6. **Deployment Automation**\n   - Automatic deployments on git push\n   - Build and restart workflows\n   - Zero-downtime deployments\n\n---\n\n## 🚀 Next Steps\n\nNow that your deployment is working, consider:\n\n1. **Set up monitoring:**\n   - Use PM2 Plus for app monitoring\n   - Set up uptime monitoring (UptimeRobot, etc.)\n   - Configure error logging (Sentry, etc.)\n\n2. **Improve security:**\n   - Set up fail2ban to prevent brute force attacks\n   - Configure automatic security updates\n   - Regular backup strategy\n\n3. **Performance optimization:**\n   - Enable Nginx caching\n   - Set up CDN for static assets\n   - Database indexing and optimization\n\n4. **Add staging environment:**\n   - Create a `staging` branch\n   - Set up separate workflow for staging\n   - Test changes before production\n\n---\n\n## 📚 Additional Resources\n\n- [GitHub Actions Documentation](https://docs.github.com/en/actions)\n- [PM2 Documentation](https://pm2.keymetrics.io/docs/)\n- [Nginx Documentation](https://nginx.org/en/docs/)\n- [Let's Encrypt Documentation](https://letsencrypt.org/docs/)\n- [MongoDB Atlas Documentation](https://docs.atlas.mongodb.com/)\n- [Bun Documentation](https://bun.sh/docs)\n- [Next.js Documentation](https://nextjs.org/docs)\n\n---\n\n## 👨‍💻 About the Author\n\n**Mohamud Osman**  \n*Founder of Dugsiiye*\n\n**Connect with me:**\n\n- 🌐 **Website**: [dugsiiye.com](https://dugsiiye.com)\n- 💻 **GitHub**: [@mchamoudadev](https://github.com/mchamoudadev)\n- 📺 **YouTube**: [@dugsiiye](https://youtube.com/@dugsiiye)\n- 💼 **LinkedIn**: [Mohamed Osman](https://linkedin.com/in/mchamoudadev)\n- 🐦 **X (Twitter)**: [@mchamoudadev](https://x.com/dugsiiye)\n\n**Visit [dugsiiye.com](https://dugsiiye.com) for more tutorials, projects, and full-stack software engineering content!**\n\n---\n\n**🎉 Amazing Work!** You've completed a professional-grade deployment setup that many companies use in production. You should be proud! \n\nEvery time you push code to GitHub, it will automatically deploy to your VPS. Your secrets are safe in GitHub Secrets, and your deployment is fully automated. 🚀\n\n**Happy Coding!** 💻✨\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmchamoudadev%2Fheadshotpro-frontend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmchamoudadev%2Fheadshotpro-frontend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmchamoudadev%2Fheadshotpro-frontend/lists"}