diff --git a/README.md b/README.md index 534ff1e..63d76cf 100644 --- a/README.md +++ b/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. diff --git a/backend/app.py b/backend/app.py index e983606..bbf8543 100644 --- a/backend/app.py +++ b/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: diff --git a/backend/db.py b/backend/db.py index a10f72b..81efa4b 100644 --- a/backend/db.py +++ b/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) diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..a15eb51 --- /dev/null +++ b/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 </dev/null </dev/null <