Compare commits
2 Commits
3b69161f1e
...
9d14d67e52
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d14d67e52 | ||
|
|
2ce918e1b3 |
303
DOCKER_DEPLOYMENT_GUIDE.md
Normal file
303
DOCKER_DEPLOYMENT_GUIDE.md
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
# Quality Application - Docker Deployment Guide
|
||||||
|
|
||||||
|
## 📋 Overview
|
||||||
|
|
||||||
|
This application is containerized with Docker and docker-compose, providing:
|
||||||
|
- **MariaDB 11.3** database with persistent storage
|
||||||
|
- **Flask** web application with Gunicorn
|
||||||
|
- **Mapped volumes** for easy access to code, data, and backups
|
||||||
|
|
||||||
|
## 🗂️ Volume Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
quality_app/
|
||||||
|
├── data/
|
||||||
|
│ └── mariadb/ # Database files (MariaDB data directory)
|
||||||
|
├── config/
|
||||||
|
│ └── instance/ # Application configuration (external_server.conf)
|
||||||
|
├── logs/ # Application and Gunicorn logs
|
||||||
|
├── backups/ # Database backup files (shared with DB container)
|
||||||
|
└── py_app/ # Application source code (optional mapping)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### 1. Setup Volumes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create necessary directories
|
||||||
|
bash setup-volumes.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure Environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create .env file from example
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Edit configuration (IMPORTANT: Change passwords!)
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical settings to change:**
|
||||||
|
- `MYSQL_ROOT_PASSWORD` - Database root password
|
||||||
|
- `DB_PASSWORD` - Application database password
|
||||||
|
- `SECRET_KEY` - Flask secret key (generate random string)
|
||||||
|
|
||||||
|
**First deployment settings:**
|
||||||
|
- `INIT_DB=true` - Initialize database schema
|
||||||
|
- `SEED_DB=true` - Seed with default data
|
||||||
|
|
||||||
|
**After first deployment:**
|
||||||
|
- `INIT_DB=false`
|
||||||
|
- `SEED_DB=false`
|
||||||
|
|
||||||
|
### 3. Deploy Application
|
||||||
|
|
||||||
|
**Option A: Automated deployment**
|
||||||
|
```bash
|
||||||
|
bash quick-deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Manual deployment**
|
||||||
|
```bash
|
||||||
|
# Build images
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# Start services
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📦 Application Dependencies
|
||||||
|
|
||||||
|
### Python Packages (from requirements.txt):
|
||||||
|
- Flask - Web framework
|
||||||
|
- Flask-SSLify - SSL support
|
||||||
|
- Werkzeug - WSGI utilities
|
||||||
|
- gunicorn - Production WSGI server
|
||||||
|
- pyodbc - ODBC database connectivity
|
||||||
|
- mariadb - MariaDB connector
|
||||||
|
- reportlab - PDF generation
|
||||||
|
- requests - HTTP library
|
||||||
|
- pandas - Data manipulation
|
||||||
|
- openpyxl - Excel file support
|
||||||
|
- APScheduler - Job scheduling for automated backups
|
||||||
|
|
||||||
|
### System Dependencies (handled in Dockerfile):
|
||||||
|
- Python 3.10
|
||||||
|
- MariaDB client libraries
|
||||||
|
- curl (for health checks)
|
||||||
|
|
||||||
|
## 🐳 Docker Images
|
||||||
|
|
||||||
|
### Web Application
|
||||||
|
- **Base**: python:3.10-slim
|
||||||
|
- **Multi-stage build** for minimal image size
|
||||||
|
- **Non-root user** for security
|
||||||
|
- **Health checks** enabled
|
||||||
|
|
||||||
|
### Database
|
||||||
|
- **Image**: mariadb:11.3
|
||||||
|
- **Persistent storage** with volume mapping
|
||||||
|
- **Performance tuning** via environment variables
|
||||||
|
|
||||||
|
## 📊 Resource Limits
|
||||||
|
|
||||||
|
### Database Container
|
||||||
|
- CPU: 2.0 cores (limit), 0.5 cores (reserved)
|
||||||
|
- Memory: 2GB (limit), 512MB (reserved)
|
||||||
|
- Buffer pool: 512MB
|
||||||
|
|
||||||
|
### Web Container
|
||||||
|
- CPU: 2.0 cores (limit), 0.5 cores (reserved)
|
||||||
|
- Memory: 2GB (limit), 512MB (reserved)
|
||||||
|
- Workers: 5 Gunicorn workers
|
||||||
|
|
||||||
|
## 🔧 Common Operations
|
||||||
|
|
||||||
|
### View Logs
|
||||||
|
```bash
|
||||||
|
# Application logs
|
||||||
|
docker-compose logs -f web
|
||||||
|
|
||||||
|
# Database logs
|
||||||
|
docker-compose logs -f db
|
||||||
|
|
||||||
|
# All logs
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restart Services
|
||||||
|
```bash
|
||||||
|
# Restart all
|
||||||
|
docker-compose restart
|
||||||
|
|
||||||
|
# Restart specific service
|
||||||
|
docker-compose restart web
|
||||||
|
docker-compose restart db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stop Services
|
||||||
|
```bash
|
||||||
|
# Stop (keeps data)
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Stop and remove volumes (WARNING: deletes database!)
|
||||||
|
docker-compose down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Application Code
|
||||||
|
|
||||||
|
**Without rebuilding (development mode):**
|
||||||
|
1. Uncomment volume mapping in docker-compose.yml:
|
||||||
|
```yaml
|
||||||
|
- ${APP_CODE_PATH}:/app:ro
|
||||||
|
```
|
||||||
|
2. Edit code in `./py_app/`
|
||||||
|
3. Restart: `docker-compose restart web`
|
||||||
|
|
||||||
|
**With rebuilding (production mode):**
|
||||||
|
```bash
|
||||||
|
docker-compose build --no-cache web
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Access
|
||||||
|
|
||||||
|
**MySQL shell inside container:**
|
||||||
|
```bash
|
||||||
|
docker-compose exec db mysql -u trasabilitate -p
|
||||||
|
# Enter password: Initial01! (or your custom password)
|
||||||
|
```
|
||||||
|
|
||||||
|
**From host machine:**
|
||||||
|
```bash
|
||||||
|
mysql -h 127.0.0.1 -P 3306 -u trasabilitate -p
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root access:**
|
||||||
|
```bash
|
||||||
|
docker-compose exec db mysql -u root -p
|
||||||
|
```
|
||||||
|
|
||||||
|
## 💾 Backup Operations
|
||||||
|
|
||||||
|
### Manual Backup
|
||||||
|
```bash
|
||||||
|
# Full backup
|
||||||
|
docker-compose exec db mysqldump -u trasabilitate -pInitial01! trasabilitate > backups/manual_$(date +%Y%m%d_%H%M%S).sql
|
||||||
|
|
||||||
|
# Data-only backup
|
||||||
|
docker-compose exec db mysqldump -u trasabilitate -pInitial01! --no-create-info trasabilitate > backups/data_only_$(date +%Y%m%d_%H%M%S).sql
|
||||||
|
|
||||||
|
# Structure-only backup
|
||||||
|
docker-compose exec db mysqldump -u trasabilitate -pInitial01! --no-data trasabilitate > backups/structure_only_$(date +%Y%m%d_%H%M%S).sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automated Backups
|
||||||
|
The application includes a built-in scheduler for automated backups. Configure via the web interface.
|
||||||
|
|
||||||
|
### Restore from Backup
|
||||||
|
```bash
|
||||||
|
# Stop application (keeps database running)
|
||||||
|
docker-compose stop web
|
||||||
|
|
||||||
|
# Restore database
|
||||||
|
docker-compose exec -T db mysql -u trasabilitate -pInitial01! trasabilitate < backups/backup_file.sql
|
||||||
|
|
||||||
|
# Start application
|
||||||
|
docker-compose start web
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Troubleshooting
|
||||||
|
|
||||||
|
### Container won't start
|
||||||
|
```bash
|
||||||
|
# Check logs
|
||||||
|
docker-compose logs db
|
||||||
|
docker-compose logs web
|
||||||
|
|
||||||
|
# Check if ports are available
|
||||||
|
ss -tulpn | grep 8781
|
||||||
|
ss -tulpn | grep 3306
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database connection failed
|
||||||
|
```bash
|
||||||
|
# Check database is healthy
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# Test database connection
|
||||||
|
docker-compose exec db mysqladmin ping -u root -p
|
||||||
|
|
||||||
|
# Check database users
|
||||||
|
docker-compose exec db mysql -u root -p -e "SELECT User, Host FROM mysql.user;"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission issues
|
||||||
|
```bash
|
||||||
|
# Check directory permissions
|
||||||
|
ls -la data/mariadb
|
||||||
|
ls -la logs
|
||||||
|
ls -la backups
|
||||||
|
|
||||||
|
# Fix permissions if needed
|
||||||
|
chmod -R 755 data logs backups config
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reset everything (WARNING: deletes all data!)
|
||||||
|
```bash
|
||||||
|
# Stop and remove containers, volumes
|
||||||
|
docker-compose down -v
|
||||||
|
|
||||||
|
# Remove volume directories
|
||||||
|
rm -rf data/mariadb/* logs/* config/instance/*
|
||||||
|
|
||||||
|
# Start fresh
|
||||||
|
bash quick-deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 Security Notes
|
||||||
|
|
||||||
|
1. **Change default passwords** in .env file
|
||||||
|
2. **Generate new SECRET_KEY** for Flask
|
||||||
|
3. Never commit .env file to version control
|
||||||
|
4. Use firewall rules to restrict database port (3306) access
|
||||||
|
5. Consider using Docker secrets for sensitive data in production
|
||||||
|
6. Regular security updates: `docker-compose pull && docker-compose up -d`
|
||||||
|
|
||||||
|
## 🌐 Port Mapping
|
||||||
|
|
||||||
|
- **8781** - Web application (configurable via APP_PORT in .env)
|
||||||
|
- **3306** - MariaDB database (configurable via DB_PORT in .env)
|
||||||
|
|
||||||
|
## 📁 Configuration Files
|
||||||
|
|
||||||
|
- **docker-compose.yml** - Service orchestration
|
||||||
|
- **.env** - Environment variables and configuration
|
||||||
|
- **Dockerfile** - Web application image definition
|
||||||
|
- **docker-entrypoint.sh** - Container initialization script
|
||||||
|
- **init-db.sql** - Database initialization script
|
||||||
|
|
||||||
|
## 🎯 Production Checklist
|
||||||
|
|
||||||
|
- [ ] Change all default passwords
|
||||||
|
- [ ] Generate secure SECRET_KEY
|
||||||
|
- [ ] Set FLASK_ENV=production
|
||||||
|
- [ ] Configure resource limits appropriately
|
||||||
|
- [ ] Set up backup schedule
|
||||||
|
- [ ] Configure firewall rules
|
||||||
|
- [ ] Set up monitoring and logging
|
||||||
|
- [ ] Test backup/restore procedures
|
||||||
|
- [ ] Document deployment procedure for your team
|
||||||
|
- [ ] Set INIT_DB=false and SEED_DB=false after first deployment
|
||||||
|
|
||||||
|
## 📞 Support
|
||||||
|
|
||||||
|
For issues or questions, refer to:
|
||||||
|
- Documentation in `documentation/` folder
|
||||||
|
- Docker logs: `docker-compose logs -f`
|
||||||
|
- Application logs: `./logs/` directory
|
||||||
@@ -53,6 +53,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
|
|||||||
# Install only runtime dependencies (much smaller than build deps)
|
# Install only runtime dependencies (much smaller than build deps)
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
default-libmysqlclient-dev \
|
default-libmysqlclient-dev \
|
||||||
|
mariadb-client \
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
|||||||
123
IMPROVEMENTS_APPLIED.md
Normal file
123
IMPROVEMENTS_APPLIED.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Improvements Applied to Quality App
|
||||||
|
|
||||||
|
## Date: November 13, 2025
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
All improvements from the production environment have been successfully transposed to the quality_app project.
|
||||||
|
|
||||||
|
## Files Updated/Copied
|
||||||
|
|
||||||
|
### 1. Docker Configuration
|
||||||
|
- **Dockerfile** - Added `mariadb-client` package for backup functionality
|
||||||
|
- **docker-compose.yml** - Updated with proper volume mappings and /data folder support
|
||||||
|
- **.env** - Updated all paths to use absolute paths under `/srv/quality_app/`
|
||||||
|
|
||||||
|
### 2. Backup & Restore System
|
||||||
|
- **database_backup.py** - Fixed backup/restore functions:
|
||||||
|
- Changed `result_success` to `result.returncode == 0`
|
||||||
|
- Added `--skip-ssl` flag for MariaDB connections
|
||||||
|
- Fixed restore function error handling
|
||||||
|
- **restore_database.sh** - Fixed SQL file parsing to handle MariaDB dump format
|
||||||
|
|
||||||
|
### 3. UI Improvements - Sticky Table Headers
|
||||||
|
- **base.css** - Added sticky header CSS for all report tables
|
||||||
|
- **scan.html** - Wrapped table in `report-table-container` div
|
||||||
|
- **fg_scan.html** - Wrapped table in `report-table-container` div
|
||||||
|
|
||||||
|
### 4. Quality Code Display Enhancement
|
||||||
|
- **fg_quality.js** - Quality code `0` displays as "OK" in green; CSV exports as "0"
|
||||||
|
- **script.js** - Same improvements for quality module reports
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
/srv/quality_app/
|
||||||
|
├── py_app/ # Application code (mapped to /app in container)
|
||||||
|
├── data/
|
||||||
|
│ └── mariadb/ # Database files
|
||||||
|
├── config/
|
||||||
|
│ └── instance/ # Application configuration
|
||||||
|
├── logs/ # Application logs
|
||||||
|
├── backups/ # Database backups
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── Dockerfile
|
||||||
|
├── .env
|
||||||
|
└── restore_database.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Configuration
|
||||||
|
|
||||||
|
### Volume Mappings in .env:
|
||||||
|
```
|
||||||
|
DB_DATA_PATH=/srv/quality_app/data/mariadb
|
||||||
|
APP_CODE_PATH=/srv/quality_app/py_app
|
||||||
|
LOGS_PATH=/srv/quality_app/logs
|
||||||
|
INSTANCE_PATH=/srv/quality_app/config/instance
|
||||||
|
BACKUP_PATH=/srv/quality_app/backups
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features Implemented
|
||||||
|
|
||||||
|
### ✅ Backup System
|
||||||
|
- Automatic scheduled backups
|
||||||
|
- Manual backup creation
|
||||||
|
- Data-only backups
|
||||||
|
- Backup retention policies
|
||||||
|
- MariaDB client tools installed
|
||||||
|
|
||||||
|
### ✅ Restore System
|
||||||
|
- Python-based restore function
|
||||||
|
- Shell script restore with proper SQL parsing
|
||||||
|
- Handles MariaDB dump format correctly
|
||||||
|
|
||||||
|
### ✅ UI Enhancements
|
||||||
|
- **Sticky Headers**: Table headers remain fixed when scrolling
|
||||||
|
- **Quality Code Display**:
|
||||||
|
- Shows "OK" in green for quality code 0
|
||||||
|
- Exports "0" in CSV files
|
||||||
|
- Better user experience
|
||||||
|
|
||||||
|
### ✅ Volume Mapping
|
||||||
|
- All volumes use absolute paths
|
||||||
|
- Support for /data folder mapping
|
||||||
|
- Easy to configure backup location on different drives
|
||||||
|
|
||||||
|
## Starting the Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /srv/quality_app
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Backup & Restore
|
||||||
|
|
||||||
|
### Create Backup:
|
||||||
|
```bash
|
||||||
|
cd /srv/quality_app
|
||||||
|
docker compose exec web bash -c "cd /app && python3 -c 'from app import create_app; from app.database_backup import DatabaseBackupManager; app = create_app();
|
||||||
|
with app.app_context(): bm = DatabaseBackupManager(); result = bm.create_backup(); print(result)'"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restore Backup:
|
||||||
|
```bash
|
||||||
|
cd /srv/quality_app
|
||||||
|
./restore_database.sh /srv/quality_app/backups/backup_file.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Database initialization is set to `false` (already initialized)
|
||||||
|
- All improvements are production-ready
|
||||||
|
- Backup path can be changed to external drive if needed
|
||||||
|
- Application port: 8781 (default)
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Review .env file and update passwords if needed
|
||||||
|
2. Test all functionality after deployment
|
||||||
|
3. Configure backup schedule if needed
|
||||||
|
4. Set up external backup drive if desired
|
||||||
|
|
||||||
|
---
|
||||||
|
**Compatibility**: All changes are backward compatible with existing data.
|
||||||
|
**Status**: Ready for deployment
|
||||||
292
MERGE_COMPATIBILITY.md
Normal file
292
MERGE_COMPATIBILITY.md
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
# Merge Compatibility Analysis: docker-deploy → master
|
||||||
|
|
||||||
|
## 📊 Merge Status: **SAFE TO MERGE** ✅
|
||||||
|
|
||||||
|
### Conflict Analysis
|
||||||
|
- **No merge conflicts detected** between `master` and `docker-deploy` branches
|
||||||
|
- All changes are additive or modify existing code in compatible ways
|
||||||
|
- The docker-deploy branch adds 13 files with 1034 insertions and 117 deletions
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
#### New Files (No conflicts):
|
||||||
|
1. `DOCKER_DEPLOYMENT_GUIDE.md` - Documentation
|
||||||
|
2. `IMPROVEMENTS_APPLIED.md` - Documentation
|
||||||
|
3. `quick-deploy.sh` - Deployment script
|
||||||
|
4. `restore_database.sh` - Restore script
|
||||||
|
5. `setup-volumes.sh` - Setup script
|
||||||
|
|
||||||
|
#### Modified Files:
|
||||||
|
1. `Dockerfile` - Added mariadb-client package
|
||||||
|
2. `docker-compose.yml` - Added /data volume mapping, resource limits
|
||||||
|
3. `py_app/app/database_backup.py` - **CRITICAL: Compatibility layer added**
|
||||||
|
4. `py_app/app/static/css/base.css` - Added sticky header styles
|
||||||
|
5. `py_app/app/static/fg_quality.js` - Quality code display enhancement
|
||||||
|
6. `py_app/app/static/script.js` - Quality code display enhancement
|
||||||
|
7. `py_app/app/templates/fg_scan.html` - Added report-table-container wrapper
|
||||||
|
8. `py_app/app/templates/scan.html` - Added report-table-container wrapper
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Compatibility Layer: database_backup.py
|
||||||
|
|
||||||
|
### Problem Identified
|
||||||
|
The docker-deploy branch changed backup commands from `mysqldump` to `mariadb-dump` and added `--skip-ssl` flag, which would break the application when running with standard Gunicorn (non-Docker) deployment.
|
||||||
|
|
||||||
|
### Solution Implemented
|
||||||
|
Added intelligent environment detection and command selection:
|
||||||
|
|
||||||
|
#### 1. Dynamic Command Detection
|
||||||
|
```python
|
||||||
|
def _detect_dump_command(self):
|
||||||
|
"""Detect which mysqldump command is available (mariadb-dump or mysqldump)"""
|
||||||
|
try:
|
||||||
|
# Try mariadb-dump first (newer MariaDB versions)
|
||||||
|
result = subprocess.run(['which', 'mariadb-dump'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return 'mariadb-dump'
|
||||||
|
|
||||||
|
# Fall back to mysqldump
|
||||||
|
result = subprocess.run(['which', 'mysqldump'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return 'mysqldump'
|
||||||
|
|
||||||
|
# Default to mariadb-dump (will error if not available)
|
||||||
|
return 'mariadb-dump'
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not detect dump command: {e}")
|
||||||
|
return 'mysqldump' # Default fallback
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Conditional SSL Arguments
|
||||||
|
```python
|
||||||
|
def _get_ssl_args(self):
|
||||||
|
"""Get SSL arguments based on environment (Docker needs --skip-ssl)"""
|
||||||
|
# Check if running in Docker container
|
||||||
|
if os.path.exists('/.dockerenv') or os.environ.get('DOCKER_CONTAINER'):
|
||||||
|
return ['--skip-ssl']
|
||||||
|
return []
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Updated Backup Command Building
|
||||||
|
```python
|
||||||
|
cmd = [
|
||||||
|
self.dump_command, # Uses detected command (mariadb-dump or mysqldump)
|
||||||
|
f"--host={self.config['host']}",
|
||||||
|
f"--port={self.config['port']}",
|
||||||
|
f"--user={self.config['user']}",
|
||||||
|
f"--password={self.config['password']}",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add SSL args if needed (Docker environment)
|
||||||
|
cmd.extend(self._get_ssl_args())
|
||||||
|
|
||||||
|
# Add backup options
|
||||||
|
cmd.extend([
|
||||||
|
'--single-transaction',
|
||||||
|
'--skip-lock-tables',
|
||||||
|
'--force',
|
||||||
|
# ... other options
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Deployment Scenarios
|
||||||
|
|
||||||
|
### Scenario 1: Docker Deployment (docker-compose)
|
||||||
|
**Environment Detection:**
|
||||||
|
- ✅ `/.dockerenv` file exists
|
||||||
|
- ✅ `DOCKER_CONTAINER` environment variable set in docker-compose.yml
|
||||||
|
|
||||||
|
**Backup Behavior:**
|
||||||
|
- Uses `mariadb-dump` (installed in Dockerfile)
|
||||||
|
- Adds `--skip-ssl` flag automatically
|
||||||
|
- Works correctly ✅
|
||||||
|
|
||||||
|
### Scenario 2: Standard Gunicorn Deployment (systemd service)
|
||||||
|
**Environment Detection:**
|
||||||
|
- ❌ `/.dockerenv` file does NOT exist
|
||||||
|
- ❌ `DOCKER_CONTAINER` environment variable NOT set
|
||||||
|
|
||||||
|
**Backup Behavior:**
|
||||||
|
- Detects available command: `mysqldump` or `mariadb-dump`
|
||||||
|
- Does NOT add `--skip-ssl` flag
|
||||||
|
- Uses system-installed MySQL/MariaDB client tools
|
||||||
|
- Works correctly ✅
|
||||||
|
|
||||||
|
### Scenario 3: Mixed Environment (External Database)
|
||||||
|
**Both deployment types can connect to:**
|
||||||
|
- External MariaDB server
|
||||||
|
- Remote database instance
|
||||||
|
- Local database with proper SSL configuration
|
||||||
|
|
||||||
|
**Backup Behavior:**
|
||||||
|
- Automatically adapts to available tools
|
||||||
|
- SSL handling based on container detection
|
||||||
|
- Works correctly ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing Plan
|
||||||
|
|
||||||
|
### Pre-Merge Testing
|
||||||
|
1. **Docker Environment:**
|
||||||
|
```bash
|
||||||
|
cd /srv/quality_app
|
||||||
|
git checkout docker-deploy
|
||||||
|
docker-compose up -d
|
||||||
|
# Test backup via web UI
|
||||||
|
# Test scheduled backup
|
||||||
|
# Test restore functionality
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Gunicorn Environment:**
|
||||||
|
```bash
|
||||||
|
# Stop Docker if running
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Start with systemd service (if available)
|
||||||
|
sudo systemctl start trasabilitate
|
||||||
|
|
||||||
|
# Test backup via web UI
|
||||||
|
# Test scheduled backup
|
||||||
|
# Test restore functionality
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Command Detection Test:**
|
||||||
|
```bash
|
||||||
|
# Inside Docker container
|
||||||
|
docker-compose exec web python3 -c "
|
||||||
|
from app.database_backup import DatabaseBackupManager
|
||||||
|
manager = DatabaseBackupManager()
|
||||||
|
print(f'Dump command: {manager.dump_command}')
|
||||||
|
print(f'SSL args: {manager._get_ssl_args()}')
|
||||||
|
"
|
||||||
|
|
||||||
|
# On host system (if MySQL client installed)
|
||||||
|
python3 -c "
|
||||||
|
from app.database_backup import DatabaseBackupManager
|
||||||
|
manager = DatabaseBackupManager()
|
||||||
|
print(f'Dump command: {manager.dump_command}')
|
||||||
|
print(f'SSL args: {manager._get_ssl_args()}')
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Post-Merge Testing
|
||||||
|
1. Verify both deployment methods still work
|
||||||
|
2. Test backup/restore in both environments
|
||||||
|
3. Verify scheduled backups function correctly
|
||||||
|
4. Check error handling when tools are missing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Merge Checklist
|
||||||
|
|
||||||
|
- [x] No merge conflicts detected
|
||||||
|
- [x] Compatibility layer implemented in `database_backup.py`
|
||||||
|
- [x] Environment detection for Docker vs Gunicorn
|
||||||
|
- [x] Dynamic command selection (mariadb-dump vs mysqldump)
|
||||||
|
- [x] Conditional SSL flag handling
|
||||||
|
- [x] UI improvements (sticky headers) are purely CSS/JS - no conflicts
|
||||||
|
- [x] Quality code display changes are frontend-only - no conflicts
|
||||||
|
- [x] New documentation files added - no conflicts
|
||||||
|
- [x] Docker-specific files don't affect Gunicorn deployment
|
||||||
|
|
||||||
|
### Safe to Merge Because:
|
||||||
|
1. **Additive Changes**: Most changes are new files or new features
|
||||||
|
2. **Backward Compatible**: Code detects environment and adapts
|
||||||
|
3. **No Breaking Changes**: Gunicorn deployment still works without Docker
|
||||||
|
4. **Independent Features**: UI improvements work in any environment
|
||||||
|
5. **Fail-Safe Defaults**: Falls back to mysqldump if mariadb-dump unavailable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Merge Process
|
||||||
|
|
||||||
|
### Recommended Steps:
|
||||||
|
```bash
|
||||||
|
cd /srv/quality_app
|
||||||
|
|
||||||
|
# 1. Ensure working directory is clean
|
||||||
|
git status
|
||||||
|
|
||||||
|
# 2. Switch to master branch
|
||||||
|
git checkout master
|
||||||
|
|
||||||
|
# 3. Pull latest changes
|
||||||
|
git pull origin master
|
||||||
|
|
||||||
|
# 4. Merge docker-deploy (should be clean merge)
|
||||||
|
git merge docker-deploy
|
||||||
|
|
||||||
|
# 5. Review merge
|
||||||
|
git log --oneline -10
|
||||||
|
|
||||||
|
# 6. Test in current environment
|
||||||
|
# (If using systemd, test the app)
|
||||||
|
# (If using Docker, test with docker-compose)
|
||||||
|
|
||||||
|
# 7. Push to remote
|
||||||
|
git push origin master
|
||||||
|
|
||||||
|
# 8. Tag the release (optional)
|
||||||
|
git tag -a v2.0-docker -m "Docker deployment support with compatibility layer"
|
||||||
|
git push origin v2.0-docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rollback Plan (if needed):
|
||||||
|
```bash
|
||||||
|
# If issues arise after merge
|
||||||
|
git log --oneline -10 # Find commit hash before merge
|
||||||
|
git reset --hard <commit-hash-before-merge>
|
||||||
|
git push origin master --force # Use with caution!
|
||||||
|
|
||||||
|
# Or revert the merge commit
|
||||||
|
git revert -m 1 <merge-commit-hash>
|
||||||
|
git push origin master
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎓 Key Improvements in docker-deploy Branch
|
||||||
|
|
||||||
|
### 1. **Bug Fixes**
|
||||||
|
- Fixed `result_success` variable error → `result.returncode == 0`
|
||||||
|
- Fixed restore SQL parsing with sed preprocessing
|
||||||
|
- Fixed missing mariadb-client in Docker container
|
||||||
|
|
||||||
|
### 2. **Docker Support**
|
||||||
|
- Complete Docker Compose setup
|
||||||
|
- Volume mapping for persistent data
|
||||||
|
- Health checks and resource limits
|
||||||
|
- Environment-based configuration
|
||||||
|
|
||||||
|
### 3. **UI Enhancements**
|
||||||
|
- Sticky table headers for scrollable reports
|
||||||
|
- Quality code 0 displays as "OK" (green)
|
||||||
|
- CSV export preserves original "0" value
|
||||||
|
|
||||||
|
### 4. **Compatibility**
|
||||||
|
- Works in Docker AND traditional Gunicorn deployment
|
||||||
|
- Auto-detects available backup tools
|
||||||
|
- Environment-aware SSL handling
|
||||||
|
- No breaking changes to existing functionality
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Support
|
||||||
|
|
||||||
|
If issues arise after merge:
|
||||||
|
1. Check environment detection: `ls -la /.dockerenv`
|
||||||
|
2. Verify backup tools: `which mysqldump mariadb-dump`
|
||||||
|
3. Review logs: `docker-compose logs web` or application logs
|
||||||
|
4. Test backup manually from command line
|
||||||
|
5. Fall back to master branch if critical issues occur
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** 2025-11-13
|
||||||
|
**Branch:** docker-deploy → master
|
||||||
|
**Status:** Ready for merge ✅
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
version: '3.8'
|
#version: '3.8'
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Recticel Quality Application - Docker Compose Configuration
|
# Recticel Quality Application - Docker Compose Configuration
|
||||||
# Simplified configuration - most settings are in .env file
|
# Production-ready with mapped volumes for code, data, and backups
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
services:
|
services:
|
||||||
@@ -26,8 +26,12 @@ services:
|
|||||||
- "${DB_PORT}:3306"
|
- "${DB_PORT}:3306"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
# Database data persistence - CRITICAL: Do not delete this volume
|
||||||
- ${DB_DATA_PATH}:/var/lib/mysql
|
- ${DB_DATA_PATH}:/var/lib/mysql
|
||||||
|
# Database initialization script
|
||||||
- ./init-db.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
|
- ./init-db.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
|
||||||
|
# Backup folder mapped for easy database dumps
|
||||||
|
- ${BACKUP_PATH}:/backups
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
- quality-app-network
|
- quality-app-network
|
||||||
@@ -116,9 +120,16 @@ services:
|
|||||||
- "${APP_PORT}:8781"
|
- "${APP_PORT}:8781"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
# Application code - mapped for easy updates without rebuilding
|
||||||
|
- ${APP_CODE_PATH}:/app
|
||||||
|
# Application logs - persistent across container restarts
|
||||||
- ${LOGS_PATH}:/srv/quality_app/logs
|
- ${LOGS_PATH}:/srv/quality_app/logs
|
||||||
|
# Instance configuration files (database config)
|
||||||
- ${INSTANCE_PATH}:/app/instance
|
- ${INSTANCE_PATH}:/app/instance
|
||||||
|
# Backup storage - shared with database container
|
||||||
- ${BACKUP_PATH}:/srv/quality_app/backups
|
- ${BACKUP_PATH}:/srv/quality_app/backups
|
||||||
|
# Host /data folder for direct access (includes /data/backups)
|
||||||
|
- /data:/data
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
- quality-app-network
|
- quality-app-network
|
||||||
@@ -159,13 +170,39 @@ networks:
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
# USAGE NOTES
|
# USAGE NOTES
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 1. Copy .env.example to .env and customize all values
|
# VOLUME STRUCTURE:
|
||||||
# 2. Set INIT_DB=true and SEED_DB=true for first deployment only
|
# ./data/mariadb/ - Database files (MariaDB data directory)
|
||||||
# 3. Change default passwords and SECRET_KEY in production
|
# ./config/instance/ - Application configuration (external_server.conf)
|
||||||
# 4. Ensure all volume paths exist with proper permissions:
|
# ./logs/ - Application logs
|
||||||
# mkdir -p /srv/quality_app/{mariadb,logs,backups}
|
# ./backups/ - Database backups
|
||||||
# 5. Start: docker-compose up -d
|
# ./py_app/ - (Optional) Application code for development
|
||||||
# 6. Stop: docker-compose down
|
#
|
||||||
# 7. Logs: docker-compose logs -f web
|
# FIRST TIME SETUP:
|
||||||
# 8. Rebuild: docker-compose up -d --build
|
# 1. Create directory structure:
|
||||||
|
# mkdir -p data/mariadb config/instance logs backups
|
||||||
|
# 2. Copy .env.example to .env and customize all values
|
||||||
|
# 3. Set INIT_DB=true and SEED_DB=true in .env for first deployment
|
||||||
|
# 4. Change default passwords and SECRET_KEY in .env (CRITICAL!)
|
||||||
|
# 5. Build and start: docker-compose up -d --build
|
||||||
|
#
|
||||||
|
# SUBSEQUENT DEPLOYMENTS:
|
||||||
|
# 1. Set INIT_DB=false and SEED_DB=false in .env
|
||||||
|
# 2. Start: docker-compose up -d
|
||||||
|
#
|
||||||
|
# COMMANDS:
|
||||||
|
# - Build and start: docker-compose up -d --build
|
||||||
|
# - Stop: docker-compose down
|
||||||
|
# - Stop & remove data: docker-compose down -v (WARNING: deletes database!)
|
||||||
|
# - View logs: docker-compose logs -f web
|
||||||
|
# - Database logs: docker-compose logs -f db
|
||||||
|
# - Restart: docker-compose restart
|
||||||
|
# - Rebuild image: docker-compose build --no-cache web
|
||||||
|
#
|
||||||
|
# BACKUP:
|
||||||
|
# - Manual backup: docker-compose exec db mysqldump -u trasabilitate -p trasabilitate > backups/manual_backup.sql
|
||||||
|
# - Restore: docker-compose exec -T db mysql -u trasabilitate -p trasabilitate < backups/backup.sql
|
||||||
|
#
|
||||||
|
# DATABASE ACCESS:
|
||||||
|
# - MySQL client: docker-compose exec db mysql -u trasabilitate -p trasabilitate
|
||||||
|
# - From host: mysql -h 127.0.0.1 -P 3306 -u trasabilitate -p
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -23,6 +23,35 @@ class DatabaseBackupManager:
|
|||||||
self.config = self._load_database_config()
|
self.config = self._load_database_config()
|
||||||
self.backup_path = self._get_backup_path()
|
self.backup_path = self._get_backup_path()
|
||||||
self._ensure_backup_directory()
|
self._ensure_backup_directory()
|
||||||
|
self.dump_command = self._detect_dump_command()
|
||||||
|
|
||||||
|
def _detect_dump_command(self):
|
||||||
|
"""Detect which mysqldump command is available (mariadb-dump or mysqldump)"""
|
||||||
|
try:
|
||||||
|
# Try mariadb-dump first (newer MariaDB versions)
|
||||||
|
result = subprocess.run(['which', 'mariadb-dump'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return 'mariadb-dump'
|
||||||
|
|
||||||
|
# Fall back to mysqldump
|
||||||
|
result = subprocess.run(['which', 'mysqldump'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return 'mysqldump'
|
||||||
|
|
||||||
|
# Default to mariadb-dump (will error if not available)
|
||||||
|
return 'mariadb-dump'
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not detect dump command: {e}")
|
||||||
|
return 'mysqldump' # Default fallback
|
||||||
|
|
||||||
|
def _get_ssl_args(self):
|
||||||
|
"""Get SSL arguments based on environment (Docker needs --skip-ssl)"""
|
||||||
|
# Check if running in Docker container
|
||||||
|
if os.path.exists('/.dockerenv') or os.environ.get('DOCKER_CONTAINER'):
|
||||||
|
return ['--skip-ssl']
|
||||||
|
return []
|
||||||
|
|
||||||
def _load_database_config(self):
|
def _load_database_config(self):
|
||||||
"""Load database configuration from external_server.conf"""
|
"""Load database configuration from external_server.conf"""
|
||||||
@@ -104,11 +133,18 @@ class DatabaseBackupManager:
|
|||||||
# Build mysqldump command
|
# Build mysqldump command
|
||||||
# Note: --skip-lock-tables and --force help with views that have permission issues
|
# Note: --skip-lock-tables and --force help with views that have permission issues
|
||||||
cmd = [
|
cmd = [
|
||||||
'mysqldump',
|
self.dump_command,
|
||||||
f"--host={self.config['host']}",
|
f"--host={self.config['host']}",
|
||||||
f"--port={self.config['port']}",
|
f"--port={self.config['port']}",
|
||||||
f"--user={self.config['user']}",
|
f"--user={self.config['user']}",
|
||||||
f"--password={self.config['password']}",
|
f"--password={self.config['password']}",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add SSL args if needed (Docker environment)
|
||||||
|
cmd.extend(self._get_ssl_args())
|
||||||
|
|
||||||
|
# Add backup options
|
||||||
|
cmd.extend([
|
||||||
'--single-transaction',
|
'--single-transaction',
|
||||||
'--skip-lock-tables',
|
'--skip-lock-tables',
|
||||||
'--force',
|
'--force',
|
||||||
@@ -118,7 +154,7 @@ class DatabaseBackupManager:
|
|||||||
'--add-drop-database',
|
'--add-drop-database',
|
||||||
'--databases',
|
'--databases',
|
||||||
self.config['database']
|
self.config['database']
|
||||||
]
|
])
|
||||||
|
|
||||||
# Execute mysqldump and save to file
|
# Execute mysqldump and save to file
|
||||||
with open(backup_file, 'w') as f:
|
with open(backup_file, 'w') as f:
|
||||||
@@ -315,11 +351,18 @@ class DatabaseBackupManager:
|
|||||||
# --complete-insert: Include column names in INSERT (more reliable)
|
# --complete-insert: Include column names in INSERT (more reliable)
|
||||||
# --extended-insert: Use multi-row INSERT for efficiency
|
# --extended-insert: Use multi-row INSERT for efficiency
|
||||||
cmd = [
|
cmd = [
|
||||||
'mysqldump',
|
self.dump_command,
|
||||||
f"--host={self.config['host']}",
|
f"--host={self.config['host']}",
|
||||||
f"--port={self.config['port']}",
|
f"--port={self.config['port']}",
|
||||||
f"--user={self.config['user']}",
|
f"--user={self.config['user']}",
|
||||||
f"--password={self.config['password']}",
|
f"--password={self.config['password']}",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add SSL args if needed (Docker environment)
|
||||||
|
cmd.extend(self._get_ssl_args())
|
||||||
|
|
||||||
|
# Add data-only backup options
|
||||||
|
cmd.extend([
|
||||||
'--no-create-info', # Skip table structure
|
'--no-create-info', # Skip table structure
|
||||||
'--skip-triggers', # Skip triggers
|
'--skip-triggers', # Skip triggers
|
||||||
'--no-create-db', # Skip database creation
|
'--no-create-db', # Skip database creation
|
||||||
@@ -328,7 +371,7 @@ class DatabaseBackupManager:
|
|||||||
'--single-transaction',
|
'--single-transaction',
|
||||||
'--skip-lock-tables',
|
'--skip-lock-tables',
|
||||||
self.config['database']
|
self.config['database']
|
||||||
]
|
])
|
||||||
|
|
||||||
# Execute mysqldump and save to file
|
# Execute mysqldump and save to file
|
||||||
with open(backup_file, 'w') as f:
|
with open(backup_file, 'w') as f:
|
||||||
@@ -396,36 +439,43 @@ class DatabaseBackupManager:
|
|||||||
'message': 'Backup file not found'
|
'message': 'Backup file not found'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build mysql restore command
|
# Read SQL file and execute using Python mariadb library
|
||||||
cmd = [
|
import mariadb
|
||||||
'mysql',
|
|
||||||
f"--host={self.config['host']}",
|
|
||||||
f"--port={self.config['port']}",
|
|
||||||
f"--user={self.config['user']}",
|
|
||||||
f"--password={self.config['password']}"
|
|
||||||
]
|
|
||||||
|
|
||||||
# Execute mysql restore
|
|
||||||
with open(file_path, 'r') as f:
|
with open(file_path, 'r') as f:
|
||||||
result = subprocess.run(
|
sql_content = f.read()
|
||||||
cmd,
|
|
||||||
stdin=f,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode == 0:
|
# Connect to database
|
||||||
return {
|
conn = mariadb.connect(
|
||||||
'success': True,
|
user=self.config['user'],
|
||||||
'message': f'Database restored successfully from {filename}'
|
password=self.config['password'],
|
||||||
}
|
host=self.config['host'],
|
||||||
else:
|
port=int(self.config['port']),
|
||||||
error_msg = result.stderr
|
database=self.config['database']
|
||||||
print(f"Restore error: {error_msg}")
|
)
|
||||||
return {
|
|
||||||
'success': False,
|
cursor = conn.cursor()
|
||||||
'message': f'Restore failed: {error_msg}'
|
|
||||||
}
|
# Split SQL into statements and execute
|
||||||
|
statements = sql_content.split(';')
|
||||||
|
executed = 0
|
||||||
|
|
||||||
|
for statement in statements:
|
||||||
|
statement = statement.strip()
|
||||||
|
if statement:
|
||||||
|
try:
|
||||||
|
cursor.execute(statement)
|
||||||
|
executed += 1
|
||||||
|
except Exception as stmt_error:
|
||||||
|
print(f"Warning executing statement: {stmt_error}")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'message': f'Database restored successfully from {filename} ({executed} statements executed)'
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Exception during restore: {e}")
|
print(f"Exception during restore: {e}")
|
||||||
@@ -499,24 +549,34 @@ class DatabaseBackupManager:
|
|||||||
print(f"Warning during table truncation: {e}")
|
print(f"Warning during table truncation: {e}")
|
||||||
# Continue anyway - the restore might still work
|
# Continue anyway - the restore might still work
|
||||||
|
|
||||||
# Build mysql restore command for data
|
# Read and execute SQL file using Python mariadb library
|
||||||
cmd = [
|
|
||||||
'mysql',
|
|
||||||
f"--host={self.config['host']}",
|
|
||||||
f"--port={self.config['port']}",
|
|
||||||
f"--user={self.config['user']}",
|
|
||||||
f"--password={self.config['password']}",
|
|
||||||
self.config['database']
|
|
||||||
]
|
|
||||||
|
|
||||||
# Execute mysql restore
|
|
||||||
with open(file_path, 'r') as f:
|
with open(file_path, 'r') as f:
|
||||||
result = subprocess.run(
|
sql_content = f.read()
|
||||||
cmd,
|
|
||||||
stdin=f,
|
conn = mariadb.connect(
|
||||||
stderr=subprocess.PIPE,
|
user=self.config['user'],
|
||||||
text=True
|
password=self.config['password'],
|
||||||
)
|
host=self.config['host'],
|
||||||
|
port=int(self.config['port']),
|
||||||
|
database=self.config['database']
|
||||||
|
)
|
||||||
|
|
||||||
|
cursor = conn.cursor()
|
||||||
|
statements = sql_content.split(';')
|
||||||
|
executed = 0
|
||||||
|
|
||||||
|
for statement in statements:
|
||||||
|
statement = statement.strip()
|
||||||
|
if statement:
|
||||||
|
try:
|
||||||
|
cursor.execute(statement)
|
||||||
|
executed += 1
|
||||||
|
except Exception as stmt_error:
|
||||||
|
print(f"Warning executing statement: {stmt_error}")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
result_success = True
|
||||||
|
result_returncode = 0
|
||||||
|
|
||||||
# Re-enable foreign key checks
|
# Re-enable foreign key checks
|
||||||
try:
|
try:
|
||||||
@@ -535,17 +595,15 @@ class DatabaseBackupManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Could not re-enable foreign key checks: {e}")
|
print(f"Warning: Could not re-enable foreign key checks: {e}")
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result_success:
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': f'Data restored successfully from {filename}'
|
'message': f'Data restored successfully from {filename}'
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
error_msg = result.stderr
|
|
||||||
print(f"Data restore error: {error_msg}")
|
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'Data restore failed: {error_msg}'
|
'message': f'Data restore failed'
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -253,4 +253,55 @@ body.dark-mode .floating-back-btn {
|
|||||||
|
|
||||||
body.dark-mode .floating-back-btn:hover {
|
body.dark-mode .floating-back-btn:hover {
|
||||||
background: linear-gradient(135deg, #343a40, #212529);
|
background: linear-gradient(135deg, #343a40, #212529);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
STICKY TABLE HEADERS - Keep first row fixed when scrolling
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.report-table-container {
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-table-container table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-table-container thead th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
z-index: 10;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
padding: 12px 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-table-container tbody td {
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode support for sticky headers */
|
||||||
|
body.dark-mode .report-table-container {
|
||||||
|
border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .report-table-container thead th {
|
||||||
|
background-color: #343a40;
|
||||||
|
border-bottom-color: #495057;
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .report-table-container tbody td {
|
||||||
|
border-bottom-color: #495057;
|
||||||
}
|
}
|
||||||
@@ -171,12 +171,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
thead.innerHTML = '';
|
thead.innerHTML = '';
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
// Find the index of the "Defect Code" column
|
||||||
|
let defectCodeIndex = -1;
|
||||||
|
|
||||||
// Add headers
|
// Add headers
|
||||||
if (data.headers && data.headers.length > 0) {
|
if (data.headers && data.headers.length > 0) {
|
||||||
data.headers.forEach(header => {
|
data.headers.forEach((header, index) => {
|
||||||
const th = document.createElement('th');
|
const th = document.createElement('th');
|
||||||
th.textContent = header;
|
th.textContent = header;
|
||||||
thead.appendChild(th);
|
thead.appendChild(th);
|
||||||
|
|
||||||
|
// Track the defect code column (quality_code)
|
||||||
|
if (header === 'Defect Code' || header === 'Quality Code') {
|
||||||
|
defectCodeIndex = index;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,9 +192,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (data.rows && data.rows.length > 0) {
|
if (data.rows && data.rows.length > 0) {
|
||||||
data.rows.forEach(row => {
|
data.rows.forEach(row => {
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
row.forEach(cell => {
|
row.forEach((cell, index) => {
|
||||||
const td = document.createElement('td');
|
const td = document.createElement('td');
|
||||||
td.textContent = cell || '';
|
|
||||||
|
// Special handling for defect code column
|
||||||
|
if (index === defectCodeIndex && (cell === 0 || cell === '0' || cell === '' || cell === null)) {
|
||||||
|
td.textContent = 'OK';
|
||||||
|
td.style.color = '#28a745'; // Green color for OK
|
||||||
|
td.style.fontWeight = '600';
|
||||||
|
td.setAttribute('data-csv-value', '0'); // Store original value for CSV
|
||||||
|
} else {
|
||||||
|
td.textContent = cell || '';
|
||||||
|
td.setAttribute('data-csv-value', cell || ''); // Store original value
|
||||||
|
}
|
||||||
|
|
||||||
tr.appendChild(td);
|
tr.appendChild(td);
|
||||||
});
|
});
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
@@ -488,7 +507,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const csvContent = rows.map(row => {
|
const csvContent = rows.map(row => {
|
||||||
const cells = Array.from(row.querySelectorAll('th, td'));
|
const cells = Array.from(row.querySelectorAll('th, td'));
|
||||||
return cells.map(cell => {
|
return cells.map(cell => {
|
||||||
let text = cell.textContent.trim();
|
// Use data-csv-value attribute if available (for defect codes), otherwise use text content
|
||||||
|
let text = cell.hasAttribute('data-csv-value') ? cell.getAttribute('data-csv-value') : cell.textContent.trim();
|
||||||
// Escape quotes and wrap in quotes if necessary
|
// Escape quotes and wrap in quotes if necessary
|
||||||
if (text.includes(',') || text.includes('"') || text.includes('\n')) {
|
if (text.includes(',') || text.includes('"') || text.includes('\n')) {
|
||||||
text = '"' + text.replace(/"/g, '""') + '"';
|
text = '"' + text.replace(/"/g, '""') + '"';
|
||||||
|
|||||||
@@ -42,13 +42,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Clear existing table content
|
// Clear existing table content
|
||||||
tableHead.innerHTML = '';
|
tableHead.innerHTML = '';
|
||||||
tableBody.innerHTML = '';
|
tableBody.innerHTML = '';
|
||||||
|
|
||||||
|
// Find the index of the "Defect Code" column
|
||||||
|
let defectCodeIndex = -1;
|
||||||
|
|
||||||
if (data.headers && data.rows && data.rows.length > 0) {
|
if (data.headers && data.rows && data.rows.length > 0) {
|
||||||
// Populate table headers
|
// Populate table headers
|
||||||
data.headers.forEach((header) => {
|
data.headers.forEach((header, index) => {
|
||||||
const th = document.createElement('th');
|
const th = document.createElement('th');
|
||||||
th.textContent = header;
|
th.textContent = header;
|
||||||
tableHead.appendChild(th);
|
tableHead.appendChild(th);
|
||||||
|
|
||||||
|
// Track the defect code column (quality_code)
|
||||||
|
if (header === 'Defect Code' || header === 'Quality Code') {
|
||||||
|
defectCodeIndex = index;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Populate table rows
|
// Populate table rows
|
||||||
@@ -57,8 +65,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
row.forEach((cell, index) => {
|
row.forEach((cell, index) => {
|
||||||
const td = document.createElement('td');
|
const td = document.createElement('td');
|
||||||
|
|
||||||
// Use the cell data as-is since backend now handles formatting
|
// Special handling for defect code column
|
||||||
td.textContent = cell;
|
if (index === defectCodeIndex && (cell === 0 || cell === '0' || cell === '' || cell === null)) {
|
||||||
|
td.textContent = 'OK';
|
||||||
|
td.style.color = '#28a745'; // Green color for OK
|
||||||
|
td.style.fontWeight = '600';
|
||||||
|
td.setAttribute('data-csv-value', '0'); // Store original value for CSV
|
||||||
|
} else {
|
||||||
|
// Use the cell data as-is since backend now handles formatting
|
||||||
|
td.textContent = cell;
|
||||||
|
td.setAttribute('data-csv-value', cell || ''); // Store original value
|
||||||
|
}
|
||||||
|
|
||||||
tr.appendChild(td);
|
tr.appendChild(td);
|
||||||
});
|
});
|
||||||
@@ -96,7 +113,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Loop through each row in the table
|
// Loop through each row in the table
|
||||||
rows.forEach((row) => {
|
rows.forEach((row) => {
|
||||||
const cells = row.querySelectorAll('th, td');
|
const cells = row.querySelectorAll('th, td');
|
||||||
const rowData = Array.from(cells).map((cell) => `"${cell.textContent.trim()}"`);
|
const rowData = Array.from(cells).map((cell) => {
|
||||||
|
// Use data-csv-value attribute if available (for defect codes), otherwise use text content
|
||||||
|
const value = cell.hasAttribute('data-csv-value') ? cell.getAttribute('data-csv-value') : cell.textContent.trim();
|
||||||
|
return `"${value}"`;
|
||||||
|
});
|
||||||
csv.push(rowData.join(','));
|
csv.push(rowData.join(','));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -537,38 +537,40 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<!-- Latest Scans Card -->
|
<!-- Latest Scans Card -->
|
||||||
<div class="card scan-table-card">
|
<div class="card scan-table-card">
|
||||||
<h3>Latest Scans</h3>
|
<h3>Latest Scans</h3>
|
||||||
<table class="scan-table">
|
<div class="report-table-container">
|
||||||
<thead>
|
<table class="scan-table">
|
||||||
<tr>
|
<thead>
|
||||||
<th>ID</th>
|
<tr>
|
||||||
<th>Op Code</th>
|
<th>ID</th>
|
||||||
<th>CP Code</th>
|
<th>Op Code</th>
|
||||||
<th>OC1 Code</th>
|
<th>CP Code</th>
|
||||||
<th>OC2 Code</th>
|
<th>OC1 Code</th>
|
||||||
<th>Defect Code</th>
|
<th>OC2 Code</th>
|
||||||
<th>Date</th>
|
<th>Defect Code</th>
|
||||||
<th>Time</th>
|
<th>Date</th>
|
||||||
<th>Apr. Quantity</th>
|
<th>Time</th>
|
||||||
<th>Rejec. Quantity</th>
|
<th>Apr. Quantity</th>
|
||||||
</tr>
|
<th>Rejec. Quantity</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{% for row in scan_data %}
|
<tbody>
|
||||||
<tr>
|
{% for row in scan_data %}
|
||||||
<td>{{ row[0] }}</td> <!-- Id -->
|
<tr>
|
||||||
<td>{{ row[1] }}</td> <!-- operator_code -->
|
<td>{{ row[0] }}</td> <!-- Id -->
|
||||||
<td>{{ row[2] }}</td> <!-- CP_full_code -->
|
<td>{{ row[1] }}</td> <!-- operator_code -->
|
||||||
<td>{{ row[3] }}</td> <!-- OC1_code -->
|
<td>{{ row[2] }}</td> <!-- CP_full_code -->
|
||||||
<td>{{ row[4] }}</td> <!-- OC2_code -->
|
<td>{{ row[3] }}</td> <!-- OC1_code -->
|
||||||
<td>{{ row[5] }}</td> <!-- quality_code -->
|
<td>{{ row[4] }}</td> <!-- OC2_code -->
|
||||||
<td>{{ row[6] }}</td> <!-- date -->
|
<td>{{ row[5] }}</td> <!-- quality_code -->
|
||||||
<td>{{ row[7] }}</td> <!-- time -->
|
<td>{{ row[6] }}</td> <!-- date -->
|
||||||
<td>{{ row[8] }}</td> <!-- approved quantity -->
|
<td>{{ row[7] }}</td> <!-- time -->
|
||||||
<td>{{ row[9] }}</td> <!-- rejected quantity -->
|
<td>{{ row[8] }}</td> <!-- approved quantity -->
|
||||||
</tr>
|
<td>{{ row[9] }}</td> <!-- rejected quantity -->
|
||||||
{% endfor %}
|
</tr>
|
||||||
</tbody>
|
{% endfor %}
|
||||||
</table>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -513,22 +513,23 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<!-- Latest Scans Card -->
|
<!-- Latest Scans Card -->
|
||||||
<div class="card scan-table-card">
|
<div class="card scan-table-card">
|
||||||
<h3>Latest Scans</h3>
|
<h3>Latest Scans</h3>
|
||||||
<table class="scan-table">
|
<div class="report-table-container">
|
||||||
<thead>
|
<table class="scan-table">
|
||||||
<tr>
|
<thead>
|
||||||
<th>ID</th>
|
<tr>
|
||||||
<th>Op Code</th>
|
<th>ID</th>
|
||||||
<th>CP Code</th>
|
<th>Op Code</th>
|
||||||
<th>OC1 Code</th>
|
<th>CP Code</th>
|
||||||
<th>OC2 Code</th>
|
<th>OC1 Code</th>
|
||||||
<th>Defect Code</th>
|
<th>OC2 Code</th>
|
||||||
<th>Date</th>
|
<th>Defect Code</th>
|
||||||
<th>Time</th>
|
<th>Date</th>
|
||||||
<th>Apr. Quantity</th>
|
<th>Time</th>
|
||||||
<th>Rejec. Quantity</th>
|
<th>Apr. Quantity</th>
|
||||||
</tr>
|
<th>Rejec. Quantity</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
|
<tbody>
|
||||||
{% for row in scan_data %}
|
{% for row in scan_data %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ row[0] }}</td> <!-- Id -->
|
<td>{{ row[0] }}</td> <!-- Id -->
|
||||||
@@ -545,6 +546,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
185
quick-deploy.sh
Normal file
185
quick-deploy.sh
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# Quick Deployment Script for Quality Application
|
||||||
|
# Handles setup, build, and deployment in one command
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Logging functions
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}ℹ️ INFO:${NC} $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}✅ SUCCESS:${NC} $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}⚠️ WARNING:${NC} $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}❌ ERROR:${NC} $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Functions
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
check_dependencies() {
|
||||||
|
log_info "Checking dependencies..."
|
||||||
|
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
log_error "Docker is not installed. Please install Docker first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v docker-compose &> /dev/null; then
|
||||||
|
log_error "docker-compose is not installed. Please install docker-compose first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Dependencies check passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_environment() {
|
||||||
|
log_info "Setting up environment..."
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
bash setup-volumes.sh
|
||||||
|
|
||||||
|
# Check for .env file
|
||||||
|
if [ ! -f ".env" ]; then
|
||||||
|
log_warning ".env file not found, creating from example..."
|
||||||
|
cp .env.example .env
|
||||||
|
log_warning "Please edit .env file and set your passwords and configuration!"
|
||||||
|
log_warning "Press CTRL+C to cancel or ENTER to continue with default values (NOT RECOMMENDED FOR PRODUCTION)"
|
||||||
|
read -r
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Environment setup complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_images() {
|
||||||
|
log_info "Building Docker images..."
|
||||||
|
docker-compose build --no-cache
|
||||||
|
log_success "Docker images built successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_services() {
|
||||||
|
log_info "Starting services..."
|
||||||
|
docker-compose up -d
|
||||||
|
log_success "Services started"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
echo ""
|
||||||
|
echo "============================================================================"
|
||||||
|
echo "📊 Service Status"
|
||||||
|
echo "============================================================================"
|
||||||
|
docker-compose ps
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
show_logs() {
|
||||||
|
log_info "Showing recent logs (press CTRL+C to exit)..."
|
||||||
|
sleep 2
|
||||||
|
docker-compose logs -f --tail=50
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Main Deployment
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo "============================================================================"
|
||||||
|
echo "🚀 Quality Application - Quick Deployment"
|
||||||
|
echo "============================================================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
BUILD_ONLY=false
|
||||||
|
SKIP_LOGS=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--build-only)
|
||||||
|
BUILD_ONLY=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--skip-logs)
|
||||||
|
SKIP_LOGS=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help)
|
||||||
|
echo "Usage: $0 [OPTIONS]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --build-only Only build images, don't start services"
|
||||||
|
echo " --skip-logs Don't show logs after deployment"
|
||||||
|
echo " --help Show this help message"
|
||||||
|
echo ""
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown option: $1"
|
||||||
|
echo "Use --help for usage information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if we're in the right directory
|
||||||
|
if [ ! -f "docker-compose.yml" ]; then
|
||||||
|
log_error "docker-compose.yml not found. Please run this script from the application root directory."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Execute deployment steps
|
||||||
|
check_dependencies
|
||||||
|
setup_environment
|
||||||
|
build_images
|
||||||
|
|
||||||
|
if [ "$BUILD_ONLY" = true ]; then
|
||||||
|
log_success "Build complete! Use 'docker-compose up -d' to start services."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
start_services
|
||||||
|
show_status
|
||||||
|
|
||||||
|
echo "============================================================================"
|
||||||
|
echo "✅ Deployment Complete!"
|
||||||
|
echo "============================================================================"
|
||||||
|
echo ""
|
||||||
|
echo "Application URL: http://localhost:8781"
|
||||||
|
echo "Database Port: 3306 (accessible from host)"
|
||||||
|
echo ""
|
||||||
|
echo "Useful commands:"
|
||||||
|
echo " View logs: docker-compose logs -f web"
|
||||||
|
echo " Stop services: docker-compose down"
|
||||||
|
echo " Restart: docker-compose restart"
|
||||||
|
echo " Database shell: docker-compose exec db mysql -u trasabilitate -p"
|
||||||
|
echo ""
|
||||||
|
echo "Volume locations:"
|
||||||
|
echo " Database: ./data/mariadb/"
|
||||||
|
echo " Configuration: ./config/instance/"
|
||||||
|
echo " Logs: ./logs/"
|
||||||
|
echo " Backups: ./backups/"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$SKIP_LOGS" = false ]; then
|
||||||
|
show_logs
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
49
restore_database.sh
Executable file
49
restore_database.sh
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Safe Database Restore Script
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: $0 <backup_file.sql>"
|
||||||
|
echo "Example: $0 backups/backup_20251113.sql"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_FILE="$1"
|
||||||
|
|
||||||
|
if [ ! -f "$BACKUP_FILE" ]; then
|
||||||
|
echo "❌ Error: Backup file not found: $BACKUP_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "============================================"
|
||||||
|
echo "🔄 Database Restore Process"
|
||||||
|
echo "============================================"
|
||||||
|
echo "Backup file: $BACKUP_FILE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 1: Stop web application
|
||||||
|
echo "1️⃣ Stopping web application..."
|
||||||
|
docker compose stop web
|
||||||
|
echo "✅ Web application stopped"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 2: Restore database
|
||||||
|
echo "2️⃣ Restoring database..."
|
||||||
|
# Use sed to skip the problematic first line with sandbox mode comment
|
||||||
|
sed '1{/^\/\*M!999999/d;}' "$BACKUP_FILE" | docker compose exec -T db bash -c "mariadb -u trasabilitate -pInitial01! trasabilitate"
|
||||||
|
echo "✅ Database restored"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 3: Start web application
|
||||||
|
echo "3️⃣ Starting web application..."
|
||||||
|
docker compose start web
|
||||||
|
sleep 5
|
||||||
|
echo "✅ Web application started"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "============================================"
|
||||||
|
echo "✅ Database restore completed successfully!"
|
||||||
|
echo "============================================"
|
||||||
|
echo ""
|
||||||
|
echo "Application URL: http://localhost:8781"
|
||||||
107
setup-volumes.sh
Normal file
107
setup-volumes.sh
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# Volume Setup Script for Quality Application
|
||||||
|
# Creates all necessary directories for Docker volume mapping
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Logging functions
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}ℹ️ INFO:${NC} $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}✅ SUCCESS:${NC} $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}⚠️ WARNING:${NC} $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}❌ ERROR:${NC} $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Main Setup Function
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo "============================================================================"
|
||||||
|
echo "🚀 Quality Application - Volume Setup"
|
||||||
|
echo "============================================================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if we're in the right directory
|
||||||
|
if [ ! -f "docker-compose.yml" ]; then
|
||||||
|
log_error "docker-compose.yml not found. Please run this script from the application root directory."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Creating directory structure for Docker volumes..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
log_info "Creating data/mariadb directory (database files)..."
|
||||||
|
mkdir -p data/mariadb
|
||||||
|
|
||||||
|
log_info "Creating config/instance directory (app configuration)..."
|
||||||
|
mkdir -p config/instance
|
||||||
|
|
||||||
|
log_info "Creating logs directory (application logs)..."
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
log_info "Creating backups directory (database backups)..."
|
||||||
|
mkdir -p backups
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_success "All directories created successfully!"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Display directory structure
|
||||||
|
echo "============================================================================"
|
||||||
|
echo "📁 Volume Directory Structure:"
|
||||||
|
echo "============================================================================"
|
||||||
|
echo " ./data/mariadb/ - MariaDB database files (persistent data)"
|
||||||
|
echo " ./config/instance/ - Application configuration files"
|
||||||
|
echo " ./logs/ - Application and Gunicorn logs"
|
||||||
|
echo " ./backups/ - Database backup files"
|
||||||
|
echo "============================================================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for .env file
|
||||||
|
if [ ! -f ".env" ]; then
|
||||||
|
log_warning ".env file not found!"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Copy .env.example to .env: cp .env.example .env"
|
||||||
|
echo " 2. Edit .env with your settings: nano .env"
|
||||||
|
echo " 3. Change passwords and SECRET_KEY (CRITICAL for production!)"
|
||||||
|
echo " 4. Set INIT_DB=true for first deployment"
|
||||||
|
echo " 5. Build and start: docker-compose up -d --build"
|
||||||
|
else
|
||||||
|
log_success ".env file found!"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Review .env settings: nano .env"
|
||||||
|
echo " 2. Change passwords if needed (especially for production)"
|
||||||
|
echo " 3. Set INIT_DB=true for first deployment"
|
||||||
|
echo " 4. Build and start: docker-compose up -d --build"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "============================================================================"
|
||||||
|
log_success "Setup complete! You can now deploy the application."
|
||||||
|
echo "============================================================================"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user