feat: v1.1.0 - Production-Ready Docker Deployment
🚀 Major Release: DigiServer v1.1.0 Production Deployment ## 📁 Project Restructure - Moved all application code to app/ directory for Docker containerization - Centralized persistent data in data/ directory with volume mounting - Removed development artifacts and cleaned up project structure ## 🐳 Docker Integration - Added production-ready Dockerfile with LibreOffice and poppler-utils - Updated docker-compose.yml for production deployment - Added .dockerignore for optimized build context - Created automated deployment script (deploy-docker.sh) - Added cleanup script (cleanup-docker.sh) ## 📄 Document Processing Enhancements - Integrated LibreOffice for professional PPTX to PDF conversion - Implemented PPTX → PDF → 4K JPG workflow for optimal quality - Added poppler-utils for enhanced PDF processing - Simplified PDF conversion to 300 DPI for reliability ## 🔧 File Management Improvements - Fixed absolute path resolution for containerized deployment - Updated all file deletion functions with proper path handling - Enhanced bulk delete functions for players and groups - Improved file upload workflow with consistent path management ## 🛠️ Code Quality & Stability - Cleaned up pptx_converter.py from 442 to 86 lines - Removed all Python cache files (__pycache__/, *.pyc) - Updated file operations for production reliability - Enhanced error handling and logging ## 📚 Documentation Updates - Updated README.md with Docker deployment instructions - Added comprehensive DEPLOYMENT.md guide - Included production deployment best practices - Added automated deployment workflow documentation ## 🔐 Security & Production Features - Environment-based configuration - Health checks and container monitoring - Automated admin user creation - Volume-mounted persistent data - Production logging and error handling ## ✅ Ready for Production - Clean project structure optimized for Docker - Automated deployment with ./deploy-docker.sh - Professional document processing pipeline - Reliable file management system - Complete documentation and deployment guides Access: http://localhost:8880 | Admin: admin/Initial01!
This commit is contained in:
5
app/models/__init__.py
Normal file
5
app/models/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .user import User
|
||||
from .player import Player
|
||||
from .group import Group, group_player
|
||||
from .content import Content
|
||||
from .server_log import ServerLog
|
||||
21
app/models/clear_db.py
Normal file
21
app/models/clear_db.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import os
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
# Ensure the instance directory exists (relative to project root)
|
||||
instance_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'instance'))
|
||||
os.makedirs(instance_dir, exist_ok=True)
|
||||
|
||||
# Set the correct database URI
|
||||
db_path = os.path.join(instance_dir, 'dashboard.db')
|
||||
print(f"Using database at: {db_path}")
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
with app.app_context():
|
||||
db.reflect() # This loads all tables from the database
|
||||
db.drop_all()
|
||||
print("Dropped all tables successfully.")
|
||||
8
app/models/content.py
Normal file
8
app/models/content.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from extensions import db
|
||||
|
||||
class Content(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
file_name = db.Column(db.String(255), nullable=False)
|
||||
duration = db.Column(db.Integer, nullable=False)
|
||||
player_id = db.Column(db.Integer, db.ForeignKey('player.id'), nullable=False)
|
||||
position = db.Column(db.Integer, default=0)
|
||||
18
app/models/create_default_user.py
Normal file
18
app/models/create_default_user.py
Normal file
@@ -0,0 +1,18 @@
|
||||
#from app import app, db, User, bcrypt
|
||||
import os
|
||||
|
||||
def create_default_user(db, User, bcrypt):
|
||||
username = os.getenv('DEFAULT_USER', 'admin')
|
||||
password = os.getenv('DEFAULT_PASSWORD', '1234')
|
||||
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
|
||||
existing_user = User.query.filter_by(username=username).first()
|
||||
if not existing_user:
|
||||
default_user = User(username=username, password=hashed_password, role='admin')
|
||||
db.session.add(default_user)
|
||||
db.session.commit()
|
||||
print(f"Default user '{username}' created with password '{password}'")
|
||||
else:
|
||||
print(f"Default user '{username}' already exists.")
|
||||
|
||||
#with app.app_context():
|
||||
# create_default_user(db, User, bcrypt)
|
||||
13
app/models/group.py
Normal file
13
app/models/group.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from extensions import db
|
||||
|
||||
class Group(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False, unique=True)
|
||||
orientation = db.Column(db.String(16), nullable=False, default='Landscape') # <-- Add this line
|
||||
players = db.relationship('Player', secondary='group_player', backref='groups')
|
||||
playlist_version = db.Column(db.Integer, default=0)
|
||||
|
||||
group_player = db.Table('group_player',
|
||||
db.Column('group_id', db.Integer, db.ForeignKey('group.id'), primary_key=True),
|
||||
db.Column('player_id', db.Integer, db.ForeignKey('player.id'), primary_key=True)
|
||||
)
|
||||
24
app/models/init_db.py
Normal file
24
app/models/init_db.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
from app import app
|
||||
from extensions import db, bcrypt
|
||||
from models import User, ServerLog # Import from models.py instead of app.py
|
||||
|
||||
def create_admin_user():
|
||||
admin_username = os.getenv('ADMIN_USER', 'admin')
|
||||
admin_password = os.getenv('ADMIN_PASSWORD', 'admin')
|
||||
hashed_password = bcrypt.generate_password_hash(admin_password).decode('utf-8')
|
||||
|
||||
if not User.query.filter_by(username=admin_username).first():
|
||||
admin_user = User(username=admin_username, password=hashed_password, role='admin')
|
||||
db.session.add(admin_user)
|
||||
db.session.commit()
|
||||
print(f"Admin user '{admin_username}' created with password '{admin_password}'")
|
||||
else:
|
||||
print(f"Admin user '{admin_username}' already exists")
|
||||
|
||||
if __name__ == '__main__':
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
create_admin_user()
|
||||
print("Database initialized with all models including ServerLog")
|
||||
|
||||
18
app/models/player.py
Normal file
18
app/models/player.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from extensions import db
|
||||
from flask_bcrypt import Bcrypt
|
||||
|
||||
bcrypt = Bcrypt()
|
||||
|
||||
class Player(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(255), nullable=False)
|
||||
hostname = db.Column(db.String(255), nullable=False)
|
||||
password = db.Column(db.String(255), nullable=False)
|
||||
quickconnect_password = db.Column(db.String(255), nullable=True)
|
||||
playlist_version = db.Column(db.Integer, default=1)
|
||||
locked_to_group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=True)
|
||||
locked_to_group = db.relationship('Group', foreign_keys=[locked_to_group_id], backref='locked_players')
|
||||
orientation = db.Column(db.String(16), nullable=False, default='Landscape') # <-- Add this line
|
||||
|
||||
def verify_quickconnect_code(self, code):
|
||||
return bcrypt.check_password_hash(self.quickconnect_password, code)
|
||||
10
app/models/server_log.py
Normal file
10
app/models/server_log.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from extensions import db
|
||||
import datetime
|
||||
|
||||
class ServerLog(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
action = db.Column(db.String(255), nullable=False)
|
||||
timestamp = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ServerLog {self.action}>"
|
||||
33
app/models/user.py
Normal file
33
app/models/user.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from extensions import db
|
||||
from flask_bcrypt import Bcrypt
|
||||
from flask_login import UserMixin
|
||||
|
||||
bcrypt = Bcrypt()
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
password = db.Column(db.String(120), nullable=False)
|
||||
role = db.Column(db.String(80), nullable=False)
|
||||
theme = db.Column(db.String(80), default='light')
|
||||
|
||||
def set_password(self, password):
|
||||
self.password = bcrypt.generate_password_hash(password).decode('utf-8')
|
||||
|
||||
def check_password(self, password):
|
||||
return bcrypt.check_password_hash(self.password, password)
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_anonymous(self):
|
||||
return False
|
||||
|
||||
def get_id(self):
|
||||
return str(self.id)
|
||||
Reference in New Issue
Block a user