This commit is contained in:
parent
26c3c0bb0e
commit
222ad13724
@ -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
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,8 +7,8 @@ node_modules/
|
||||
/packages/**/dist/
|
||||
|
||||
# Env & secrets
|
||||
.env.local
|
||||
.env
|
||||
.env.local
|
||||
.env.*
|
||||
!.env.example
|
||||
apps/**/.env
|
||||
|
||||
@ -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 <previous-commit-hash>
|
||||
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)
|
||||
|
||||
@ -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)
|
||||
|
||||
80
INFISICAL_SETUP.md
Normal file
80
INFISICAL_SETUP.md
Normal file
@ -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.
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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';
|
||||
|
||||
61
deploy.sh
61
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"
|
||||
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
|
||||
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}"
|
||||
|
||||
9
infisical/.env.example
Normal file
9
infisical/.env.example
Normal file
@ -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
|
||||
66
infisical/docker-compose.yml
Normal file
66
infisical/docker-compose.yml
Normal file
@ -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:
|
||||
Loading…
Reference in New Issue
Block a user