Browse Source

Add installer and portability fixes

main
def 2 weeks ago
parent
commit
c17570cfd7
  1. 17
      README.md
  2. 10
      backend/app.py
  3. 31
      backend/db.py
  4. 143
      install.sh

17
README.md

@ -73,3 +73,20 @@ Notes
- Core billing/export/report workflow is now operational.
- Email log table records invoice, revenue report, and accounting package sends.
## Installer
From the project root:
```bash
chmod +x install.sh
./install.sh
## Installer
From the project root:
chmod +x install.sh
./install.sh
The installer will install dependencies, configure MariaDB, create the database, and start the service.

10
backend/app.py

@ -4,6 +4,7 @@ from utils import generate_client_code, generate_service_code
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
from decimal import Decimal, InvalidOperation
from pathlib import Path
from email.message import EmailMessage
from io import BytesIO, StringIO
@ -22,9 +23,12 @@ app = Flask(
LOCAL_TZ = ZoneInfo("America/Toronto")
BASE_DIR = Path(__file__).resolve().parent.parent
def load_version():
try:
with open("/home/def/otb_billing/VERSION", "r") as f:
with open(BASE_DIR / "VERSION", "r") as f:
return f.read().strip()
except Exception:
return "unknown"
@ -1389,7 +1393,7 @@ def export_invoices_pdf_zip():
logo_url = (settings.get("business_logo_url") or "").strip()
if logo_url.startswith("/static/"):
local_logo_path = "/home/def/otb_billing" + logo_url
local_logo_path = str(BASE_DIR) + logo_url
try:
pdf.drawImage(ImageReader(local_logo_path), left, y - 35, width=42, height=42, preserveAspectRatio=True, mask='auto')
except Exception:
@ -1879,7 +1883,7 @@ def invoice_pdf(invoice_id):
logo_url = (settings.get("business_logo_url") or "").strip()
if logo_url.startswith("/static/"):
local_logo_path = "/home/def/otb_billing" + logo_url
local_logo_path = str(BASE_DIR) + logo_url
try:
pdf.drawImage(ImageReader(local_logo_path), left, y - 35, width=42, height=42, preserveAspectRatio=True, mask='auto')
except Exception:

31
backend/db.py

@ -1,11 +1,26 @@
import os
import mysql.connector
from config import Config
def get_db_connection():
return mysql.connector.connect(
host=Config.DB_HOST,
port=Config.DB_PORT,
user=Config.DB_USER,
password=Config.DB_PASSWORD,
database=Config.DB_NAME
)
host = os.getenv("OTB_BILLING_DB_HOST", "127.0.0.1")
port = int(os.getenv("OTB_BILLING_DB_PORT", "3306"))
database = os.getenv("OTB_BILLING_DB_NAME", "otb_billing")
user = os.getenv("OTB_BILLING_DB_USER", "otb_billing")
password = os.getenv("OTB_BILLING_DB_PASSWORD", "")
unix_socket = os.getenv("OTB_BILLING_DB_SOCKET", "").strip()
kwargs = {
"database": database,
"user": user,
"password": password,
"autocommit": False,
}
if unix_socket:
kwargs["unix_socket"] = unix_socket
else:
kwargs["host"] = host
kwargs["port"] = port
return mysql.connector.connect(**kwargs)

143
install.sh

@ -0,0 +1,143 @@
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="otb_billing"
APP_USER_DEFAULT="${SUDO_USER:-$USER}"
APP_ROOT_DEFAULT="/opt/otb_billing"
ENV_DIR="/etc/otb_billing"
ENV_FILE="${ENV_DIR}/otb_billing.env"
SERVICE_NAME="otb-billing"
SCHEMA_FILE="sql/schema_v0.0.2.sql"
if [[ ! -f "$SCHEMA_FILE" ]]; then
echo "ERROR: $SCHEMA_FILE not found. Run this from the repo root."
exit 1
fi
echo "=================================================="
echo "OTB Billing installer"
echo "=================================================="
read -rp "Install app user [${APP_USER_DEFAULT}]: " APP_USER
APP_USER="${APP_USER:-$APP_USER_DEFAULT}"
read -rp "Install path [${APP_ROOT_DEFAULT}]: " APP_ROOT
APP_ROOT="${APP_ROOT:-$APP_ROOT_DEFAULT}"
read -rp "MariaDB database name [otb_billing]: " DB_NAME
DB_NAME="${DB_NAME:-otb_billing}"
read -rp "MariaDB database user [otb_billing]: " DB_USER
DB_USER="${DB_USER:-otb_billing}"
read -rsp "MariaDB database password: " DB_PASS
echo
if [[ -z "$DB_PASS" ]]; then
echo "ERROR: database password cannot be blank."
exit 1
fi
read -rp "App listen host [0.0.0.0]: " APP_HOST
APP_HOST="${APP_HOST:-0.0.0.0}"
read -rp "App listen port [5050]: " APP_PORT
APP_PORT="${APP_PORT:-5050}"
echo
echo "Installing system packages..."
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
python3 python3-venv python3-pip mariadb-server rsync
echo
echo "Creating app directory..."
sudo mkdir -p "$APP_ROOT"
sudo rsync -a --delete \
--exclude '.git' \
--exclude '__pycache__' \
--exclude 'releases' \
--exclude '*.pyc' \
./ "$APP_ROOT"/
sudo chown -R "$APP_USER":"$APP_USER" "$APP_ROOT"
echo
echo "Creating Python virtualenv..."
sudo -u "$APP_USER" python3 -m venv "$APP_ROOT/venv"
sudo -u "$APP_USER" "$APP_ROOT/venv/bin/pip" install --upgrade pip
sudo -u "$APP_USER" "$APP_ROOT/venv/bin/pip" install -r "$APP_ROOT/requirements.txt"
echo
echo "Ensuring MariaDB is running..."
sudo systemctl enable mariadb
sudo systemctl restart mariadb
echo
echo "Creating MariaDB database and user..."
sudo mysql <<SQL
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';
ALTER USER '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';
GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_USER}'@'localhost';
FLUSH PRIVILEGES;
SQL
echo
echo "Importing schema..."
sudo mysql "$DB_NAME" < "$APP_ROOT/$SCHEMA_FILE"
echo
echo "Writing environment file..."
sudo mkdir -p "$ENV_DIR"
sudo tee "$ENV_FILE" >/dev/null <<ENVEOF
OTB_BILLING_DB_HOST=127.0.0.1
OTB_BILLING_DB_PORT=3306
OTB_BILLING_DB_NAME=${DB_NAME}
OTB_BILLING_DB_USER=${DB_USER}
OTB_BILLING_DB_PASSWORD=${DB_PASS}
FLASK_ENV=production
HOST=${APP_HOST}
PORT=${APP_PORT}
ENVEOF
sudo chmod 600 "$ENV_FILE"
echo
echo "Creating systemd service..."
sudo tee "/etc/systemd/system/${SERVICE_NAME}.service" >/dev/null <<SERVICEEOF
[Unit]
Description=OTB Billing Flask App
After=network.target mariadb.service
Requires=mariadb.service
[Service]
Type=simple
User=${APP_USER}
WorkingDirectory=${APP_ROOT}
EnvironmentFile=${ENV_FILE}
ExecStart=${APP_ROOT}/venv/bin/python ${APP_ROOT}/backend/app.py
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
SERVICEEOF
echo
echo "Enabling service..."
sudo systemctl daemon-reload
sudo systemctl enable "${SERVICE_NAME}"
sudo systemctl restart "${SERVICE_NAME}"
echo
echo "Verifying service..."
sudo systemctl --no-pager --full status "${SERVICE_NAME}" || true
echo
echo "Installer complete."
echo "App path: ${APP_ROOT}"
echo "Service: ${SERVICE_NAME}"
echo "URL: http://${APP_HOST}:${APP_PORT}"
echo
echo "Useful commands:"
echo " sudo systemctl status ${SERVICE_NAME}"
echo " sudo journalctl -u ${SERVICE_NAME} -f"
Loading…
Cancel
Save