Compare commits

...

10 Commits

Author SHA1 Message Date
3cc703a7d1 add saved files 2025-09-04 13:14:10 -04:00
DigiServer Developer
505c8e268c Final working version: playlist API, persistent data, and deployment fixes 2025-08-25 16:40:17 +03:00
DigiServer Developer
6cefce81ef Update: media file handling and playlist filename consistency using secure_filename 2025-08-25 13:13:12 +03:00
359e330758 media 2025-08-25 00:35:53 -04:00
9c124dbd7e fix: remove video conversion, use uploaded videos as-is for playlist (bugfix) 2025-08-25 00:33:40 -04:00
DigiServer Developer
7b24245ddb updated the upload functionality to handle large files and added a new image file 2025-08-21 16:27:16 +03:00
DigiServer Developer
58694ff3f4 Update all changes before rebase and push 2025-08-21 16:26:53 +03:00
7f5991f60d fix: use correct endpoint for group media delete in manage_group.html 2025-08-20 15:11:22 -04:00
DigiServer Developer
5e4950563c Fix admin authentication and update port mapping
- Fix environment variable mismatch in create_default_user.py
- Now correctly uses ADMIN_USER and ADMIN_PASSWORD from docker-compose
- Maintains backward compatibility with DEFAULT_USER and DEFAULT_PASSWORD
- Change port mapping from 8880 to 80 for easier access
- Resolves login issues with admin user credentials
2025-08-11 17:01:58 +03:00
091e985ff2 fix: Simplified Docker deployment and fixed upload path resolution
🐳 Docker Configuration Improvements:
- Simplified docker-compose.yml to use single app folder bind mount
- Removed complex data folder mapping that caused path confusion
- Updated environment variables to match entrypoint script expectations
- Streamlined deployment for better reliability

🔧 Upload System Fixes:
- Fixed path resolution issues in uploads.py for containerized deployment
- Simplified upload folder path handling to work correctly in containers
- Removed complex absolute path conversion logic that caused file placement issues
- Ensured all file operations use consistent /app/static/uploads path

📁 File Processing Improvements:
- Fixed PPTX to JPG conversion workflow path handling
- Corrected PDF processing to save files in correct container location
- Improved video conversion path resolution
- Enhanced error handling and logging for upload operations

🚀 Production Benefits:
- Eliminates 404 errors for uploaded media files
- Ensures files are saved in correct locations within container
- Simplifies development and debugging with direct app folder mounting
- Maintains data consistency across container restarts

 This resolves the upload workflow issues where PPTX files were not
being correctly processed and saved to the expected locations.
2025-08-05 19:16:08 -04:00
9 changed files with 122 additions and 137 deletions

View File

@@ -69,8 +69,12 @@ db_path = os.path.join(instance_dir, 'dashboard.db')
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}' app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Set maximum content length to 1GB # Set maximum content length to 2GB
app.config['MAX_CONTENT_LENGTH'] = 2048 * 2048 * 2048 # 2GB, adjust as needed app.config['MAX_CONTENT_LENGTH'] = 2048 * 1024 * 1024 # 2GB, adjust as needed
# Set longer timeouts for file processing
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 300 # 5 minutes for static files
app.config['PERMANENT_SESSION_LIFETIME'] = 1800 # 30 minutes for sessions
# Ensure the instance folder exists # Ensure the instance folder exists
os.makedirs(app.instance_path, exist_ok=True) os.makedirs(app.instance_path, exist_ok=True)
@@ -95,6 +99,22 @@ login_manager.login_view = 'login'
migrate = Migrate(app, db) migrate = Migrate(app, db)
# Add error handlers for better user experience
@app.errorhandler(413)
def request_entity_too_large(error):
flash('File too large. Please upload files smaller than 2GB.', 'danger')
return redirect(url_for('dashboard'))
@app.errorhandler(408)
def request_timeout(error):
flash('Request timed out. Please try uploading smaller files or try again later.', 'danger')
return redirect(url_for('dashboard'))
@app.errorhandler(500)
def internal_server_error(error):
flash('An internal server error occurred. Please try again or contact support.', 'danger')
return redirect(url_for('dashboard'))
@login_manager.user_loader @login_manager.user_loader
def load_user(user_id): def load_user(user_id):
return db.session.get(User, int(user_id)) return db.session.get(User, int(user_id))
@@ -213,8 +233,23 @@ def upload_content():
flash('Please select a target type and target ID.', 'danger') flash('Please select a target type and target ID.', 'danger')
return redirect(url_for('upload_content')) return redirect(url_for('upload_content'))
# Process uploaded files and get results try:
results = process_uploaded_files(app, files, media_type, duration, target_type, target_id) # Process uploaded files and get results
results = process_uploaded_files(app, files, media_type, duration, target_type, target_id)
# Check for any failed uploads
failed_files = [r for r in results if not r.get('success', True)]
if failed_files:
for failed in failed_files:
flash(f"Error uploading {failed.get('filename', 'unknown file')}: {failed.get('message', 'Unknown error')}", 'warning')
else:
flash('All files uploaded and processed successfully!', 'success')
except Exception as e:
print(f"Error in upload_content: {e}")
import traceback
traceback.print_exc()
flash(f'Upload failed: {str(e)}', 'danger')
return redirect(return_url) return redirect(return_url)

View File

@@ -2,8 +2,9 @@
import os import os
def create_default_user(db, User, bcrypt): def create_default_user(db, User, bcrypt):
username = os.getenv('DEFAULT_USER', 'admin') # Use ADMIN_USER and ADMIN_PASSWORD to match docker-compose environment variables
password = os.getenv('DEFAULT_PASSWORD', '1234') username = os.getenv('ADMIN_USER', os.getenv('DEFAULT_USER', 'admin'))
password = os.getenv('ADMIN_PASSWORD', os.getenv('DEFAULT_PASSWORD', '1234'))
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8') hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
existing_user = User.query.filter_by(username=username).first() existing_user = User.query.filter_by(username=username).first()
if not existing_user: if not existing_user:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -110,35 +110,44 @@
<ul class="list-group sortable-list" id="groupMediaList"> <ul class="list-group sortable-list" id="groupMediaList">
{% for media in content %} {% for media in content %}
<li class="list-group-item d-flex align-items-center {{ 'dark-mode' if theme == 'dark' else '' }}" <li class="list-group-item d-flex align-items-center {{ 'dark-mode' if theme == 'dark' else '' }}"
draggable="true" draggable="true"
data-id="{{ media.id }}" data-id="{{ media.id }}"
data-position="{{ loop.index0 }}"> data-position="{{ loop.index0 }}">
<!-- Checkbox for bulk selection --> <!-- Checkbox for bulk selection -->
<div class="me-2"> <div class="me-2">
<input class="form-check-input media-checkbox" <input class="form-check-input media-checkbox"
type="checkbox" type="checkbox"
name="selected_content" name="selected_content"
value="{{ media.id }}"> value="{{ media.id }}">
</div> </div>
<!-- Drag handle --> <!-- Drag handle -->
<div class="drag-handle me-2" title="Drag to reorder"> <div class="drag-handle me-2" title="Drag to reorder">
<i class="bi bi-grip-vertical"></i> <i class="bi bi-grip-vertical"></i>
&#9776; &#9776;
</div> </div>
<div class="flex-grow-1"> <!-- Media Thumbnail and Name -->
<div class="flex-grow-1 mb-2 mb-md-0 d-flex align-items-center">
<img src="{{ url_for('static', filename='uploads/' ~ media.file_name) }}"
alt="thumbnail"
style="width: 48px; height: 48px; object-fit: cover; margin-right: 10px; border-radius: 4px;"
onerror="this.style.display='none';">
<p class="mb-0"><strong>Media Name:</strong> {{ media.file_name }}</p> <p class="mb-0"><strong>Media Name:</strong> {{ media.file_name }}</p>
</div> </div>
<form action="{{ url_for('edit_group_media', group_id=group.id, content_id=media.id) }}" method="post" class="d-flex align-items-center"> <<<<<<< HEAD
=======
>>>>>>> 2255cc2 (Show media thumbnails in manage group page, matching player page style)
<form action="{{ url_for('edit_group_media_route', group_id=group.id, content_id=media.id) }}" method="post" class="d-flex align-items-center">
<div class="input-group me-2"> <div class="input-group me-2">
<span class="input-group-text">seconds</span> <span class="input-group-text">seconds</span>
<input type="number" class="form-control {{ 'dark-mode' if theme == 'dark' else '' }}" name="duration" value="{{ media.duration }}" required> <input type="number" class="form-control {{ 'dark-mode' if theme == 'dark' else '' }}" name="duration" value="{{ media.duration }}" required>
</div> </div>
<button type="submit" class="btn btn-warning me-2">Edit</button> <button type="submit" class="btn btn-warning me-2">Edit</button>
</form> </form>
<form action="{{ url_for('delete_group_media', group_id=group.id, content_id=media.id) }}" method="post" style="display:inline;"> <form action="{{ url_for('delete_group_media_route', group_id=group.id, content_id=media.id) }}" method="post" style="display:inline;">
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this media?');">Delete</button> <button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this media?');">Delete</button>
</form> </form>
</li> </li>

View File

@@ -243,7 +243,7 @@
statusMessage.textContent = 'Converting PDF to 4K images. This may take a while...'; statusMessage.textContent = 'Converting PDF to 4K images. This may take a while...';
break; break;
case 'ppt': case 'ppt':
statusMessage.textContent = 'Converting PowerPoint to 4K images. This may take a while...'; statusMessage.textContent = 'Converting PowerPoint to images (PPTX → PDF → Images). This may take 2-5 minutes...';
break; break;
default: default:
statusMessage.textContent = 'Uploading and processing your files. Please wait...'; statusMessage.textContent = 'Uploading and processing your files. Please wait...';

View File

@@ -9,12 +9,23 @@ The converted PDF is then processed by the main upload workflow for 4K image gen
import os import os
import subprocess import subprocess
import logging import logging
import signal
import time
# Set up logging # Set up logging
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def cleanup_libreoffice_processes():
"""Clean up any hanging LibreOffice processes"""
try:
subprocess.run(['pkill', '-f', 'soffice'], capture_output=True, timeout=10)
time.sleep(1) # Give processes time to terminate
except Exception as e:
logger.warning(f"Failed to cleanup LibreOffice processes: {e}")
def pptx_to_pdf_libreoffice(pptx_path, output_dir): def pptx_to_pdf_libreoffice(pptx_path, output_dir):
""" """
Convert PPTX to PDF using LibreOffice for highest quality. Convert PPTX to PDF using LibreOffice for highest quality.
@@ -30,6 +41,9 @@ def pptx_to_pdf_libreoffice(pptx_path, output_dir):
str: Path to the generated PDF file, or None if conversion failed str: Path to the generated PDF file, or None if conversion failed
""" """
try: try:
# Clean up any existing LibreOffice processes
cleanup_libreoffice_processes()
# Ensure output directory exists # Ensure output directory exists
os.makedirs(output_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True)
@@ -39,14 +53,19 @@ def pptx_to_pdf_libreoffice(pptx_path, output_dir):
'--headless', '--headless',
'--convert-to', 'pdf', '--convert-to', 'pdf',
'--outdir', output_dir, '--outdir', output_dir,
'--invisible', # Run without any UI
'--nodefault', # Don't start with default template
pptx_path pptx_path
] ]
logger.info(f"Converting PPTX to PDF using LibreOffice: {pptx_path}") logger.info(f"Converting PPTX to PDF using LibreOffice: {pptx_path}")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) # Increase timeout to 300 seconds (5 minutes) for large presentations
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if result.returncode != 0: if result.returncode != 0:
logger.error(f"LibreOffice conversion failed: {result.stderr}") logger.error(f"LibreOffice conversion failed: {result.stderr}")
logger.error(f"LibreOffice stdout: {result.stdout}")
cleanup_libreoffice_processes() # Clean up on failure
return None return None
# Find the generated PDF file # Find the generated PDF file
@@ -55,16 +74,22 @@ def pptx_to_pdf_libreoffice(pptx_path, output_dir):
if os.path.exists(pdf_path): if os.path.exists(pdf_path):
logger.info(f"PDF conversion successful: {pdf_path}") logger.info(f"PDF conversion successful: {pdf_path}")
cleanup_libreoffice_processes() # Clean up after success
return pdf_path return pdf_path
else: else:
logger.error(f"PDF file not found after conversion: {pdf_path}") logger.error(f"PDF file not found after conversion: {pdf_path}")
cleanup_libreoffice_processes() # Clean up on failure
return None return None
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
logger.error("LibreOffice conversion timed out (120s)") logger.error("LibreOffice conversion timed out (300s)")
cleanup_libreoffice_processes() # Clean up on timeout
return None return None
except Exception as e: except Exception as e:
logger.error(f"Error in PPTX to PDF conversion: {e}") logger.error(f"Error in PPTX to PDF conversion: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
cleanup_libreoffice_processes() # Clean up on error
return None return None

View File

@@ -12,10 +12,10 @@ def add_image_to_playlist(app, file, filename, duration, target_type, target_id)
""" """
Save the image file and add it to the playlist database. Save the image file and add it to the playlist database.
""" """
# Ensure we use absolute path for upload folder # Use simple path resolution for containerized environment
upload_folder = app.config['UPLOAD_FOLDER'] upload_folder = app.config['UPLOAD_FOLDER']
if not os.path.isabs(upload_folder): # In container, working directory is /app, so static/uploads resolves correctly
upload_folder = os.path.abspath(upload_folder) print(f"Upload folder config: {upload_folder}")
# Ensure upload folder exists # Ensure upload folder exists
if not os.path.exists(upload_folder): if not os.path.exists(upload_folder):
@@ -52,86 +52,12 @@ def add_image_to_playlist(app, file, filename, duration, target_type, target_id)
# Video conversion functions # Video conversion functions
def convert_video(input_file, output_folder): def convert_video(input_file, output_folder):
""" print(f"Video conversion skipped for: {input_file}")
Converts a video file to MP4 format with H.264 codec. return input_file
"""
# Ensure we use absolute path for output folder
if not os.path.isabs(output_folder):
output_folder = os.path.abspath(output_folder)
print(f"Converted output folder to absolute path: {output_folder}")
if not os.path.exists(output_folder):
os.makedirs(output_folder, exist_ok=True)
print(f"Created output folder: {output_folder}")
# Generate the output file path
base_name = os.path.splitext(os.path.basename(input_file))[0]
output_file = os.path.join(output_folder, f"{base_name}.mp4")
print(f"Converting video: {input_file} -> {output_file}")
# FFmpeg command to convert the video
command = [
"ffmpeg",
"-i", input_file, # Input file
"-c:v", "libx264", # Video codec: H.264
"-preset", "fast", # Encoding speed/quality tradeoff
"-crf", "23", # Constant Rate Factor (quality, lower is better)
"-vf", "scale=-1:1080", # Scale video to 1080p (preserve aspect ratio)
"-r", "30", # Frame rate: 30 FPS
"-c:a", "aac", # Audio codec: AAC
"-b:a", "128k", # Audio bitrate
output_file # Output file
]
try:
# Run the FFmpeg command
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(f"Video converted successfully: {output_file}")
return output_file
except subprocess.CalledProcessError as e:
print(f"Error converting video: {e.stderr.decode()}")
return None
def convert_video_and_update_playlist(app, file_path, original_filename, target_type, target_id, duration): def convert_video_and_update_playlist(app, file_path, original_filename, target_type, target_id, duration):
""" print(f"Video conversion skipped for: {file_path}")
Converts a video and updates the playlist database. return None
"""
print(f"Starting video conversion for: {file_path}")
# Ensure we use absolute path for upload folder
upload_folder = app.config['UPLOAD_FOLDER']
if not os.path.isabs(upload_folder):
upload_folder = os.path.abspath(upload_folder)
print(f"Converted upload folder to absolute path: {upload_folder}")
converted_file = convert_video(file_path, upload_folder)
if converted_file:
converted_filename = os.path.basename(converted_file)
print(f"Video converted successfully: {converted_filename}")
# Use the application context to interact with the database
with app.app_context():
# Update the database with the converted filename
if target_type == 'group':
group = Group.query.get_or_404(target_id)
for player in group.players:
content = Content.query.filter_by(player_id=player.id, file_name=original_filename).first()
if content:
content.file_name = converted_filename
elif target_type == 'player':
content = Content.query.filter_by(player_id=target_id, file_name=original_filename).first()
if content:
content.file_name = converted_filename
db.session.commit()
print(f"Database updated with converted video: {converted_filename}")
# Delete the original file only if it exists
if os.path.exists(file_path):
os.remove(file_path)
print(f"Original file deleted: {file_path}")
else:
print(f"Video conversion failed for: {file_path}")
# PDF conversion functions # PDF conversion functions
def convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True, dpi=300): def convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True, dpi=300):
@@ -140,25 +66,7 @@ def convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True, dpi=300):
Uses standard 300 DPI for reliable conversion. Uses standard 300 DPI for reliable conversion.
""" """
print(f"Converting PDF to JPG images: {pdf_file} at {dpi} DPI") print(f"Converting PDF to JPG images: {pdf_file} at {dpi} DPI")
print(f"Original output folder: {output_folder}") print(f"Output folder: {output_folder}")
# Force absolute path resolution to ensure we use the app directory
if not os.path.isabs(output_folder):
# If relative path, resolve from the current working directory
output_folder = os.path.abspath(output_folder)
print(f"Converted relative path to absolute: {output_folder}")
else:
print(f"Using provided absolute path: {output_folder}")
# Ensure we're using the app static folder, not workspace root
if output_folder.endswith('static/uploads'):
# Check if we're accidentally using workspace root instead of app folder
expected_app_path = '/opt/digiserver/app/static/uploads'
if output_folder != expected_app_path:
print(f"WARNING: Correcting path from {output_folder} to {expected_app_path}")
output_folder = expected_app_path
print(f"Final output folder: {output_folder}")
try: try:
# Ensure output folder exists # Ensure output folder exists
@@ -266,11 +174,6 @@ def process_pdf(input_file, output_folder, duration, target_type, target_id):
print(f"Processing PDF file: {input_file}") print(f"Processing PDF file: {input_file}")
print(f"Output folder: {output_folder}") print(f"Output folder: {output_folder}")
# Ensure we have absolute path for output folder
if not os.path.isabs(output_folder):
output_folder = os.path.abspath(output_folder)
print(f"Converted output folder to absolute path: {output_folder}")
# Ensure output folder exists # Ensure output folder exists
if not os.path.exists(output_folder): if not os.path.exists(output_folder):
os.makedirs(output_folder, exist_ok=True) os.makedirs(output_folder, exist_ok=True)
@@ -306,11 +209,6 @@ def process_pptx(input_file, output_folder, duration, target_type, target_id):
print(f"Processing PPTX file using PDF workflow: {input_file}") print(f"Processing PPTX file using PDF workflow: {input_file}")
print(f"Output folder: {output_folder}") print(f"Output folder: {output_folder}")
# Ensure we have absolute path for output folder
if not os.path.isabs(output_folder):
output_folder = os.path.abspath(output_folder)
print(f"Converted output folder to absolute path: {output_folder}")
# Ensure output folder exists # Ensure output folder exists
if not os.path.exists(output_folder): if not os.path.exists(output_folder):
os.makedirs(output_folder, exist_ok=True) os.makedirs(output_folder, exist_ok=True)
@@ -318,21 +216,33 @@ def process_pptx(input_file, output_folder, duration, target_type, target_id):
try: try:
# Step 1: Convert PPTX to PDF using LibreOffice for vector quality # Step 1: Convert PPTX to PDF using LibreOffice for vector quality
print("Step 1: Converting PPTX to PDF...")
from utils.pptx_converter import pptx_to_pdf_libreoffice from utils.pptx_converter import pptx_to_pdf_libreoffice
pdf_file = pptx_to_pdf_libreoffice(input_file, output_folder) pdf_file = pptx_to_pdf_libreoffice(input_file, output_folder)
if not pdf_file: if not pdf_file:
print("Error: Failed to convert PPTX to PDF") print("Error: Failed to convert PPTX to PDF")
print("This could be due to:")
print("- LibreOffice not properly installed")
print("- Corrupted PPTX file")
print("- Insufficient memory")
print("- File permission issues")
return False return False
print(f"PPTX successfully converted to PDF: {pdf_file}") print(f"PPTX successfully converted to PDF: {pdf_file}")
# Step 2: Use the same PDF to images workflow as direct PDF uploads # Step 2: Use the same PDF to images workflow as direct PDF uploads
print("Step 2: Converting PDF to JPG images...")
# Convert PDF to JPG images (300 DPI, same as PDF workflow) # Convert PDF to JPG images (300 DPI, same as PDF workflow)
image_filenames = convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True, dpi=300) image_filenames = convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True, dpi=300)
if not image_filenames: if not image_filenames:
print("Error: Failed to convert PDF to images") print("Error: Failed to convert PDF to images")
print("This could be due to:")
print("- poppler-utils not properly installed")
print("- PDF corruption during conversion")
print("- Insufficient disk space")
print("- Memory issues during image processing")
return False return False
print(f"Generated {len(image_filenames)} JPG images from PPTX → PDF") print(f"Generated {len(image_filenames)} JPG images from PPTX → PDF")
@@ -341,11 +251,14 @@ def process_pptx(input_file, output_folder, duration, target_type, target_id):
if os.path.exists(input_file): if os.path.exists(input_file):
os.remove(input_file) os.remove(input_file)
print(f"Original PPTX file deleted: {input_file}") print(f"Original PPTX file deleted: {input_file}")
# Step 4: Update playlist with generated images in sequential order # Step 4: Update playlist with generated images in sequential order
print("Step 3: Adding images to playlist...")
success = update_playlist_with_files(image_filenames, duration, target_type, target_id) success = update_playlist_with_files(image_filenames, duration, target_type, target_id)
if success: if success:
print(f"Successfully processed PPTX: {len(image_filenames)} images added to playlist") print(f"Successfully processed PPTX: {len(image_filenames)} images added to playlist")
else:
print("Error: Failed to add images to playlist database")
return success return success
except Exception as e: except Exception as e:
@@ -377,10 +290,9 @@ def process_uploaded_files(app, files, media_type, duration, target_type, target
# Generate a secure filename and save the file # Generate a secure filename and save the file
filename = secure_filename(file.filename) filename = secure_filename(file.filename)
# Ensure we use absolute path for upload folder # Use simple path resolution for containerized environment
upload_folder = app.config['UPLOAD_FOLDER'] upload_folder = app.config['UPLOAD_FOLDER']
if not os.path.isabs(upload_folder): print(f"Upload folder: {upload_folder}")
upload_folder = os.path.abspath(upload_folder)
# Ensure upload folder exists # Ensure upload folder exists
if not os.path.exists(upload_folder): if not os.path.exists(upload_folder):

13
docker-compose.yml Executable file → Normal file
View File

@@ -1,6 +1,5 @@
# DigiServer - Digital Signage Management Platform # Production Docker Compose Configuration
# Version: 1.1.0 # Use this for production deployment
# Build Date: 2025-06-29
services: services:
digiserver: digiserver:
@@ -12,10 +11,14 @@ services:
environment: environment:
- FLASK_APP=app.py - FLASK_APP=app.py
- FLASK_RUN_HOST=0.0.0.0 - FLASK_RUN_HOST=0.0.0.0
- DEFAULT_USER=admin - FLASK_ENV=production
- DEFAULT_PASSWORD=Initial01! - FLASK_DEBUG=0
- ADMIN_USER=admin
- ADMIN_PASSWORD=Initial01!
- SECRET_KEY=Ma_Duc_Dupa_Merele_Lui_Ana - SECRET_KEY=Ma_Duc_Dupa_Merele_Lui_Ana
volumes: volumes:
# Mount app code
- ./app:/app
# Persistent data volumes # Persistent data volumes
- ./data/instance:/app/instance - ./data/instance:/app/instance
- ./data/uploads:/app/static/uploads - ./data/uploads:/app/static/uploads