ci: implement 3-stage deployment pipeline with semantic-release
This commit is contained in:
parent
af8d86dedb
commit
d4d84ad0db
13 changed files with 271 additions and 169 deletions
|
|
@ -1,21 +1,50 @@
|
||||||
|
# Dependencies and build artifacts
|
||||||
node_modules
|
node_modules
|
||||||
.nuxt
|
.nuxt
|
||||||
.output
|
.output
|
||||||
|
dist
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Git and version control
|
||||||
.git
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Environment files
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
|
||||||
|
# Logs
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
*.log
|
|
||||||
|
# Testing and coverage
|
||||||
coverage
|
coverage
|
||||||
.nyc_output
|
.nyc_output
|
||||||
dist
|
|
||||||
|
# Temporary files
|
||||||
tmp
|
tmp
|
||||||
temp
|
temp
|
||||||
.cache
|
|
||||||
.parcel-cache
|
# Semantic-release artifacts (copied explicitly in final stage)
|
||||||
|
CHANGELOG.md
|
||||||
|
|
||||||
|
# Development files
|
||||||
|
bun.lock
|
||||||
|
|
||||||
|
# CI/CD files that don't need to be in the container
|
||||||
|
.github/
|
||||||
|
.releaserc.json
|
||||||
|
|
||||||
|
# Documentation that gets copied explicitly in final stage
|
||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
|
CLAUDE.md
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -19,7 +19,6 @@ logs
|
||||||
.idea
|
.idea
|
||||||
public/headshot_pp.png
|
public/headshot_pp.png
|
||||||
.secrets
|
.secrets
|
||||||
.claude
|
|
||||||
|
|
||||||
# Local env files
|
# Local env files
|
||||||
.env
|
.env
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,44 @@
|
||||||
{
|
{
|
||||||
"branches": [
|
"branches": ["main"],
|
||||||
"main"
|
"preset": "conventionalcommits",
|
||||||
],
|
"presetConfig": {
|
||||||
|
"types": [
|
||||||
|
{ "type": "breaking", "section": "BREAKING CHANGES" },
|
||||||
|
{ "type": "feat", "section": "New Features" },
|
||||||
|
{ "type": "new", "section": "New Features" },
|
||||||
|
{ "type": "update", "section": "Dependencies" },
|
||||||
|
{ "type": "upgrade", "section": "Dependencies" },
|
||||||
|
{ "type": "deps", "section": "Dependencies" },
|
||||||
|
{ "type": "fix", "section": "Bug Fixes" },
|
||||||
|
{ "type": "build", "section": "Building and CI" },
|
||||||
|
{ "type": "ci", "section": "Building and CI" },
|
||||||
|
{ "type": "chore", "hidden": true },
|
||||||
|
{ "type": "docs", "hidden": true },
|
||||||
|
{ "type": "refactor", "hidden": true }
|
||||||
|
]
|
||||||
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
[
|
||||||
"@semantic-release/commit-analyzer",
|
"@semantic-release/commit-analyzer",
|
||||||
|
{
|
||||||
|
"releaseRules": [
|
||||||
|
{ "breaking": true, "release": "major" },
|
||||||
|
{ "revert": true, "release": "patch" },
|
||||||
|
{ "type": "breaking", "release": "major" },
|
||||||
|
{ "type": "feat", "release": "minor" },
|
||||||
|
{ "type": "update", "release": "minor" },
|
||||||
|
{ "type": "upgrade", "release": "minor" },
|
||||||
|
{ "type": "deps", "release": "minor" },
|
||||||
|
{ "type": "new", "release": "minor" },
|
||||||
|
{ "type": "fix", "release": "patch" },
|
||||||
|
{ "type": "build", "release": false },
|
||||||
|
{ "type": "ci", "release": false },
|
||||||
|
{ "type": "chore", "release": false },
|
||||||
|
{ "type": "docs", "release": false },
|
||||||
|
{ "type": "refactor", "release": false }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"@semantic-release/release-notes-generator",
|
"@semantic-release/release-notes-generator",
|
||||||
"@semantic-release/changelog",
|
"@semantic-release/changelog",
|
||||||
[
|
[
|
||||||
|
|
@ -16,7 +51,7 @@
|
||||||
[
|
[
|
||||||
"@semantic-release/git",
|
"@semantic-release/git",
|
||||||
{
|
{
|
||||||
"assets": ["CHANGELOG.md", "package.json", "package-lock.json", "bun.lock"],
|
"assets": ["CHANGELOG.md", "package.json", "package-lock.json"],
|
||||||
"message": "chore: Release ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
"message": "chore: Release ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
60
Dockerfile
60
Dockerfile
|
|
@ -1,19 +1,62 @@
|
||||||
FROM node:24-slim
|
# Stage 1: Runtime dependencies
|
||||||
|
FROM node:24-slim AS deps
|
||||||
|
SHELL ["bash", "-exc"]
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y python3 make g++ curl net-tools && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN --mount=type=bind,source=package-lock.json,target=package-lock.json --mount=type=bind,source=package.json,target=package.json npm ci --omit=dev && npm cache clean --force
|
||||||
|
|
||||||
|
# Stage 2: Build dependencies - Includes dev dependencies for build
|
||||||
|
FROM node:24-slim AS build-deps
|
||||||
|
SHELL ["bash", "-exc"]
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y python3 make g++ curl net-tools && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install all dependencies including dev dependencies for build
|
||||||
|
RUN --mount=type=bind,source=package-lock.json,target=package-lock.json --mount=type=bind,source=package.json,target=package.json npm ci && npm cache clean --force
|
||||||
|
|
||||||
|
# Stage 3: Source and build - Cache invalidated when source code changes
|
||||||
|
FROM build-deps AS builder
|
||||||
|
SHELL ["bash", "-exc"]
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy source code (excluding semantic-release artifacts)
|
||||||
|
COPY nuxt.config.ts tsconfig.json tailwind.config.js eslint.config.mjs ./
|
||||||
|
COPY app/ ./app/
|
||||||
|
COPY server/ ./server/
|
||||||
|
COPY public/ ./public/
|
||||||
|
|
||||||
|
# Build the application (this is cached unless source code changes)
|
||||||
|
RUN npx nuxt build
|
||||||
|
|
||||||
|
# Stage 4: Runtime assembly - Only invalidated by version/metadata changes
|
||||||
|
FROM deps AS runtime
|
||||||
|
|
||||||
ARG VERSION=0.0.0
|
ARG VERSION=0.0.0
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y python3 make g++ curl net-tools && rm -rf /var/lib/apt/lists/*
|
# Copy production node_modules from deps stage
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
|
||||||
COPY package.json package-lock.json* ./
|
# Copy built application from builder stage
|
||||||
|
COPY --from=builder /app/.output ./.output
|
||||||
|
|
||||||
RUN npm ci
|
# Copy package configs
|
||||||
|
# These change frequently but don't affect the expensive build steps above
|
||||||
COPY . .
|
COPY package.json package-lock.json ./
|
||||||
|
|
||||||
RUN npx nuxt build
|
|
||||||
|
|
||||||
|
# Set runtime environment
|
||||||
ENV NUXT_HOST=0.0.0.0
|
ENV NUXT_HOST=0.0.0.0
|
||||||
ENV NUXT_PORT=3000
|
ENV NUXT_PORT=3000
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
@ -22,6 +65,7 @@ ENV APP_VERSION=${VERSION}
|
||||||
ENV PUID=1000
|
ENV PUID=1000
|
||||||
ENV PGID=1000
|
ENV PGID=1000
|
||||||
|
|
||||||
|
# Set up data directory and permissions
|
||||||
RUN mkdir -p data && chown -R ${PUID}:${PGID} data && chown -R ${PUID}:${PGID} /app && chmod 755 /app data
|
RUN mkdir -p data && chown -R ${PUID}:${PGID} data && chown -R ${PUID}:${PGID} /app && chmod 755 /app data
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
|
||||||
133
README.md
133
README.md
|
|
@ -1,95 +1,142 @@
|
||||||
# 🚀 Personal Portfolio Website
|
# 🚀 Portfolio Bonanza
|
||||||
|
|
||||||
My schnazzy portfolio turbocharged with Nuxt 4 and zero-downtime deployment wizardry! ✨
|
A spicy little portfolio site with some absolutely ridiculous SMS shenanigans built on Nuxt 4! Looks like things are getting too spicy for the pepper! 🌶️
|
||||||
|
|
||||||
## ⭐ What's Inside
|
## ✨ The Good Stuff
|
||||||
|
|
||||||
- 🌙 **Dark UI** with DaisyUI components
|
- 🌙 **Night mode everything** - DaisyUI components that won't burn your eyeballs
|
||||||
- 📱 **SMS Contact Form** with phone verification (because emails are so last millennium)
|
- 📱 **SMS contact form** - Because I get wayyy too many emails already
|
||||||
- 🎨 **Smooth animations** and typing effects
|
- 🎭 **Snazzy animations** - ScrollReveal + Typed.js doing their thang
|
||||||
- 🐳 **Zero-downtime deployments** via Docker + HAProxy
|
- 🐳 **Zero-downtime deploys** - Blue-green magic with Docker & HAProxy
|
||||||
- 🔐 **WireGuard tunnel** to home SMS gateway
|
- 🔧 **Overengineered infrastructure** - We like to do things the hard way over here
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## 🏃♂️ Getting Started
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
# Grab the dependencies
|
||||||
bun install
|
bun install
|
||||||
|
|
||||||
# Start development server
|
# Copy example env file and edit
|
||||||
|
cp .env.example .env && nano .env
|
||||||
|
|
||||||
|
# Fire up the dev server
|
||||||
bun run dev
|
bun run dev
|
||||||
|
|
||||||
# Build for production
|
# Build the contraption
|
||||||
bun run build
|
bun run build
|
||||||
|
|
||||||
# Generate static site
|
# Bake it
|
||||||
bun run generate
|
bun run generate
|
||||||
```
|
```
|
||||||
|
|
||||||
**Coffee Levels:**
|
**Current vibe levels:**
|
||||||
☕
|
😴 sleepy
|
||||||
☕☕
|
🙂 caffeinated
|
||||||
☕☕☕
|
🤓 productive
|
||||||
☕☕☕☕
|
🔥 on fire
|
||||||
🚨🚨🚨🚨🚨
|
💀 send halp
|
||||||
|
|
||||||
## 📱 Contact System Flow
|
## 📞 Contact Form Wizardry
|
||||||
|
|
||||||
A bamboozling two-step form: collect message → verify phone → SMS me directly!
|
Two-step verification dance: drop your message → prove you're human with SMS → message gets yeeted to my phone via some questionable infrastructure choices.
|
||||||
Rate limited to prevent spam-a-geddon. 🚫
|
|
||||||
|
|
||||||
## 🏗️ Architecture
|
Rate-limited because spam just sucksssss! 🛡️
|
||||||
|
|
||||||
|
## 🏗️ Infrastructure Tomfoolery
|
||||||
|
|
||||||
|
Here's where things get ~completely~ unhinged:
|
||||||
|
|
||||||
```
|
```
|
||||||
🌍 Internet → VPS → 🔐 WireGuard → 🏠 Android SMS Gateway
|
🌐 Your Browser → Some VPS → 🔐 WireGuard Tunnel
|
||||||
|
→ 🏠 Home Network → 🧟♂️ My Frankenstein Phone/SMS-Gateway
|
||||||
|
→ 📱 Someone's Legit Regular Phone
|
||||||
```
|
```
|
||||||
|
|
||||||
Copy `.env.example` to `.env` for configuration.
|
### The SMS Gateway Setup
|
||||||
|
|
||||||
|
I'm running the `android-sms-gateway` app on a completely deranged setup:
|
||||||
|
- **Hardware:** Pixel 1 with the battery surgically removed (no spicy pillows please)
|
||||||
|
- **Power:** Permanently plugged into the wall like some kind of cursed landline
|
||||||
|
- **Network:** Hardwired ethernet via USB-C adapter ("WhyFight" with WiFi?)
|
||||||
|
- **Purpose:** Sits there 24/7 just waiting to send OTPs and forward your messages to my actual phone
|
||||||
|
|
||||||
|
The whole contraption lives on my home network and the VPS reaches it through a WireGuard tunnel because apparently I enjoy making simple things complicated just to save $2 a month!
|
||||||
|
|
||||||
|
## 🌳 Git Workflow Madness
|
||||||
|
|
||||||
|
This repo follows a three-branch strategy with automated promotions because manually managing branches is for chumps:
|
||||||
|
|
||||||
|
- **`dev`** - Where the magic happens! All new features and fixes go here
|
||||||
|
- **`staging`** - Integration testing playground, auto-promoted from develop daily Monday-Friday at 13:00 UTC/05:00 AM PST
|
||||||
|
- **`main`** - Production branch, gets promoted from staging weekly on Mondays at 12:00 UTC/04:00 AM PST
|
||||||
|
|
||||||
|
### The Repository Setup
|
||||||
|
|
||||||
|
We're running a bit of a funky setup here:
|
||||||
|
- **Primary repo:** Self-hosted Forgejo instance (because it's more fun that way!)
|
||||||
|
- **Mirror:** GitHub (push-only mirror for CI/CD and visibility)
|
||||||
|
- **CI/CD:** GitHub Actions (triggered by the mirror's pushes or scheduled)
|
||||||
|
|
||||||
|
So the workflow is: push to Forgejo origin → auto-mirrors to GitHub → GitHub Actions does the heavy lifting.
|
||||||
|
|
||||||
|
### Branch Promotion Dance 💃
|
||||||
|
|
||||||
|
**Daily (develop → staging):**
|
||||||
|
- Checks if develop has new commits since last staging update
|
||||||
|
- Validates that the latest build actually passed
|
||||||
|
- Fast-forward merge to staging (keeps history clean)
|
||||||
|
- Supports `[skip ci]` in commit messages for any oopsies or boo-boos
|
||||||
|
|
||||||
|
**Weekly (staging → main):**
|
||||||
|
- Runs semantic-release on staging for proper versioning
|
||||||
|
- Fast-forward merge to main for production release
|
||||||
|
- Some branch rebasing gymnastics to keep develop up-to-date
|
||||||
|
- Also supports `[skip ci]`
|
||||||
|
|
||||||
## 🚢 Deployment Shenanigans
|
## 🚢 Deployment Shenanigans
|
||||||
|
|
||||||
Push to `staging` or `release` branches to trigger blue-green deployments!
|
Any (automated) pushes to `staging` or `main` branches to trigger blue-green staging and production deployments! Easy peasy lemon squeezy!
|
||||||
|
|
||||||
### Blue-Green Magic ✨
|
### Blue-Green Magic ✨
|
||||||
1. **Build Phase:** Build new containers alongside old ones
|
1. **Build Phase:** Build new containers and validate
|
||||||
2. **Deploy Phase:** Health check the newbies
|
2. **Deploy Phase:** Health check the newbies
|
||||||
3. **Switch Phase:** HAProxy traffic switcheroo
|
3. **Switch Phase:** HAProxy config update and traffic switcheroo
|
||||||
4. **Cleanup Phase:** Cleanup old containers
|
4. **Cleanup Phase:** Nuke old containers from high orbit
|
||||||
5. **Moon Phases:** 🌑 🌒 🌓 🌔 🌝 🌖 🌗 🌘 🌚
|
5. **Moon Phases:** 🌑 🌒 🌓 🌔 🌝 🌖 🌗 🌘 🌚
|
||||||
|
|
||||||
### GitHub Secrets Setup
|
### GitHub Secrets Setup
|
||||||
Set these in your repo for deployment thingamajigs:
|
Deployments generate the needed .env from GitHub secrets, so make sure to slap some secrets up first!
|
||||||
- `DEPLOY_KEY`, `DEPLOY_HOST` - SSH access stuff
|
|
||||||
- SMS gateway credentials and phone number
|
|
||||||
- `NUXT_SUPER_SECRET_SALT` - for cryptographic tomfoolery
|
|
||||||
|
|
||||||
### WireGuard Setup
|
### WireGuard Setup
|
||||||
Copy `wireguard/wg0.conf.template` → `wg0.conf` and fill in your tunnel deets.
|
The stack is rocking gluetun, so just set up your WIREGUARD_ environment variables in Github secrets:
|
||||||
|
|
||||||
## 🔒 Security Fortress
|
## 🔒 Security Fortress
|
||||||
|
|
||||||
- 🔐 WireGuard tunnel encryption
|
- 🔐 WireGuard tunnel encryption
|
||||||
- 🛡️ Container firewalls and non-root execution
|
- 🛡️ Container firewalls and non-root execution
|
||||||
- 🔢 TOTP phone verification + rate limiting
|
- 🔢 TOTP phone verification + rate limiting
|
||||||
- 🔤 ASCII-only validation (emoji-proof!)
|
- 🔤 ASCII-only validation (No spammy weird characters please!)
|
||||||
|
|
||||||
## 🔧 Troubleshooting
|
## 🆘 When Things Go Wrong
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check container health
|
# Check if containers are actually alive
|
||||||
docker-compose ps && docker logs portfolio
|
docker compose ps && docker compose logs portfolio
|
||||||
|
|
||||||
# Test SMS connectivity
|
# Poke the SMS gateway to see if it's responsive
|
||||||
docker exec portfolio curl -f http://192.168.0.XXX:9090
|
docker compose exec portfolio curl -f http://your-sms-gateway-ip:9090/health
|
||||||
|
|
||||||
|
# Check WireGuard tunnel status
|
||||||
|
docker compose exec portfolio wg show
|
||||||
```
|
```
|
||||||
|
|
||||||
**Debug Panic Levels:** 😎 → 🤔 → 😅 → 😰 → 💀 → 🍕
|
**Debugging stages of grief:** 😎 confident → 🤔 confused → 😅 nervous → 😰 panicking → 💀 accepting fate → 🍕 ordering pizza
|
||||||
|
|
||||||
## 📁 What's Where
|
## 📁 What's Where
|
||||||
|
|
||||||
```
|
```
|
||||||
├── app/ # Nuxt 4 frontend
|
├── app/ # Nuxt 4 frontend
|
||||||
├── server/ # API routes + SMS gateway libs
|
├── server/ # API routes
|
||||||
├── deploy/ # Deployment scripts
|
├── deploy/ # Deployment scripts
|
||||||
└── .github/ # CI/CD workflows
|
└── .github/ # CI/CD workflows
|
||||||
```
|
```
|
||||||
|
|
@ -104,4 +151,4 @@ This project is licensed under **AGPL 3.0 only** - see the [LICENSE](LICENSE) fi
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Built with ❤️ and lots of ☕*
|
*Built with ❤️ and lots of ☕! (Sheesh, if you've read this far, just hire me, please!)*
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
services:
|
services:
|
||||||
portfolio:
|
portfolio:
|
||||||
image: badbl0cks/portfolio:${IMAGE_TAG:-stable}
|
image: ${IMAGE_NAME:-portfolio:latest}
|
||||||
# image: ghcr.io/xe/x/httpdebug
|
# image: ghcr.io/xe/x/httpdebug
|
||||||
# entrypoint: ["/ko-app/httpdebug", "--bind", ":3000"]
|
# entrypoint: ["/ko-app/httpdebug", "--bind", ":3000"]
|
||||||
container_name: portfolio-${RELEASE_TYPE}-${DEPLOYMENT_COLOR}
|
container_name: portfolio-${RELEASE_TYPE}-${DEPLOYMENT_COLOR}
|
||||||
|
|
@ -21,7 +21,16 @@ services:
|
||||||
- "deployment.release_type=${RELEASE_TYPE}"
|
- "deployment.release_type=${RELEASE_TYPE}"
|
||||||
user: "1000:1000"
|
user: "1000:1000"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "-s", "--max-time", "5", "http://localhost:3000/api/health"]
|
test:
|
||||||
|
[
|
||||||
|
"CMD",
|
||||||
|
"curl",
|
||||||
|
"-f",
|
||||||
|
"-s",
|
||||||
|
"--max-time",
|
||||||
|
"5",
|
||||||
|
"http://localhost:3000/api/health",
|
||||||
|
]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 15s
|
timeout: 15s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
@ -47,7 +56,8 @@ services:
|
||||||
aliases:
|
aliases:
|
||||||
- wireguard-${RELEASE_TYPE}
|
- wireguard-${RELEASE_TYPE}
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "ping", "-c", "1", "-W", "3", "$$NUXT_ANDROID_SMS_GATEWAY_IP"]
|
test:
|
||||||
|
["CMD", "ping", "-c", "1", "-W", "3", "$$NUXT_ANDROID_SMS_GATEWAY_IP"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 15s
|
timeout: 15s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ else
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Validate common required environment variables
|
||||||
|
require_var "DEPLOY_HOST"
|
||||||
|
|
||||||
readonly HAPROXY_BASE_DIR="/srv/haproxy"
|
readonly HAPROXY_BASE_DIR="/srv/haproxy"
|
||||||
readonly HAPROXY_CONFIGS_DIR="${HAPROXY_BASE_DIR}/configs"
|
readonly HAPROXY_CONFIGS_DIR="${HAPROXY_BASE_DIR}/configs"
|
||||||
readonly HAPROXY_COMPOSE_FILE="${HAPROXY_BASE_DIR}/docker-compose.yml"
|
readonly HAPROXY_COMPOSE_FILE="${HAPROXY_BASE_DIR}/docker-compose.yml"
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,12 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
source "${SCRIPT_DIR}/common-lib.sh"
|
source "${SCRIPT_DIR}/common-lib.sh"
|
||||||
|
|
||||||
# Validate required environment variables
|
# Validate required environment variables
|
||||||
require_var "DOCKER_HOST"
|
|
||||||
require_var "REPO_PROJECT_PATH"
|
|
||||||
require_var "REPO_NAME_ONLY"
|
|
||||||
require_var "REPO"
|
|
||||||
require_var "IMAGE_TAR"
|
require_var "IMAGE_TAR"
|
||||||
require_var "PROD"
|
require_var "PROD"
|
||||||
require_var "DOMAIN"
|
require_var "DOMAIN"
|
||||||
require_var "WIREGUARD_ENDPOINT_HOST"
|
require_var "WIREGUARD_ENDPOINT_HOST"
|
||||||
|
require_var "REPO_PROJECT_PATH"
|
||||||
|
require_var "DEPLOY_HOST"
|
||||||
|
|
||||||
validate_deployment_env
|
validate_deployment_env
|
||||||
|
|
||||||
|
|
@ -76,10 +74,18 @@ echo "DEPLOYMENT_COLOR=\"${NEW_COLOR}\"" >> .env
|
||||||
echo "RELEASE_TYPE=\"${RELEASE_TYPE}\"" >> .env
|
echo "RELEASE_TYPE=\"${RELEASE_TYPE}\"" >> .env
|
||||||
echo "WIREGUARD_ENDPOINT_IP=\"${WIREGUARD_ENDPOINT_IP}\"" >> .env
|
echo "WIREGUARD_ENDPOINT_IP=\"${WIREGUARD_ENDPOINT_IP}\"" >> .env
|
||||||
|
|
||||||
|
# Set computed image name based on release type
|
||||||
|
if [ "$RELEASE_TYPE" = "staging" ]; then
|
||||||
|
IMAGE_NAME="portfolio-dev:${IMAGE_TAG}"
|
||||||
|
else
|
||||||
|
IMAGE_NAME="portfolio:${IMAGE_TAG}"
|
||||||
|
fi
|
||||||
|
echo "IMAGE_NAME=\"${IMAGE_NAME}\"" >> .env
|
||||||
|
|
||||||
echo "📋 Copying deployment files..."
|
echo "📋 Copying deployment files..."
|
||||||
scp deploy/docker-compose.yml deploy/haproxy.cfg .env deploy:"${NEW_RELEASE_PATH}/"
|
scp deploy/docker-compose.yml deploy/haproxy.cfg .env deploy:"${NEW_RELEASE_PATH}/"
|
||||||
|
|
||||||
echo "🐳 Loading Docker image..."
|
echo "🐳 Loading Docker image (${IMAGE_TAR})..."
|
||||||
docker load -i "${IMAGE_TAR}"
|
docker load -i "${IMAGE_TAR}"
|
||||||
|
|
||||||
PROJECT_NAME=$(get_project_name "$NEW_COLOR")
|
PROJECT_NAME=$(get_project_name "$NEW_COLOR")
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Generate Docker tags based on git ref and environment
|
|
||||||
# Usage: ./generate-docker-tags.sh IMAGE_BASE GIT_SHA GIT_REF PROD
|
|
||||||
|
|
||||||
if [ $# -ne 4 ]; then
|
|
||||||
echo "Error: Invalid number of arguments" > /dev/stderr
|
|
||||||
echo "Usage: $0 IMAGE_BASE GIT_SHA GIT_REF PROD" > /dev/stderr
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
IMAGE_BASE="$1"
|
|
||||||
GIT_SHA="$2"
|
|
||||||
GIT_REF="$3"
|
|
||||||
PROD="$4"
|
|
||||||
|
|
||||||
# Validate inputs
|
|
||||||
if [ -z "$IMAGE_BASE" ] || [ -z "$GIT_SHA" ]; then
|
|
||||||
echo "Error: IMAGE_BASE and GIT_SHA cannot be empty" > /dev/stderr
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Always include SHA tags
|
|
||||||
echo "${IMAGE_BASE}:sha-${GIT_SHA:0:7}"
|
|
||||||
echo "${IMAGE_BASE}:sha-${GIT_SHA}"
|
|
||||||
|
|
||||||
# Handle version tags
|
|
||||||
if [[ "$GIT_REF" =~ ^refs/tags/v([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$ ]]; then
|
|
||||||
MAJOR="${BASH_REMATCH[1]}"
|
|
||||||
MINOR="${BASH_REMATCH[2]}"
|
|
||||||
PATCH="${BASH_REMATCH[3]}"
|
|
||||||
PRERELEASE="${BASH_REMATCH[4]}"
|
|
||||||
|
|
||||||
if [[ -z "$PRERELEASE" ]] && [[ "$PROD" == "true" ]]; then
|
|
||||||
echo "${IMAGE_BASE}:latest"
|
|
||||||
echo "${IMAGE_BASE}:stable"
|
|
||||||
[[ "$MAJOR" -gt 0 ]] && echo "${IMAGE_BASE}:v${MAJOR}"
|
|
||||||
echo "${IMAGE_BASE}:v${MAJOR}.${MINOR}"
|
|
||||||
echo "${IMAGE_BASE}:v${MAJOR}.${MINOR}.${PATCH}"
|
|
||||||
else
|
|
||||||
echo "${IMAGE_BASE}:latest-staging"
|
|
||||||
echo "${IMAGE_BASE}:staging"
|
|
||||||
echo "${IMAGE_BASE}:v${MAJOR}.${MINOR}.${PATCH}-prerelease"
|
|
||||||
fi
|
|
||||||
elif [[ "$PROD" == "false" ]]; then
|
|
||||||
echo "${IMAGE_BASE}:latest-staging"
|
|
||||||
echo "${IMAGE_BASE}:staging"
|
|
||||||
fi
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Parse repository name and generate project paths
|
|
||||||
# Usage: ./parse-repository-name.sh GITHUB_REPOSITORY
|
|
||||||
|
|
||||||
if [ $# -eq 0 ]; then
|
|
||||||
echo "Error: No repository name provided" > /dev/stderr
|
|
||||||
echo "Usage: $0 GITHUB_REPOSITORY" > /dev/stderr
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
GITHUB_REPOSITORY="$1"
|
|
||||||
|
|
||||||
echo "GITHUB_REPOSITORY: $GITHUB_REPOSITORY" > /dev/stderr
|
|
||||||
|
|
||||||
if [[ "$GITHUB_REPOSITORY" == *".git" ]]; then
|
|
||||||
if [[ "$GITHUB_REPOSITORY" == "https://"* ]]; then
|
|
||||||
echo "GITHUB_REPOSITORY ends in .git and is an HTTPS URI" > /dev/stderr
|
|
||||||
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | cut -d'/' -f4-5 | sed 's/[^a-zA-Z0-9\/-]/-/g')
|
|
||||||
elif [[ "$GITHUB_REPOSITORY" == "git@"* ]]; then
|
|
||||||
echo "GITHUB_REPOSITORY ends in .git and is an SSH URI" > /dev/stderr
|
|
||||||
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | cut -d':' -f2 | sed 's/[^a-zA-Z0-9\/-]/-/g')
|
|
||||||
else
|
|
||||||
echo "GITHUB_REPOSITORY ends in .git and is not a URI" > /dev/stderr
|
|
||||||
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | sed 's/[^a-zA-Z0-9\/-]/-/g')
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "GITHUB_REPOSITORY is not a URI" > /dev/stderr
|
|
||||||
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/[^a-zA-Z0-9\/-]/-/g')
|
|
||||||
fi
|
|
||||||
|
|
||||||
REPO_NAME_ONLY=$(echo "$REPO" | cut -d'/' -f2)
|
|
||||||
# Default path - will be modified by deployment script based on environment
|
|
||||||
REPO_PROJECT_PATH="/srv/${REPO_NAME_ONLY}"
|
|
||||||
|
|
||||||
# Output in format that can be sourced - using printf %q for proper escaping
|
|
||||||
printf "export REPO=%q\n" "$REPO"
|
|
||||||
printf "export REPO_NAME_ONLY=%q\n" "$REPO_NAME_ONLY"
|
|
||||||
printf "export REPO_PROJECT_PATH=%q\n" "$REPO_PROJECT_PATH"
|
|
||||||
35
package-lock.json
generated
35
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "portfolio",
|
"name": "portfolio",
|
||||||
"version": "0.0.0-development",
|
"version": "0.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "portfolio",
|
"name": "portfolio",
|
||||||
"version": "0.0.0-development",
|
"version": "0.0.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/fonts": "0.11.4",
|
"@nuxt/fonts": "0.11.4",
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"@nuxt/ui": "3.2.0",
|
"@nuxt/ui": "3.2.0",
|
||||||
"@unhead/vue": "^2.0.12",
|
"@unhead/vue": "^2.0.12",
|
||||||
"android-sms-gateway": "^3.0.0",
|
"android-sms-gateway": "^3.0.0",
|
||||||
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||||
"daisyui": "^5.0.46",
|
"daisyui": "^5.0.46",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"nuxt": "^4.0.1",
|
"nuxt": "^4.0.1",
|
||||||
|
|
@ -784,7 +785,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit": {
|
"node_modules/@eslint/plugin-kit": {
|
||||||
"version": "0.3.3",
|
"version": "0.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz",
|
||||||
|
"integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -5910,7 +5913,6 @@
|
||||||
},
|
},
|
||||||
"node_modules/array-ify": {
|
"node_modules/array-ify": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/ast-kit": {
|
"node_modules/ast-kit": {
|
||||||
|
|
@ -6848,7 +6850,6 @@
|
||||||
},
|
},
|
||||||
"node_modules/compare-func": {
|
"node_modules/compare-func": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"array-ify": "^1.0.0",
|
"array-ify": "^1.0.0",
|
||||||
|
|
@ -6857,7 +6858,6 @@
|
||||||
},
|
},
|
||||||
"node_modules/compare-func/node_modules/dot-prop": {
|
"node_modules/compare-func/node_modules/dot-prop": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-obj": "^2.0.0"
|
"is-obj": "^2.0.0"
|
||||||
|
|
@ -6959,6 +6959,18 @@
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/conventional-changelog-conventionalcommits": {
|
||||||
|
"version": "9.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.1.0.tgz",
|
||||||
|
"integrity": "sha512-MnbEysR8wWa8dAEvbj5xcBgJKQlX/m0lhS8DsyAAWDHdfs2faDJxTgzRYlRYpXSe7UiKrIIlB4TrBKU9q9DgkA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"compare-func": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/conventional-changelog-writer": {
|
"node_modules/conventional-changelog-writer": {
|
||||||
"version": "8.2.0",
|
"version": "8.2.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
@ -9988,7 +10000,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ipx": {
|
"node_modules/ipx": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ipx/-/ipx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-XuM9FEGOT+/45mfAWZ5ykwkZ/oE7vWpd1iWjRffMWlwAYIRzb/xD6wZhQ4BzmPMX6Ov5dqK0wUyD0OEN9oWT6g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -10184,7 +10198,6 @@
|
||||||
},
|
},
|
||||||
"node_modules/is-obj": {
|
"node_modules/is-obj": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -16673,6 +16686,8 @@
|
||||||
},
|
},
|
||||||
"node_modules/semantic-release": {
|
"node_modules/semantic-release": {
|
||||||
"version": "24.2.7",
|
"version": "24.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.7.tgz",
|
||||||
|
"integrity": "sha512-g7RssbTAbir1k/S7uSwSVZFfFXwpomUB9Oas0+xi9KStSCmeDXcA7rNhiskjLqvUe/Evhx8fVCT16OSa34eM5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -17996,7 +18011,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tmp": {
|
"node_modules/tmp": {
|
||||||
"version": "0.2.3",
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.14"
|
"node": ">=14.14"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "portfolio",
|
"name": "portfolio",
|
||||||
"version": "0.0.0-development",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
"@nuxt/ui": "3.2.0",
|
"@nuxt/ui": "3.2.0",
|
||||||
"@unhead/vue": "^2.0.12",
|
"@unhead/vue": "^2.0.12",
|
||||||
"android-sms-gateway": "^3.0.0",
|
"android-sms-gateway": "^3.0.0",
|
||||||
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||||
"daisyui": "^5.0.46",
|
"daisyui": "^5.0.46",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"nuxt": "^4.0.1",
|
"nuxt": "^4.0.1",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue