add admin token authentication for migration script
All checks were successful
CI / update (push) Successful in 12s
All checks were successful
CI / update (push) Successful in 12s
Allow migration to run without browser session by using ADMIN_SECRET_TOKEN environment variable. This enables running the migration directly on the server via SSH. Changes: - Add ADMIN_SECRET_TOKEN support to migration endpoint - Update shell script to read token from environment - Improve script with better error handling and token validation - Update documentation with admin token setup instructions The endpoint now accepts authentication via either: - Valid user session (browser-based) - ADMIN_SECRET_TOKEN from environment (server-based) Usage on server: source .env && ./scripts/migrate-image-hashes.sh
This commit is contained in:
@@ -27,11 +27,25 @@ The migration will:
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Must be logged in as admin
|
- **Authentication**: Either be logged in as admin OR have `ADMIN_SECRET_TOKEN` set
|
||||||
- Only runs in production (when `IMAGE_DIR=/var/lib/www/static`)
|
- Only runs in production (when `IMAGE_DIR=/var/lib/www/static`)
|
||||||
- Requires confirmation token to prevent accidental runs
|
- Requires confirmation token to prevent accidental runs
|
||||||
- Backup your database before running (recommended)
|
- Backup your database before running (recommended)
|
||||||
|
|
||||||
|
### Setting Up Admin Token (Production Server)
|
||||||
|
|
||||||
|
Add `ADMIN_SECRET_TOKEN` to your production `.env` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate a secure random token
|
||||||
|
openssl rand -hex 32
|
||||||
|
|
||||||
|
# Add to .env (production only!)
|
||||||
|
echo "ADMIN_SECRET_TOKEN=your-generated-token-here" >> .env
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: Keep this token secret and only set it on the production server. Do NOT commit it to git.
|
||||||
|
|
||||||
## Step 1: Deploy Code Changes
|
## Step 1: Deploy Code Changes
|
||||||
|
|
||||||
Deploy the updated codebase to production. The changes include:
|
Deploy the updated codebase to production. The changes include:
|
||||||
@@ -66,7 +80,41 @@ sudo nginx -t && sudo nginx -s reload
|
|||||||
|
|
||||||
## Step 3: Run Migration
|
## Step 3: Run Migration
|
||||||
|
|
||||||
### Option 1: Using curl (Recommended)
|
### Option 1: Using Shell Script (Recommended for Server)
|
||||||
|
|
||||||
|
SSH into your production server and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/homepage
|
||||||
|
|
||||||
|
# Source your .env to get ADMIN_SECRET_TOKEN
|
||||||
|
source .env
|
||||||
|
|
||||||
|
# Make script executable (first time only)
|
||||||
|
chmod +x scripts/migrate-image-hashes.sh
|
||||||
|
|
||||||
|
# Run migration
|
||||||
|
./scripts/migrate-image-hashes.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
- Check that `ADMIN_SECRET_TOKEN` is set
|
||||||
|
- Ask for confirmation
|
||||||
|
- Call the API endpoint with the admin token
|
||||||
|
- Pretty-print the results
|
||||||
|
|
||||||
|
### Option 2: Using curl with Admin Token
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On production server with .env sourced
|
||||||
|
source .env
|
||||||
|
|
||||||
|
curl -X POST https://bocken.org/api/admin/migrate-image-hashes \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"confirm\": \"MIGRATE_IMAGES\", \"adminToken\": \"$ADMIN_SECRET_TOKEN\"}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Using curl with Session Cookie (Browser)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Get your session cookie from browser DevTools
|
# Get your session cookie from browser DevTools
|
||||||
@@ -78,22 +126,7 @@ curl -X POST https://bocken.org/api/admin/migrate-image-hashes \
|
|||||||
-d '{"confirm": "MIGRATE_IMAGES"}'
|
-d '{"confirm": "MIGRATE_IMAGES"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Option 2: Using the Shell Script
|
### Option 4: Using Browser (Postman, Insomnia, etc.)
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /path/to/homepage
|
|
||||||
|
|
||||||
# Save your session cookie to a file (from browser DevTools)
|
|
||||||
echo "your-session-cookie-value" > .prod-session-cookie
|
|
||||||
|
|
||||||
# Make script executable
|
|
||||||
chmod +x scripts/migrate-image-hashes.sh
|
|
||||||
|
|
||||||
# Run migration
|
|
||||||
./scripts/migrate-image-hashes.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 3: Using Browser (Postman, Insomnia, etc.)
|
|
||||||
|
|
||||||
1. Make sure you're logged in to bocken.org in your browser
|
1. Make sure you're logged in to bocken.org in your browser
|
||||||
2. Send POST request to: `https://bocken.org/api/admin/migrate-image-hashes`
|
2. Send POST request to: `https://bocken.org/api/admin/migrate-image-hashes`
|
||||||
@@ -186,7 +219,7 @@ If something goes wrong:
|
|||||||
|
|
||||||
1. ✅ **Production-only**: Won't run unless `IMAGE_DIR=/var/lib/www/static`
|
1. ✅ **Production-only**: Won't run unless `IMAGE_DIR=/var/lib/www/static`
|
||||||
2. ✅ **Confirmation token**: Requires `{"confirm": "MIGRATE_IMAGES"}` in request body
|
2. ✅ **Confirmation token**: Requires `{"confirm": "MIGRATE_IMAGES"}` in request body
|
||||||
3. ✅ **Authentication**: Requires logged-in user
|
3. ✅ **Authentication**: Requires either logged-in user OR valid `ADMIN_SECRET_TOKEN`
|
||||||
4. ✅ **Non-destructive**: Copies files (keeps originals)
|
4. ✅ **Non-destructive**: Copies files (keeps originals)
|
||||||
5. ✅ **Skip already migrated**: Won't re-process files that already have hashes
|
5. ✅ **Skip already migrated**: Won't re-process files that already have hashes
|
||||||
6. ✅ **Detailed logging**: Returns detailed report of what was changed
|
6. ✅ **Detailed logging**: Returns detailed report of what was changed
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# It will:
|
# It will:
|
||||||
# 1. Find all images without hashes (shortname.webp)
|
# 1. Find all images without hashes (shortname.webp)
|
||||||
# 2. Generate content hashes for them
|
# 2. Generate content hashes for them
|
||||||
# 3. Rename them to shortname.{hash}.webp
|
# 3. Copy them to shortname.{hash}.webp (keeps originals)
|
||||||
# 4. Update the database
|
# 4. Update the database
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
@@ -15,10 +15,24 @@ echo "Image Hash Migration Script"
|
|||||||
echo "======================================"
|
echo "======================================"
|
||||||
echo ""
|
echo ""
|
||||||
echo "This will migrate all existing images to use content-based hashes."
|
echo "This will migrate all existing images to use content-based hashes."
|
||||||
echo "Images will be renamed from 'shortname.webp' to 'shortname.{hash}.webp'"
|
echo "Images will be copied from 'shortname.webp' to 'shortname.{hash}.webp'"
|
||||||
|
echo "Original unhashed files will be kept for graceful degradation."
|
||||||
echo ""
|
echo ""
|
||||||
echo "⚠️ WARNING: This operation will rename files on disk!"
|
echo "⚠️ WARNING: This operation will modify the database and create new files!"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Check for admin token
|
||||||
|
if [ -z "$ADMIN_SECRET_TOKEN" ]; then
|
||||||
|
echo "Error: ADMIN_SECRET_TOKEN environment variable not set."
|
||||||
|
echo ""
|
||||||
|
echo "Please set it first:"
|
||||||
|
echo " export ADMIN_SECRET_TOKEN='your-secret-token'"
|
||||||
|
echo ""
|
||||||
|
echo "Or source your .env file:"
|
||||||
|
echo " source .env"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
read -p "Are you sure you want to continue? (yes/no): " confirm
|
read -p "Are you sure you want to continue? (yes/no): " confirm
|
||||||
|
|
||||||
if [ "$confirm" != "yes" ]; then
|
if [ "$confirm" != "yes" ]; then
|
||||||
@@ -31,17 +45,16 @@ echo "Starting migration..."
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Get the production URL (modify this to your production URL)
|
# Get the production URL (modify this to your production URL)
|
||||||
PROD_URL="https://bocken.org"
|
PROD_URL="${PROD_URL:-https://bocken.org}"
|
||||||
|
|
||||||
# Make the API call
|
# Make the API call with admin token
|
||||||
response=$(curl -s -X POST \
|
response=$(curl -s -X POST \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-H "Cookie: $(cat .prod-session-cookie 2>/dev/null || echo '')" \
|
-d "{\"confirm\": \"MIGRATE_IMAGES\", \"adminToken\": \"$ADMIN_SECRET_TOKEN\"}" \
|
||||||
-d '{"confirm": "MIGRATE_IMAGES"}' \
|
|
||||||
"${PROD_URL}/api/admin/migrate-image-hashes")
|
"${PROD_URL}/api/admin/migrate-image-hashes")
|
||||||
|
|
||||||
# Pretty print the response
|
# Pretty print the response
|
||||||
echo "$response" | jq '.'
|
echo "$response" | jq '.' 2>/dev/null || echo "$response"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
import { IMAGE_DIR } from '$env/static/private';
|
import { IMAGE_DIR } from '$env/static/private';
|
||||||
|
import { env } from '$env/dynamic/private';
|
||||||
import { Recipe } from '$models/Recipe';
|
import { Recipe } from '$models/Recipe';
|
||||||
import { connectDB } from '$utils/db';
|
import { connectDB } from '$utils/db';
|
||||||
import { generateImageHash, getHashedFilename } from '$utils/imageHash';
|
import { generateImageHash, getHashedFilename } from '$utils/imageHash';
|
||||||
@@ -15,6 +16,7 @@ export const POST = (async ({ locals, request }) => {
|
|||||||
// Require confirmation token to prevent accidental runs
|
// Require confirmation token to prevent accidental runs
|
||||||
const data = await request.json();
|
const data = await request.json();
|
||||||
const confirmToken = data?.confirm;
|
const confirmToken = data?.confirm;
|
||||||
|
const adminToken = data?.adminToken;
|
||||||
|
|
||||||
if (!isProd) {
|
if (!isProd) {
|
||||||
throw error(403, 'This endpoint only runs in production (IMAGE_DIR must be /var/lib/www)');
|
throw error(403, 'This endpoint only runs in production (IMAGE_DIR must be /var/lib/www)');
|
||||||
@@ -24,8 +26,13 @@ export const POST = (async ({ locals, request }) => {
|
|||||||
throw error(400, 'Missing or invalid confirmation token. Send {"confirm": "MIGRATE_IMAGES"}');
|
throw error(400, 'Missing or invalid confirmation token. Send {"confirm": "MIGRATE_IMAGES"}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check authentication: either valid session OR admin token from env
|
||||||
const auth = await locals.auth();
|
const auth = await locals.auth();
|
||||||
if (!auth) throw error(401, 'Need to be logged in');
|
const isAdminToken = adminToken && env.ADMIN_SECRET_TOKEN && adminToken === env.ADMIN_SECRET_TOKEN;
|
||||||
|
|
||||||
|
if (!auth && !isAdminToken) {
|
||||||
|
throw error(401, 'Need to be logged in or provide valid admin token');
|
||||||
|
}
|
||||||
|
|
||||||
await connectDB();
|
await connectDB();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user