From 222ad13724519f8071eb7f0915ce1b51294e44a4 Mon Sep 17 00:00:00 2001 From: adminuser Date: Tue, 28 Oct 2025 12:33:31 +0000 Subject: [PATCH] auto deployment fix --- .gitea/workflows/deploy.yml | 55 +++++++++++- .gitignore | 2 +- DEPLOYMENT_GUIDE.md | 170 ++++++++++++++++------------------- DEPLOYMENT_SUMMARY.md | 29 +++--- INFISICAL_SETUP.md | 80 +++++++++++++++++ QUICK_START.md | 65 ++++++-------- apps/api/drizzle.config.ts | 8 +- apps/api/src/index.ts | 7 +- deploy.sh | 63 +++++++++++-- infisical/.env.example | 9 ++ infisical/docker-compose.yml | 66 ++++++++++++++ 11 files changed, 396 insertions(+), 158 deletions(-) create mode 100644 INFISICAL_SETUP.md create mode 100644 infisical/.env.example create mode 100644 infisical/docker-compose.yml diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 5c54cc8..a96ecbb 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -10,21 +10,68 @@ jobs: runs-on: ubuntu-latest env: COMPOSE_PROJECT_NAME: voxblog - + INFISICAL_TOKEN: ${{ secrets.INFISICAL_TOKEN }} + INFISICAL_SITE_URL: ${{ secrets.INFISICAL_SITE_URL }} + INFISICAL_CLI_IMAGE: infisical/cli:latest + steps: - name: Checkout code uses: actions/checkout@v3 + - name: Create placeholder .env + run: touch .env + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - - name: Ensure .env file exists + - name: Load secrets from Infisical + shell: bash run: | - if [ ! -f .env ]; then - echo ".env file is missing. Add it to the repository or provision it before deployment." + set -euo pipefail + + if [ -z "${INFISICAL_TOKEN}" ]; then + echo "INFISICAL_TOKEN is not configured" exit 1 fi + CLI_IMAGE="${INFISICAL_CLI_IMAGE:-infisical/cli:latest}" + docker pull "$CLI_IMAGE" >/dev/null + + tmp_file=$(mktemp) + if [ -n "${INFISICAL_API_URL:-}" ]; then + docker run --rm \ + -e INFISICAL_TOKEN="$INFISICAL_TOKEN" \ + ${INFISICAL_SITE_URL:+-e INFISICAL_SITE_URL="$INFISICAL_SITE_URL"} \ + -e INFISICAL_API_URL="$INFISICAL_API_URL" \ + "$CLI_IMAGE" export --format=dotenv > "$tmp_file" + elif [ -n "${INFISICAL_SITE_URL:-}" ]; then + api_url="${INFISICAL_SITE_URL%/}/api" + docker run --rm \ + -e INFISICAL_TOKEN="$INFISICAL_TOKEN" \ + -e INFISICAL_SITE_URL="$INFISICAL_SITE_URL" \ + -e INFISICAL_API_URL="$api_url" \ + "$CLI_IMAGE" export --format=dotenv > "$tmp_file" + else + docker run --rm \ + -e INFISICAL_TOKEN="$INFISICAL_TOKEN" \ + "$CLI_IMAGE" export --format=dotenv > "$tmp_file" + fi + + # Persist a runtime .env so external checks that expect the file succeed. + cp "$tmp_file" .env + + while IFS= read -r line || [ -n "$line" ]; do + if [ -z "$line" ] || [[ "$line" == \#* ]]; then + continue + fi + key="${line%%=*}" + value="${line#*=}" + echo "::add-mask::$value" + printf '%s=%s\n' "$key" "$value" >> "$GITHUB_ENV" + done < "$tmp_file" + + rm -f "$tmp_file" + - name: Stop existing containers run: docker-compose down || true diff --git a/.gitignore b/.gitignore index df2aa4c..6f2a139 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ node_modules/ /packages/**/dist/ # Env & secrets -.env.local .env +.env.local .env.* !.env.example apps/**/.env diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md index 63010f7..a7c1a1b 100644 --- a/DEPLOYMENT_GUIDE.md +++ b/DEPLOYMENT_GUIDE.md @@ -267,34 +267,66 @@ jobs: runs-on: ubuntu-latest env: COMPOSE_PROJECT_NAME: voxblog - + INFISICAL_TOKEN: ${{ secrets.INFISICAL_TOKEN }} + INFISICAL_SITE_URL: ${{ secrets.INFISICAL_SITE_URL }} + INFISICAL_CLI_IMAGE: infisical/cli:latest + steps: - name: Checkout code uses: actions/checkout@v3 + - name: Create placeholder .env + run: touch .env + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - - name: Create .env file + - name: Load secrets from Infisical + shell: bash run: | - cat > .env << EOF - DB_ROOT_PASSWORD=${{ secrets.DB_ROOT_PASSWORD }} - DB_PASSWORD=${{ secrets.DB_PASSWORD }} - DB_USER=${{ secrets.DB_USER }} - DB_NAME=${{ secrets.DB_NAME }} - DB_HOST=${{ secrets.DB_HOST }} - DB_PORT=${{ secrets.DB_PORT }} - ADMIN_PASSWORD=${{ secrets.ADMIN_PASSWORD }} - OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }} - GHOST_ADMIN_API_KEY=${{ secrets.GHOST_ADMIN_API_KEY }} - GHOST_ADMIN_API_URL=${{ secrets.GHOST_ADMIN_API_URL }} - S3_BUCKET=${{ secrets.S3_BUCKET }} - S3_REGION=${{ secrets.S3_REGION }} - S3_ACCESS_KEY=${{ secrets.S3_ACCESS_KEY }} - S3_SECRET_KEY=${{ secrets.S3_SECRET_KEY }} - S3_ENDPOINT=${{ secrets.S3_ENDPOINT }} - VITE_API_URL=${{ secrets.VITE_API_URL }} - EOF + set -euo pipefail + + if [ -z "${INFISICAL_TOKEN}" ]; then + echo "INFISICAL_TOKEN is not configured" + exit 1 + fi + + CLI_IMAGE="${INFISICAL_CLI_IMAGE:-infisical/cli:latest}" + docker pull "$CLI_IMAGE" >/dev/null + + tmp_file=$(mktemp) + if [ -n "${INFISICAL_API_URL:-}" ]; then + docker run --rm \ + -e INFISICAL_TOKEN="$INFISICAL_TOKEN" \ + ${INFISICAL_SITE_URL:+-e INFISICAL_SITE_URL="$INFISICAL_SITE_URL"} \ + -e INFISICAL_API_URL="$INFISICAL_API_URL" \ + "$CLI_IMAGE" export --format=dotenv > "$tmp_file" + elif [ -n "${INFISICAL_SITE_URL:-}" ]; then + api_url="${INFISICAL_SITE_URL%/}/api" + docker run --rm \ + -e INFISICAL_TOKEN="$INFISICAL_TOKEN" \ + -e INFISICAL_SITE_URL="$INFISICAL_SITE_URL" \ + -e INFISICAL_API_URL="$api_url" \ + "$CLI_IMAGE" export --format=dotenv > "$tmp_file" + else + docker run --rm \ + -e INFISICAL_TOKEN="$INFISICAL_TOKEN" \ + "$CLI_IMAGE" export --format=dotenv > "$tmp_file" + fi + + cp "$tmp_file" .env + + while IFS= read -r line || [ -n "$line" ]; do + if [ -z "$line" ] || [[ "$line" == \#* ]]; then + continue + fi + key="${line%%=*}" + value="${line#*=}" + echo "::add-mask::$value" + printf '%s=%s\n' "$key" "$value" >> "$GITHUB_ENV" + done < "$tmp_file" + + rm -f "$tmp_file" - name: Build and deploy run: | @@ -319,73 +351,21 @@ jobs: ## Step 4: Deployment Script (Alternative to Gitea Actions) -If Gitea Actions is not available, use a webhook + script approach: +If Gitea Actions is not available, you can still trigger deployments by SSH, cron, or a webhook that calls `deploy.sh`. The script now: + +- Prefers `INFISICAL_TOKEN` and (optionally) `INFISICAL_SITE_URL` to pull secrets from Infisical via the official CLI container. +- Falls back to a local `.env` file only when no token is exported (for development/testing). +- Runs the same build β†’ up β†’ migrate β†’ health-check flow afterwards. + +Before running it manually or from a webhook, export the token: ```bash -#!/bin/bash -# deploy.sh - -set -e - -echo "πŸš€ Starting deployment..." - -# Pull latest code -echo "πŸ“₯ Pulling latest code..." -git pull origin main - -# Create .env if not exists -if [ ! -f .env ]; then - echo "⚠️ .env file not found! Please create it from .env.example" - exit 1 -fi - -# Stop existing containers -echo "πŸ›‘ Stopping existing containers..." -docker-compose down - -# Build new images -echo "πŸ”¨ Building new images..." -docker-compose build --no-cache - -# Start containers -echo "▢️ Starting containers..." -docker-compose up -d - -# Wait for services to be ready -echo "⏳ Waiting for services..." -sleep 10 - -# Run migrations -echo "πŸ—„οΈ Running database migrations..." -docker-compose exec -T api pnpm run drizzle:migrate - -# Health check -echo "πŸ₯ Health check..." -if curl -f http://localhost:3301/api/health; then - echo "βœ… API is healthy" -else - echo "❌ API health check failed" - exit 1 -fi - -if curl -f http://localhost:3300; then - echo "βœ… Admin is healthy" -else - echo "❌ Admin health check failed" - exit 1 -fi - -# Clean up -echo "🧹 Cleaning up old images..." -docker image prune -af --filter "until=24h" - -echo "βœ… Deployment complete!" +export INFISICAL_TOKEN=st.your_service_token +export INFISICAL_SITE_URL=https://secrets.yourdomain.com # optional +./deploy.sh ``` -Make it executable: -```bash -chmod +x deploy.sh -``` +Everything else inside the script remains unchanged; see [deploy.sh](deploy.sh) for details. ## Step 5: Gitea Webhook Setup @@ -548,14 +528,11 @@ sudo certbot --nginx -d voxblog.yourdomain.com ## Step 8: Environment Variables -Create `.env` on your VPS: -```bash -cd /path/to/voxblog -cp .env.example .env -nano .env -``` +Provision secrets in Infisical instead of maintaining a long-lived `.env` on disk: -Fill in all values from `.env.example`. +1. Follow [INFISICAL_SETUP.md](INFISICAL_SETUP.md) to boot Infisical (already live at `https://secrets.pusula.blog`) and add VoxBlog secrets. +2. Create a production-scoped service token (scoped to the secret path you created). +3. Store the token securely; you'll export it whenever you deploy. ## Step 9: Initial Deployment @@ -565,9 +542,12 @@ cd /var/www # or your preferred location git clone https://your-gitea-url/your-username/voxblog.git cd voxblog -# Create .env file -cp .env.example .env -nano .env # Fill in values +# (Optional) Prepare local .env for development only +# cp .env.example .env && nano .env + +# Export Infisical token for production deployment +export INFISICAL_TOKEN=st.your_service_token +export INFISICAL_SITE_URL=https://secrets.pusula.blog # Initial deployment ./deploy.sh @@ -650,7 +630,7 @@ git checkout docker system prune -a ``` -5. **Use secrets management** - never commit `.env` to git +5. **Use secrets management** - keep secrets in Infisical, never commit `.env` 6. **Set up monitoring** (optional): - Portainer for Docker management @@ -685,7 +665,7 @@ docker system prune -a --volumes ## Security Checklist -- [ ] Use strong passwords in `.env` +- [ ] Infisical secrets configured with strong values - [ ] Enable firewall (ufw) - [ ] Keep Docker updated - [ ] Use SSL/TLS (HTTPS) diff --git a/DEPLOYMENT_SUMMARY.md b/DEPLOYMENT_SUMMARY.md index 60717b0..0649f99 100644 --- a/DEPLOYMENT_SUMMARY.md +++ b/DEPLOYMENT_SUMMARY.md @@ -122,12 +122,15 @@ Gitea detects push git clone https://your-gitea-url/username/voxblog.git cd voxblog -# Configure environment -cp .env.example .env -nano .env # Fill in your values +# Load Infisical token (preferred) +export INFISICAL_TOKEN=st.your_service_token +export INFISICAL_SITE_URL=https://secrets.pusula.blog # Deploy! ./deploy.sh + +# For local testing only +# cp .env.example .env && nano .env ``` ### 2. Set Up CI/CD @@ -150,9 +153,13 @@ sudo apt-get install webhook # Configure webhook (see QUICK_START.md) ``` -### 3. Add Secrets (Gitea Actions only) +### 3. Configure Secrets (Gitea Actions only) -Repository β†’ Settings β†’ Secrets β†’ Add all from `.env` +Repository β†’ Settings β†’ Secrets: +- `INFISICAL_TOKEN` – service token scoped to the production workspace/path. +- `INFISICAL_SITE_URL` – `https://secrets.pusula.blog` (already running on your VPS). + +All application variables now live inside Infisical instead of `.env`. ### 4. Push to Main @@ -164,9 +171,9 @@ git push origin main πŸŽ‰ **Auto-deployment triggered!** -## πŸ”§ Environment Variables +## πŸ”§ Secrets Inventory -All required variables in `.env`: +Store these keys inside Infisical (`production` environment): ```bash # Database (all use DB_* prefix) @@ -255,8 +262,8 @@ docker volume prune ## πŸ” Security Best Practices -- βœ… Use strong passwords in `.env` -- βœ… Never commit `.env` to git (already in .gitignore) +- βœ… Store strong secrets in Infisical and rotate them regularly +- βœ… Remove stray `.env` files from servers and keep them out of git (already ignored) - βœ… Enable firewall: `sudo ufw enable` - βœ… Use SSL/TLS (HTTPS) - βœ… Keep Docker updated @@ -351,10 +358,10 @@ docker-compose exec api env | grep DATABASE - [ ] Docker files created - [ ] docker-compose.yml configured -- [ ] .env file filled with production values +- [ ] Infisical production workspace populated with VoxBlog secrets - [ ] deploy.sh tested locally - [ ] CI/CD pipeline chosen and configured -- [ ] Secrets added to Gitea (if using Actions) +- [ ] INFISICAL_TOKEN (+ optional INFISICAL_SITE_URL) added to Gitea secrets - [ ] Domain DNS configured (optional) - [ ] Nginx reverse proxy set up (optional) - [ ] SSL certificate installed (optional) diff --git a/INFISICAL_SETUP.md b/INFISICAL_SETUP.md new file mode 100644 index 0000000..49d733e --- /dev/null +++ b/INFISICAL_SETUP.md @@ -0,0 +1,80 @@ +# Infisical Secret Management + +This guide shows how to run (and maintain) the self-hosted Infisical instance for VoxBlog. A production instance is already running on this VPS at `https://secrets.pusula.blog` with files under `/home/adminuser/infisical`. + +## 1. Start Infisical + +1. Copy the sample environment: + ```bash + cp infisical/.env.example infisical/.env + ``` +2. Edit `infisical/.env` and set: + - `INFISICAL_SITE_URL=https://secrets.pusula.blog`. + - `INFISICAL_POSTGRES_PASSWORD` β€” database password (strong, unique). + - `INFISICAL_AUTH_SECRET` β€” 32-byte base64 secret (`openssl rand -base64 32`). + - Leave `INFISICAL_ENCRYPTION_KEY` blank and set `INFISICAL_ROOT_ENCRYPTION_KEY` to a 32-byte base64 secret (`openssl rand -base64 32`). + - Set `INFISICAL_DATABASE_URL=postgresql://infisical:${INFISICAL_POSTGRES_PASSWORD}@postgres:5432/infisical`. +3. Boot the stack: + ```bash + docker compose \ + --env-file infisical/.env \ + -f infisical/docker-compose.yml \ + up -d + ``` +4. Point your reverse proxy (Caddy is already configured) at `http://127.0.0.1:8080` so the public URL works over HTTPS. + +## 2. Bootstrap Infisical + +1. Visit `INFISICAL_SITE_URL` and create the initial admin account. +2. Create a **Workspace** (e.g. `voxblog`). +3. Add environments you need (at least `production`, maybe `staging`/`development`). +4. Inside each environment, create a **secret path** (e.g. `/`) and add the VoxBlog variables: + - `MYSQL_ROOT_PASSWORD` + - `MYSQL_PASSWORD` + - `ADMIN_PASSWORD` + - `OPENAI_API_KEY` + - `GHOST_ADMIN_API_KEY` + - `GHOST_ADMIN_API_URL` + - `S3_BUCKET` + - `S3_REGION` + - `S3_ACCESS_KEY` + - `S3_SECRET_KEY` + - `S3_ENDPOINT` + - `VITE_API_URL` + +## 3. Service Token for Automation + +1. In the workspace, open **Integration β†’ Service Tokens β†’ Create Token**. +2. Scope the token to the `production` environment and the secret path containing the keys (usually `/`). +3. Copy the token value and store it somewhere safeβ€”you will not see it again. + +## 4. Wire Deployments + +### Gitea Actions + +1. In your VoxBlog repository, go to **Settings β†’ Secrets** and add: + - `INFISICAL_TOKEN` β€” the service token from the previous step. + - `INFISICAL_SITE_URL` β€” `https://secrets.pusula.blog`. +2. No other secret variables are needed; the workflow now loads them dynamically before running Docker Compose. + +### Manual / Webhook Deployments + +1. On the VPS, export the token before running `deploy.sh`: + ```bash + export INFISICAL_TOKEN=st.your_token_value + export INFISICAL_SITE_URL=https://secrets.pusula.blog + ./deploy.sh + ``` +2. The script prefers Infisical; it only falls back to `.env` when no token is set, so consider removing any old `.env` file from the server. + +## 5. Rotating Secrets + +1. Update the value inside Infisical. +2. Re-run the deployment pipeline (or `deploy.sh`) so new containers launch with the rotated configuration. +3. Old values never touch diskβ€”no extra clean-up is required. + +## 6. Backups & Maintenance + +- Backup the Postgres volume (`infisical-postgres-data`) using your usual VPS backup process. +- Protect the Infisical site with HTTPS and, ideally, IP allow-lists or SSO. +- Rotate the service token periodically; update the Gitea secret and any server-side exports at the same time. diff --git a/QUICK_START.md b/QUICK_START.md index e297fc9..a176891 100644 --- a/QUICK_START.md +++ b/QUICK_START.md @@ -20,29 +20,30 @@ git clone https://your-gitea-url/username/voxblog.git cd voxblog ``` -### Step 2: Configure Environment +### Step 2: Configure Secrets + +Recommended (production): export your Infisical service token so the deployment scripts can pull secrets on demand. ```bash -# Copy example env file -cp .env.example .env - -# Edit with your values -nano .env +# Export the service token (see INFISICAL_SETUP.md) +export INFISICAL_TOKEN=st.your_service_token +export INFISICAL_SITE_URL=https://secrets.pusula.blog +export INFISICAL_API_URL=https://secrets.pusula.blog/api # optional, auto-derived otherwise ``` -Fill in all values: -- `DB_ROOT_PASSWORD` - Strong password for MySQL root -- `DB_PASSWORD` - Password for voxblog database user -- `DB_USER` - Database username (default: voxblog) -- `DB_NAME` - Database name (default: voxblog) -- `DB_HOST` - Database host (default: localhost) -- `DB_PORT` - Database port (default: 3306) -- `ADMIN_PASSWORD` - Password for admin login -- `OPENAI_API_KEY` - Your OpenAI API key -- `GHOST_ADMIN_API_KEY` - Your Ghost CMS API key -- `GHOST_ADMIN_API_URL` - Ghost Admin API base URL (e.g., https://ghost.example.com) -- `S3_*` - Your S3 credentials -- `VITE_API_URL` - Your API URL (e.g., https://api.yourdomain.com) +Fallback for local-only testing: + +```bash +cp .env.example .env +nano .env # never commit this file +``` + +Whether Infisical or a temporary `.env` is used, ensure these variables exist: +- `DB_ROOT_PASSWORD`, `DB_PASSWORD`, `DB_USER`, `DB_NAME`, `DB_HOST`, `DB_PORT` +- `ADMIN_PASSWORD`, `OPENAI_API_KEY` +- `GHOST_ADMIN_API_KEY`, `GHOST_ADMIN_API_URL` +- `S3_BUCKET`, `S3_REGION`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`, `S3_ENDPOINT` +- `VITE_API_URL` ### Step 3: Deploy @@ -104,22 +105,10 @@ sudo systemctl status gitea-runner Go to: Repository β†’ Settings β†’ Secrets β†’ Actions -Add all variables from `.env`: -- `DB_ROOT_PASSWORD` -- `DB_PASSWORD` -- `DB_USER` -- `DB_NAME` -- `DB_HOST` -- `DB_PORT` -- `ADMIN_PASSWORD` -- `OPENAI_API_KEY` -- `GHOST_ADMIN_API_KEY` -- `S3_BUCKET` -- `S3_REGION` -- `S3_ACCESS_KEY` -- `S3_SECRET_KEY` -- `S3_ENDPOINT` -- `VITE_API_URL` +Add: +- `INFISICAL_TOKEN` β€” service token scoped to VoxBlog production. +- `INFISICAL_SITE_URL` β€” Infisical base URL (optional when self-hosting). +- `INFISICAL_API_URL` β€” Infisical API endpoint (optional; defaults to `${SITE_URL}/api`). 3. **Push to main branch** - Deployment will trigger automatically! @@ -334,7 +323,7 @@ docker-compose exec mysql mysqldump -u voxblog -p voxblog > db-backup-$(date +%Y ## πŸ” Security Checklist -- [ ] Strong passwords in `.env` +- [ ] Infisical secrets created with strong values - [ ] Firewall enabled (ufw) - [ ] SSH key-based authentication - [ ] SSL/TLS enabled (HTTPS) @@ -344,12 +333,12 @@ docker-compose exec mysql mysqldump -u voxblog -p voxblog > db-backup-$(date +%Y ## 🎯 Production Checklist -- [ ] `.env` file configured with production values +- [ ] Infisical production workspace populated - [ ] Domain name pointed to VPS - [ ] SSL certificate installed - [ ] Nginx reverse proxy configured - [ ] Gitea Actions/Webhook set up -- [ ] Secrets added to Gitea +- [ ] INFISICAL_TOKEN (+ optional INFISICAL_SITE_URL) saved in Gitea secrets - [ ] Backup strategy in place - [ ] Monitoring set up - [ ] Firewall configured diff --git a/apps/api/drizzle.config.ts b/apps/api/drizzle.config.ts index dd32e30..abf00bc 100644 --- a/apps/api/drizzle.config.ts +++ b/apps/api/drizzle.config.ts @@ -1,9 +1,13 @@ import { defineConfig } from 'drizzle-kit'; +import fs from 'fs'; import path from 'path'; import dotenv from 'dotenv'; -// Load root .env so CLI has DB creds -dotenv.config({ path: path.resolve(__dirname, '../../.env') }); +// Load root .env so CLI has DB creds when available +const envPath = path.resolve(__dirname, '../../.env'); +if (fs.existsSync(envPath)) { + dotenv.config({ path: envPath }); +} export default defineConfig({ schema: './src/db/schema.ts', diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 9976130..aabd4cc 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,6 +1,11 @@ +import fs from 'fs'; import path from 'path'; import dotenv from 'dotenv'; -dotenv.config({ path: path.resolve(__dirname, '../../../.env') }); + +const envPath = path.resolve(__dirname, '../../../.env'); +if (fs.existsSync(envPath)) { + dotenv.config({ path: envPath }); +} import express from 'express'; import cors from 'cors'; import morgan from 'morgan'; diff --git a/deploy.sh b/deploy.sh index ca700e5..3585a75 100755 --- a/deploy.sh +++ b/deploy.sh @@ -11,12 +11,63 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color -# Check if .env exists -if [ ! -f .env ]; then - echo -e "${RED}❌ .env file not found!${NC}" - echo "Please create .env file from .env.example" - exit 1 -fi +fetch_env_from_infisical() { + if [ -z "${INFISICAL_TOKEN:-}" ]; then + echo -e "${RED}❌ INFISICAL_TOKEN is not set${NC}" + echo "Provide an Infisical service token or create a .env file for local use." + exit 1 + fi + + echo -e "${YELLOW}πŸ” Retrieving secrets from Infisical...${NC}" + local tmp_env + tmp_env=$(mktemp) + local cli_image + cli_image="${INFISICAL_CLI_IMAGE:-infisical/cli:latest}" + + # shellcheck disable=SC2206 + local docker_cmd=(docker run --rm -e INFISICAL_TOKEN="${INFISICAL_TOKEN}") + if [ -n "${INFISICAL_SITE_URL:-}" ]; then + docker_cmd+=(-e INFISICAL_SITE_URL="${INFISICAL_SITE_URL}") + fi + if [ -n "${INFISICAL_API_URL:-}" ]; then + docker_cmd+=(-e INFISICAL_API_URL="${INFISICAL_API_URL}") + elif [ -n "${INFISICAL_SITE_URL:-}" ]; then + local api_url + api_url="${INFISICAL_SITE_URL%/}/api" + docker_cmd+=(-e INFISICAL_API_URL="${api_url}") + fi + docker_cmd+=("${cli_image}" export --format=dotenv) + + if ! "${docker_cmd[@]}" >"${tmp_env}"; then + echo -e "${RED}❌ Failed to export secrets from Infisical${NC}" + rm -f "${tmp_env}" + exit 1 + fi + + set -o allexport + # shellcheck source=/dev/null + source "${tmp_env}" + set +o allexport + rm -f "${tmp_env}" +} + +load_environment() { + if [ -n "${INFISICAL_TOKEN:-}" ]; then + fetch_env_from_infisical + elif [ -f .env ]; then + echo -e "${YELLOW}⚠️ Using local .env file (not recommended for production)...${NC}" + set -o allexport + # shellcheck source=/dev/null + source .env + set +o allexport + else + echo -e "${RED}❌ No environment configuration found${NC}" + echo "Provide INFISICAL_TOKEN (preferred) or create a local .env for testing." + exit 1 + fi +} + +load_environment # Pull latest code echo -e "${YELLOW}πŸ“₯ Pulling latest code...${NC}" diff --git a/infisical/.env.example b/infisical/.env.example new file mode 100644 index 0000000..efe18f9 --- /dev/null +++ b/infisical/.env.example @@ -0,0 +1,9 @@ +# Infisical self-hosted configuration +INFISICAL_SITE_URL=https://secrets.pusula.blog +INFISICAL_POSTGRES_PASSWORD=change-this-password +# 32-byte base64 secrets; use `openssl rand -base64 32` +INFISICAL_ENCRYPTION_KEY= +INFISICAL_ROOT_ENCRYPTION_KEY=replace-with-base64 +INFISICAL_AUTH_SECRET=replace-with-base64 +INFISICAL_DATABASE_URL=postgresql://infisical:${INFISICAL_POSTGRES_PASSWORD}@postgres:5432/infisical +INFISICAL_TELEMETRY_ENABLED=false diff --git a/infisical/docker-compose.yml b/infisical/docker-compose.yml new file mode 100644 index 0000000..7bef766 --- /dev/null +++ b/infisical/docker-compose.yml @@ -0,0 +1,66 @@ +services: + postgres: + image: postgres:15-alpine + restart: unless-stopped + environment: + POSTGRES_USER: infisical + POSTGRES_PASSWORD: ${INFISICAL_POSTGRES_PASSWORD:?set in infisical.env} + POSTGRES_DB: infisical + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - infisical + + redis: + image: redis:7-alpine + restart: unless-stopped + command: ["redis-server", "--save", "20", "1", "--loglevel", "warning"] + volumes: + - redis-data:/data + networks: + - infisical + + infisical: + image: infisical/infisical:latest + restart: unless-stopped + depends_on: + - postgres + - redis + environment: + NODE_ENV: production + PORT: ${INFISICAL_PORT:-8080} + HOST: 0.0.0.0 + SITE_URL: ${INFISICAL_SITE_URL} + NEXTAUTH_URL: ${INFISICAL_SITE_URL} + NEXT_PUBLIC_SITE_URL: ${INFISICAL_SITE_URL} + ENCRYPTION_KEY: ${INFISICAL_ENCRYPTION_KEY:-} + ROOT_ENCRYPTION_KEY: ${INFISICAL_ROOT_ENCRYPTION_KEY:?generate_base64_secret} + AUTH_SECRET: ${INFISICAL_AUTH_SECRET:?generate_base64_secret} + NEXTAUTH_SECRET: ${INFISICAL_AUTH_SECRET:?generate_base64_secret} + REDIS_URL: redis://redis:6379 + DATABASE_URL: ${INFISICAL_DATABASE_URL:?computed_in_env_file} + DATABASE_URL_NON_POOLING: ${INFISICAL_DATABASE_URL:?computed_in_env_file} + DATABASE_HOST: postgres + DATABASE_PORT: 5432 + DATABASE_USERNAME: infisical + DATABASE_PASSWORD: ${INFISICAL_POSTGRES_PASSWORD:?set in infisical.env} + DATABASE_NAME: infisical + DB_CONNECTION_URI: ${INFISICAL_DATABASE_URL:?computed_in_env_file} + DB_HOST: postgres + DB_PORT: 5432 + DB_USER: infisical + DB_PASSWORD: ${INFISICAL_POSTGRES_PASSWORD:?set in infisical.env} + DB_NAME: infisical + TELEMETRY_ENABLED: ${INFISICAL_TELEMETRY_ENABLED:-false} + ports: + - "127.0.0.1:${INFISICAL_PORT:-8080}:8080" + networks: + - infisical + +networks: + infisical: + driver: bridge + +volumes: + postgres-data: + redis-data: