mirror of
https://github.com/taogaetz/chefbible.git
synced 2025-12-06 11:47:24 -05:00
docker compose fixed for persistence
This commit is contained in:
parent
208c8be6c4
commit
3d043d9ade
103
DATABASE_MANAGEMENT.md
Normal file
103
DATABASE_MANAGEMENT.md
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
# ChefBible Database Management
|
||||||
|
|
||||||
|
Easy backup and restore for your recipes using the `db-manager.sh` script.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Setup (One-time)
|
||||||
|
```bash
|
||||||
|
# Create the data directory on your server
|
||||||
|
sudo mkdir -p /home/chefbible/data
|
||||||
|
sudo chown 1001:1001 /home/chefbible/data
|
||||||
|
sudo chmod 755 /home/chefbible/data
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Before Updating Your App
|
||||||
|
```bash
|
||||||
|
# Create a backup
|
||||||
|
./db-manager.sh backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. After Updating (if data is lost)
|
||||||
|
```bash
|
||||||
|
# Restore from the latest backup
|
||||||
|
./db-manager.sh restore
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Commands
|
||||||
|
|
||||||
|
| Command | Description | Example |
|
||||||
|
|---------|-------------|---------|
|
||||||
|
| `backup` | Create a backup of the database | `./db-manager.sh backup` |
|
||||||
|
| `restore` | Restore from the most recent backup | `./db-manager.sh restore` |
|
||||||
|
| `list` | List all available backups | `./db-manager.sh list` |
|
||||||
|
| `status` | Show current database status | `./db-manager.sh status` |
|
||||||
|
| `help` | Show help message | `./db-manager.sh help` |
|
||||||
|
|
||||||
|
## Typical Workflow
|
||||||
|
|
||||||
|
### Before App Updates:
|
||||||
|
```bash
|
||||||
|
# 1. Check current status
|
||||||
|
./db-manager.sh status
|
||||||
|
|
||||||
|
# 2. Create backup
|
||||||
|
./db-manager.sh backup
|
||||||
|
|
||||||
|
# 3. Update your app in Portainer
|
||||||
|
# (Your data should be preserved, but backup is safety net)
|
||||||
|
```
|
||||||
|
|
||||||
|
### If Data Gets Lost:
|
||||||
|
```bash
|
||||||
|
# 1. Check available backups
|
||||||
|
./db-manager.sh list
|
||||||
|
|
||||||
|
# 2. Restore from latest backup
|
||||||
|
./db-manager.sh restore
|
||||||
|
|
||||||
|
# 3. Restart your container in Portainer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup Storage
|
||||||
|
|
||||||
|
- **Location**: `/home/chefbible/backups/`
|
||||||
|
- **Format**: `chefbible_backup_YYYYMMDD_HHMMSS.db`
|
||||||
|
- **Retention**: Keeps last 5 backups automatically
|
||||||
|
- **Size**: Typically 1-10MB per backup
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Permission Issues
|
||||||
|
```bash
|
||||||
|
# If you get permission errors, run with sudo:
|
||||||
|
sudo ./db-manager.sh backup
|
||||||
|
sudo ./db-manager.sh restore
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container Not Found
|
||||||
|
```bash
|
||||||
|
# If container name is different, edit the script:
|
||||||
|
# Change CONTAINER_NAME="chefbible" to your actual container name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Path Issues
|
||||||
|
```bash
|
||||||
|
# Check if the database path exists:
|
||||||
|
ls -la /home/chefbible/data/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safety Features
|
||||||
|
|
||||||
|
✅ **Automatic cleanup** - Keeps only last 5 backups
|
||||||
|
✅ **Size reporting** - Shows backup sizes
|
||||||
|
✅ **Status checking** - Verifies database and container state
|
||||||
|
✅ **Error handling** - Clear error messages and suggestions
|
||||||
|
✅ **Color output** - Easy to read status messages
|
||||||
|
|
||||||
|
## Pro Tips
|
||||||
|
|
||||||
|
1. **Always backup before major updates**
|
||||||
|
2. **Test restore process** on a test environment first
|
||||||
|
3. **Keep backups in a separate location** for extra safety
|
||||||
|
4. **Monitor backup sizes** - large backups might indicate issues
|
||||||
169
db-manager.sh
Executable file
169
db-manager.sh
Executable file
@ -0,0 +1,169 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# ChefBible Database Manager
|
||||||
|
# Easy backup and restore for your recipes
|
||||||
|
|
||||||
|
BACKUP_DIR="/home/chefbible/backups"
|
||||||
|
DB_PATH="/home/chefbible/data/database.db"
|
||||||
|
CONTAINER_NAME="chefbible"
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
echo -e "${BLUE}ChefBible Database Manager${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Usage: $0 [command]"
|
||||||
|
echo ""
|
||||||
|
echo "Commands:"
|
||||||
|
echo " backup - Create a backup of the database"
|
||||||
|
echo " restore - Restore from the most recent backup"
|
||||||
|
echo " list - List all available backups"
|
||||||
|
echo " status - Show current database status"
|
||||||
|
echo " help - Show this help message"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 backup # Create backup"
|
||||||
|
echo " $0 restore # Restore from latest backup"
|
||||||
|
echo " $0 restore backup_20240101 # Restore from specific backup"
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_database() {
|
||||||
|
echo -e "${BLUE}🔄 Creating backup of ChefBible database...${NC}"
|
||||||
|
|
||||||
|
# Create backup directory if it doesn't exist
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# Check if database exists
|
||||||
|
if [ -f "$DB_PATH" ]; then
|
||||||
|
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/chefbible_backup_$TIMESTAMP.db"
|
||||||
|
|
||||||
|
# Create backup
|
||||||
|
cp "$DB_PATH" "$BACKUP_FILE"
|
||||||
|
echo -e "${GREEN}✅ Backup created: $(basename "$BACKUP_FILE")${NC}"
|
||||||
|
|
||||||
|
# Show backup size
|
||||||
|
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
||||||
|
echo -e "${GREEN}📊 Backup size: $BACKUP_SIZE${NC}"
|
||||||
|
|
||||||
|
# Keep only last 5 backups
|
||||||
|
ls -t "$BACKUP_DIR"/chefbible_backup_*.db 2>/dev/null | tail -n +6 | xargs -r rm
|
||||||
|
echo -e "${GREEN}🧹 Cleaned up old backups (keeping last 5)${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠️ No database found at $DB_PATH${NC}"
|
||||||
|
echo -e "${YELLOW} This might be a fresh installation${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}🎉 Backup process complete!${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_database() {
|
||||||
|
echo -e "${BLUE}🔄 Restoring ChefBible database...${NC}"
|
||||||
|
|
||||||
|
# Create data directory if it doesn't exist
|
||||||
|
mkdir -p "$(dirname "$DB_PATH")"
|
||||||
|
|
||||||
|
# If no backup file specified, use the most recent one
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
BACKUP_FILE=$(ls -t "$BACKUP_DIR"/chefbible_backup_*.db 2>/dev/null | head -n1)
|
||||||
|
if [ -z "$BACKUP_FILE" ]; then
|
||||||
|
echo -e "${RED}❌ No backup files found in $BACKUP_DIR${NC}"
|
||||||
|
echo -e "${YELLOW} Run '$0 backup' first to create a backup${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -e "${BLUE}📁 Using most recent backup: $(basename "$BACKUP_FILE")${NC}"
|
||||||
|
else
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/chefbible_backup_$1.db"
|
||||||
|
if [ ! -f "$BACKUP_FILE" ]; then
|
||||||
|
echo -e "${RED}❌ Backup file not found: $BACKUP_FILE${NC}"
|
||||||
|
echo -e "${YELLOW} Available backups:${NC}"
|
||||||
|
list_backups
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stop the container if it's running
|
||||||
|
echo -e "${YELLOW}⏹️ Stopping ChefBible container...${NC}"
|
||||||
|
docker stop "$CONTAINER_NAME" 2>/dev/null || echo -e "${YELLOW} Container not running${NC}"
|
||||||
|
|
||||||
|
# Restore the database
|
||||||
|
echo -e "${BLUE}📥 Restoring database from: $(basename "$BACKUP_FILE")${NC}"
|
||||||
|
cp "$BACKUP_FILE" "$DB_PATH"
|
||||||
|
|
||||||
|
# Set proper permissions
|
||||||
|
chown 1001:1001 "$DB_PATH" 2>/dev/null || echo -e "${YELLOW} Note: Run with sudo to set proper permissions${NC}"
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ Database restored successfully!${NC}"
|
||||||
|
echo -e "${GREEN}🚀 You can now start the ChefBible container${NC}"
|
||||||
|
|
||||||
|
# Show restored database size
|
||||||
|
if [ -f "$DB_PATH" ]; then
|
||||||
|
DB_SIZE=$(du -h "$DB_PATH" | cut -f1)
|
||||||
|
echo -e "${GREEN}📊 Restored database size: $DB_SIZE${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
list_backups() {
|
||||||
|
echo -e "${BLUE}📋 Available backups:${NC}"
|
||||||
|
if [ -d "$BACKUP_DIR" ] && [ "$(ls -A "$BACKUP_DIR"/chefbible_backup_*.db 2>/dev/null)" ]; then
|
||||||
|
ls -lh "$BACKUP_DIR"/chefbible_backup_*.db | awk '{print " " $9 " (" $5 ", " $6 " " $7 " " $8 ")"}'
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW} No backups found${NC}"
|
||||||
|
echo -e "${YELLOW} Run '$0 backup' to create your first backup${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
echo -e "${BLUE}📊 ChefBible Database Status${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if database exists
|
||||||
|
if [ -f "$DB_PATH" ]; then
|
||||||
|
DB_SIZE=$(du -h "$DB_PATH" | cut -f1)
|
||||||
|
echo -e "${GREEN}✅ Database exists: $DB_PATH${NC}"
|
||||||
|
echo -e "${GREEN}📊 Size: $DB_SIZE${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ No database found at $DB_PATH${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check container status
|
||||||
|
if docker ps | grep -q "$CONTAINER_NAME"; then
|
||||||
|
echo -e "${GREEN}✅ Container is running${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠️ Container is not running${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show backup count
|
||||||
|
BACKUP_COUNT=$(ls "$BACKUP_DIR"/chefbible_backup_*.db 2>/dev/null | wc -l)
|
||||||
|
echo -e "${BLUE}📋 Backups available: $BACKUP_COUNT${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main script logic
|
||||||
|
case "$1" in
|
||||||
|
"backup")
|
||||||
|
backup_database
|
||||||
|
;;
|
||||||
|
"restore")
|
||||||
|
restore_database "$2"
|
||||||
|
;;
|
||||||
|
"list")
|
||||||
|
list_backups
|
||||||
|
;;
|
||||||
|
"status")
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
"help"|"--help"|"-h"|"")
|
||||||
|
show_help
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}❌ Unknown command: $1${NC}"
|
||||||
|
echo ""
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
13
deploy.sh
Executable file
13
deploy.sh
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Super Simple ChefBible Deploy
|
||||||
|
# Just run: ./deploy.sh
|
||||||
|
|
||||||
|
echo "🚀 Building and pushing ChefBible..."
|
||||||
|
|
||||||
|
# Build and push in one go
|
||||||
|
docker build -t git.redbackpack.ca/taogaetz/chefbible:latest . && \
|
||||||
|
docker push git.redbackpack.ca/taogaetz/chefbible:latest
|
||||||
|
|
||||||
|
echo "✅ Done! Now go to Portainer and click 'Update the stack'"
|
||||||
|
echo "💾 If data gets lost, run './db-manager.sh restore' on your server"
|
||||||
@ -6,7 +6,7 @@ services:
|
|||||||
container_name: chefbible
|
container_name: chefbible
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "4000:3000"
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- DATABASE_URL=file:/app/data/database.db
|
- DATABASE_URL=file:/app/data/database.db
|
||||||
@ -14,14 +14,10 @@ services:
|
|||||||
- CLOUDINARY_URL=${CLOUDINARY_URL}
|
- CLOUDINARY_URL=${CLOUDINARY_URL}
|
||||||
- ORIGIN=${ORIGIN}
|
- ORIGIN=${ORIGIN}
|
||||||
volumes:
|
volumes:
|
||||||
- chefbible_data:/app/data
|
- /home/chefbible/data:/app/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" ]
|
test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" ]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
||||||
volumes:
|
|
||||||
chefbible_data:
|
|
||||||
driver: local
|
|
||||||
|
|||||||
@ -11,8 +11,14 @@ if [ -z "$DATABASE_URL" ]; then
|
|||||||
fi
|
fi
|
||||||
npx prisma migrate deploy
|
npx prisma migrate deploy
|
||||||
|
|
||||||
echo "Seeding database..."
|
echo "Checking if database needs seeding..."
|
||||||
npx prisma db seed
|
# Check if database file exists and has content
|
||||||
|
if [ ! -f "/app/data/database.db" ] || [ ! -s "/app/data/database.db" ]; then
|
||||||
|
echo "Database is empty or doesn't exist, seeding with sample data..."
|
||||||
|
npx prisma db seed
|
||||||
|
else
|
||||||
|
echo "Database already exists with data, skipping seed..."
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Starting application..."
|
echo "Starting application..."
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
@ -14,7 +14,7 @@ generator client {
|
|||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "sqlite"
|
provider = "sqlite"
|
||||||
url = "file:./database.db"
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Recipe {
|
model Recipe {
|
||||||
|
|||||||
@ -3,13 +3,14 @@ import { PrismaClient } from '@prisma-app/client';
|
|||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
console.log('🧹 Clearing existing records...');
|
// Check if database already has data
|
||||||
await prisma.menuRecipe.deleteMany();
|
const existingRecipes = await prisma.recipe.count();
|
||||||
await prisma.recipeIngredient.deleteMany();
|
|
||||||
await prisma.menu.deleteMany();
|
if (existingRecipes > 0) {
|
||||||
await prisma.recipe.deleteMany();
|
console.log('📊 Database already contains data, skipping seed...');
|
||||||
await prisma.ingredient.deleteMany();
|
console.log(`Found ${existingRecipes} existing recipes`);
|
||||||
await prisma.allergen.deleteMany();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('🌱 Seeding database with new recipes...');
|
console.log('🌱 Seeding database with new recipes...');
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import prisma from '$lib/server/prisma';
|
|||||||
// Configure Cloudinary conditionally
|
// Configure Cloudinary conditionally
|
||||||
let cloudinary: any = null;
|
let cloudinary: any = null;
|
||||||
try {
|
try {
|
||||||
const { CLOUDINARY_URL } = await import('$env/static/private');
|
const CLOUDINARY_URL = process.env.CLOUDINARY_URL;
|
||||||
if (CLOUDINARY_URL) {
|
if (CLOUDINARY_URL) {
|
||||||
const { v2 } = await import('cloudinary');
|
const { v2 } = await import('cloudinary');
|
||||||
cloudinary = v2;
|
cloudinary = v2;
|
||||||
@ -62,7 +62,7 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
|||||||
{ quality: 'auto:good' }
|
{ quality: 'auto:good' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
(error, result) => {
|
(error: any, result: any) => {
|
||||||
if (error) reject(error);
|
if (error) reject(error);
|
||||||
else if (result) resolve(result as { secure_url: string });
|
else if (result) resolve(result as { secure_url: string });
|
||||||
else reject(new Error('No result from Cloudinary'));
|
else reject(new Error('No result from Cloudinary'));
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import prisma from '$lib/server/prisma';
|
|||||||
// Configure Cloudinary conditionally
|
// Configure Cloudinary conditionally
|
||||||
let cloudinary: any = null;
|
let cloudinary: any = null;
|
||||||
try {
|
try {
|
||||||
const { CLOUDINARY_URL } = await import('$env/static/private');
|
const CLOUDINARY_URL = process.env.CLOUDINARY_URL;
|
||||||
if (CLOUDINARY_URL) {
|
if (CLOUDINARY_URL) {
|
||||||
const { v2 } = await import('cloudinary');
|
const { v2 } = await import('cloudinary');
|
||||||
cloudinary = v2;
|
cloudinary = v2;
|
||||||
@ -62,7 +62,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
{ quality: 'auto:good' }
|
{ quality: 'auto:good' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
(error, result) => {
|
(error: any, result: any) => {
|
||||||
if (error) reject(error);
|
if (error) reject(error);
|
||||||
else if (result) resolve(result as { secure_url: string });
|
else if (result) resolve(result as { secure_url: string });
|
||||||
else reject(new Error('No result from Cloudinary'));
|
else reject(new Error('No result from Cloudinary'));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user