| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008 |
- #!/usr/bin/env bash
- set -euo pipefail
- # Simple TUI-based BeepZone setup helper using `dialog`.
- # Targets macOS + Debian-ish Linux (podman, rustup mysql-client already installed).
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- WORK_DIR="${SCRIPT_DIR}"
- : "${DIALOG:=dialog}"
- if ! command -v "$DIALOG" >/dev/null 2>&1; then
- echo "\n[ERROR] 'dialog' is not installed or not in PATH." >&2
- echo "On macOS: brew install dialog" >&2
- echo "On Debian: sudo apt install dialog" >&2
- exit 1
- fi
- HAS_PODMAN=true
- if ! command -v podman >/dev/null 2>&1; then
- HAS_PODMAN=false
- "$DIALOG" --title "BeepZone Setup" --yes-label "Exit" --no-label "Ignore" --yesno "podman is recommended but not found in PATH.\nPlease install and configure podman (for dev work podman-desktop is recommended)." 10 60
- if [ $? -eq 0 ]; then
- exit 1
- fi
- fi
- IS_DEBIAN_NATIVE=false
- if [[ -f /etc/debian_version ]]; then
- if grep -qE "^(12|13)" /etc/debian_version; then
- IS_DEBIAN_NATIVE=true
- fi
- fi
- # Check for MariaDB/MySQL client - try common brew locations on macOS
- MYSQL_CLIENT=""
- if command -v mariadb >/dev/null 2>&1; then
- MYSQL_CLIENT="mariadb"
- elif command -v mysql >/dev/null 2>&1; then
- MYSQL_CLIENT="mysql"
- elif [[ -x /opt/homebrew/opt/mysql-client/bin/mysql ]]; then
- MYSQL_CLIENT="/opt/homebrew/opt/mysql-client/bin/mysql"
- export PATH="/opt/homebrew/opt/mysql-client/bin:$PATH"
- elif [[ -x /opt/homebrew/opt/mariadb/bin/mariadb ]]; then
- MYSQL_CLIENT="/opt/homebrew/opt/mariadb/bin/mariadb"
- export PATH="/opt/homebrew/opt/mariadb/bin:$PATH"
- elif [[ -x /opt/homebrew/bin/mariadb ]]; then
- MYSQL_CLIENT="/opt/homebrew/bin/mariadb"
- export PATH="/opt/homebrew/bin:$PATH"
- elif [[ -x /opt/homebrew/bin/mysql ]]; then
- MYSQL_CLIENT="/opt/homebrew/bin/mysql"
- export PATH="/opt/homebrew/bin:$PATH"
- elif [[ -x /usr/local/opt/mysql-client/bin/mysql ]]; then
- MYSQL_CLIENT="/usr/local/opt/mysql-client/bin/mysql"
- export PATH="/usr/local/opt/mysql-client/bin:$PATH"
- elif [[ -x /usr/local/bin/mariadb ]]; then
- MYSQL_CLIENT="/usr/local/bin/mariadb"
- export PATH="/usr/local/bin:$PATH"
- elif [[ -x /usr/local/bin/mysql ]]; then
- MYSQL_CLIENT="/usr/local/bin/mysql"
- export PATH="/usr/local/bin:$PATH"
- else
- "$DIALOG" --title "BeepZone Setup" --yes-label "Exit" --no-label "Ignore" --yesno "MariaDB/MySQL client is required but not found.\n\nSearched locations:\n- System PATH\n- /opt/homebrew/opt/mysql-client/bin/\n- /opt/homebrew/opt/mariadb/bin/\n- /opt/homebrew/bin/\n- /usr/local/opt/mysql-client/bin/\n- /usr/local/bin/\n\nOn macOS: brew install mysql-client\nOn Debian: sudo apt-get install mariadb-client" 18 70
- if [ $? -eq 0 ]; then
- exit 1
- fi
- fi
- TMP_FILE="$(mktemp)"
- CHOICE_FILE="$(mktemp)"
- trap 'rm -f "$TMP_FILE" "$CHOICE_FILE"' EXIT
- LOG_FILE="/tmp/beepzone-helper.log"
- > "$LOG_FILE"
- ENV_FILE="$SCRIPT_DIR/.env"
- # Load existing settings from .env if present
- if [[ -f "$ENV_FILE" ]]; then
- source "$ENV_FILE"
- fi
- # Set defaults if not loaded from .env
- : "${BEEPZONE_DB_CONTAINER_NAME:=beepzone-mariadb}"
- : "${BEEPZONE_DB_IMAGE:=mariadb:12}"
- : "${DB_HOST:=127.0.0.1}"
- : "${DB_PORT:=3306}"
- : "${DB_NAME:=beepzone}"
- : "${DB_USER:=beepzone}"
- : "${DB_PASS:=beepzone}"
- : "${DB_ROOT_PASSWORD:=root}"
- : "${SECKELAPI_REPO:=https://git.teleco.ch/crt/seckelapi.git}"
- : "${CLIENT_REPO:=https://git.teleco.ch/crt/beepzone-client-egui-emo.git}"
- : "${DEPLOYMENT_TYPE:=clean}"
- save_env() {
- cat > "$ENV_FILE" << EOF
- # BeepZone Setup Configuration
- # Auto-generated by beepzone-helper.sh
- BEEPZONE_DB_CONTAINER_NAME="$BEEPZONE_DB_CONTAINER_NAME"
- BEEPZONE_DB_IMAGE="$BEEPZONE_DB_IMAGE"
- DB_HOST="$DB_HOST"
- DB_PORT="$DB_PORT"
- DB_NAME="$DB_NAME"
- DB_USER="$DB_USER"
- DB_PASS="$DB_PASS"
- DB_ROOT_PASSWORD="$DB_ROOT_PASSWORD"
- SECKELAPI_REPO="$SECKELAPI_REPO"
- CLIENT_REPO="$CLIENT_REPO"
- DEPLOYMENT_TYPE="$DEPLOYMENT_TYPE"
- EOF
- }
- ask_main_menu() {
- while true; do
- local options=()
-
- if $HAS_PODMAN; then
- options+=(1 "Configure and run MariaDB (podman)")
- fi
-
- if $IS_DEBIAN_NATIVE; then
- options+=(9 "Configure and run MariaDB (native Debian)") options+=(10 "Setup SeckelAPI (Native Debian)") fi
-
- if [[ -n "$MYSQL_CLIENT" ]]; then
- options+=(
- 2 "Import DB schema and data"
- 3 "Manage users and roles"
- )
- fi
- if $HAS_PODMAN; then
- options+=(4 "Configure and setup SeckelAPI (podman)")
- fi
- options+=(
- 5 "Build desktop client"
- 6 "Update from git masters"
- 7 "Clean sources"
- 8 "Quit"
- )
- $DIALOG --clear \
- --title "BeepZone Setup" \
- --menu "Choose an action" 17 76 7 \
- "${options[@]}" 2>"$CHOICE_FILE"
- choice=$(<"$CHOICE_FILE")
- case $choice in
- 1) configure_and_run_db ;;
- 9) configure_and_run_native_db ;;
- 10) setup_seckelapi_native ;;
- 2) import_schema_and_seed ;;
- 3) manage_users_and_roles ;;
- 4) setup_seckelapi ;;
- 5) build_desktop_client ;;
- 6) update_from_git_masters ;;
- 7) clean_sources ;;
- 8) exit 0 ;;
- esac
- done
- }
- configure_and_run_db() {
- $DIALOG --form "MariaDB container configuration" 18 70 8 \
- "DB Host" 1 1 "$DB_HOST" 1 18 30 30 \
- "DB Port" 2 1 "$DB_PORT" 2 18 30 30 \
- "DB Name" 3 1 "$DB_NAME" 3 18 30 30 \
- "DB User" 4 1 "$DB_USER" 4 18 30 30 \
- "DB Password" 5 1 "$DB_PASS" 5 18 30 30 \
- "Root Password" 6 1 "$DB_ROOT_PASSWORD" 6 18 30 30 \
- "Container Name" 7 1 "$BEEPZONE_DB_CONTAINER_NAME" 7 18 30 30 \
- "Image" 8 1 "$BEEPZONE_DB_IMAGE" 8 18 30 30 2>"$TMP_FILE" || return
- # Parse dialog output (8 lines, one per field)
- {
- read -r DB_HOST
- read -r DB_PORT
- read -r DB_NAME
- read -r DB_USER
- read -r DB_PASS
- read -r DB_ROOT_PASSWORD
- read -r BEEPZONE_DB_CONTAINER_NAME
- read -r BEEPZONE_DB_IMAGE
- } < "$TMP_FILE"
- save_env
- if podman ps -a --format '{{.Names}}' | grep -q "^${BEEPZONE_DB_CONTAINER_NAME}$"; then
- $DIALOG --yesno "Container '$BEEPZONE_DB_CONTAINER_NAME' already exists.\n\nStart (or restart) it now?" 10 60
- if [[ $? -eq 0 ]]; then
- podman start "$BEEPZONE_DB_CONTAINER_NAME" >/dev/null 2>&1 || podman restart "$BEEPZONE_DB_CONTAINER_NAME" >/dev/null 2>&1 || true
- $DIALOG --msgbox "Container '$BEEPZONE_DB_CONTAINER_NAME' started (or already running)." 7 60
- fi
- return
- fi
- run_cmd=(
- podman run -d
- --name "${BEEPZONE_DB_CONTAINER_NAME}"
- -e "MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}"
- -e "MARIADB_DATABASE=${DB_NAME}"
- -e "MARIADB_USER=${DB_USER}"
- -e "MARIADB_PASSWORD=${DB_PASS}"
- -p "${DB_PORT}:3306"
- "${BEEPZONE_DB_IMAGE}"
- )
- pretty_cmd="podman run -d \\
- --name ${BEEPZONE_DB_CONTAINER_NAME} \\
- -e MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD} \\
- -e MARIADB_DATABASE=${DB_NAME} \\
- -e MARIADB_USER=${DB_USER} \\
- -e MARIADB_PASSWORD=${DB_PASS} \\
- -p ${DB_PORT}:3306 ${BEEPZONE_DB_IMAGE}"
- if $DIALOG --yesno "About to run:\n\n${pretty_cmd}\n\nProceed?" 17 76; then
- if "${run_cmd[@]}" >>"$LOG_FILE" 2>&1; then
- $DIALOG --msgbox "MariaDB container '${BEEPZONE_DB_CONTAINER_NAME}' started successfully." 8 70
- else
- error_log=$(tail -20 "$LOG_FILE")
- $DIALOG --title "Error" --msgbox "Failed to start MariaDB container.\n\nError:\n${error_log}" 20 76
- fi
- fi
- }
- configure_and_run_native_db() {
- "$DIALOG" --msgbox "You'll need this for the Native MariaDB Setup:\n\n1. Debian 12/13\n2. Sudo privileges\n3. The following packages installed:\n - mariadb-server\n - mariadb-client\n - sudo\n\nIf you are unsure or know you're missing one hit CTRL-C now" 14 60
- # Ask if root password is set
- if ! "$DIALOG" --yesno "Have you already set up a root password for MariaDB?" 10 60; then
- # User said No (exit code 1)
- clear
- echo "Running mysql_secure_installation..."
- sudo mysql_secure_installation
- fi
- $DIALOG --form "MariaDB configuration" 16 70 6 \
- "DB Host" 1 1 "$DB_HOST" 1 18 30 30 \
- "DB Port" 2 1 "$DB_PORT" 2 18 30 30 \
- "DB Name" 3 1 "$DB_NAME" 3 18 30 30 \
- "DB User" 4 1 "$DB_USER" 4 18 30 30 \
- "DB Password" 5 1 "$DB_PASS" 5 18 30 30 \
- "Root Password" 6 1 "$DB_ROOT_PASSWORD" 6 18 30 30 2>"$TMP_FILE" || return
- # Parse dialog output (6 lines)
- {
- read -r DB_HOST
- read -r DB_PORT
- read -r DB_NAME
- read -r DB_USER
- read -r DB_PASS
- read -r DB_ROOT_PASSWORD
- } < "$TMP_FILE"
- save_env
- # Check if mariadb is even running
- if ! systemctl is-active --quiet mariadb; then
- if $DIALOG --yesno "MariaDB service aint running. Try and start it?" 10 60; then
- sudo systemctl start mariadb || {
- $DIALOG --msgbox "Crap, Failed to start MariaDB service" 8 60
- return
- }
- else
- return
- fi
- fi
- local create_sql="
- CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\`;
- CREATE USER IF NOT EXISTS '${DB_USER}'@'%' IDENTIFIED BY '${DB_PASS}';
- GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_USER}'@'%';
- FLUSH PRIVILEGES;
- "
- if "$DIALOG" --yesno "Script will now do the following:\n\nCreate DB: $DB_NAME\nCreate User: $DB_USER\n\nThis alright?" 12 70; then
- if echo "$create_sql" | mysql -u root -p"$DB_ROOT_PASSWORD" 2>>"$LOG_FILE"; then
- "$DIALOG" --msgbox "Database '$DB_NAME' and user '$DB_USER' created successfully." 8 70
- else
- "$DIALOG" --msgbox "Fuck something went wrong Check $LOG_FILE for details." 8 70
- fi
- fi
- }
- import_schema_and_seed() {
- local import_type schema_file full_dump_file
- schema_file="$WORK_DIR/backend/database/schema/beepzone-schema-dump.sql"
- full_dump_file="$WORK_DIR/backend/database/dev/beepzone-full-dump.sql"
- # Ask what type of import
- $DIALOG --clear \
- --title "Database Import" \
- --menu "Select import type" 12 70 2 \
- 1 "Full dump (schema + data in one file, not recommended unless you know what you're doing)" \
- 2 "Clean schema only (no data, recommended but make sure to read docs on how to get started)" 2>"$CHOICE_FILE" || return
- import_type=$(<"$CHOICE_FILE")
- if [[ "$import_type" == "1" ]]; then
- # Full dump import
- if [[ ! -f "$full_dump_file" ]]; then
- $DIALOG --msgbox "full dump file not found at:\n$full_dump_file" 10 70
- return
- fi
- $DIALOG --yesno "import full database dump from:\n$full_dump_file\n\nThis will DROP and recreate the database.\n\nYou Sure?" 12 70 || return
- {
- echo "DROP DATABASE IF EXISTS \`$DB_NAME\`;"
- echo "CREATE DATABASE \`$DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci;"
- echo "USE \`$DB_NAME\`;"
- echo "SET FOREIGN_KEY_CHECKS=0;"
- cat "$full_dump_file"
- echo "SET FOREIGN_KEY_CHECKS=1;"
- } | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
- mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" >>"$LOG_FILE" 2>&1 || {
- error_log=$(tail -30 "$LOG_FILE")
- $DIALOG --title "Fuck" --msgbox "full dump import failed.\n\nError:\n${error_log}" 22 76
- return
- }
- DEPLOYMENT_TYPE="dev"
- save_env
- $DIALOG --msgbox "full database dump imported successfully!" 8 70
- else
- # Clean schema import
- if [[ ! -f "$schema_file" ]]; then
- $DIALOG --msgbox "schema file not found at:\n$schema_file" 10 60
- return
- fi
- $DIALOG --yesno "import clean schema from:\n$schema_file\n\nThis will DROP and recreate the database.\n\nProceed?" 12 70 || return
- {
- echo "DROP DATABASE IF EXISTS \`$DB_NAME\`;"
- echo "CREATE DATABASE \`$DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci;"
- echo "USE \`$DB_NAME\`;"
- echo "SET FOREIGN_KEY_CHECKS=0;"
- cat "$schema_file"
- echo "SET FOREIGN_KEY_CHECKS=1;"
- } | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
- mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" >>"$LOG_FILE" 2>&1 || {
- error_log=$(tail -30 "$LOG_FILE")
- $DIALOG --title "Error" --msgbox "schema import failed.\n\nError:\n${error_log}" 22 76
- return
- }
- DEPLOYMENT_TYPE="clean"
- save_env
- $DIALOG --msgbox "schema imported successfully!\n\nI recommend you make an Admin role with power 100 and and Admin user as the next step." 8 70
- fi
- }
- manage_users_and_roles() {
- while true; do
- $DIALOG --clear \
- --title "Manage Users & Roles" \
- --menu "Choose an action:" 17 60 6 \
- 1 "Create a role" \
- 2 "Create a user" \
- 3 "Delete a role" \
- 4 "Delete a user" \
- 5 "Back to main menu" 2>"$CHOICE_FILE"
-
- [[ ! -s "$CHOICE_FILE" ]] && return 0
- choice=$(<"$CHOICE_FILE")
-
- case $choice in
- 1) create_role ;;
- 2) create_user ;;
- 3) delete_role ;;
- 4) delete_user ;;
- 5) return 0 ;;
- esac
- done
- }
- create_role() {
- $DIALOG --title "Create Role" \
- --form "Enter role details:" 12 60 2 \
- "Role name:" 1 1 "" 1 20 30 0 \
- "Power (1-100):" 2 1 "" 2 20 10 0 \
- 2>"$TMP_FILE"
- [[ ! -s "$TMP_FILE" ]] && return 1
- local name power
- {
- read -r name
- read -r power
- } < "$TMP_FILE"
- [[ -z "$name" || -z "$power" ]] && {
- $DIALOG --msgbox "Role name and power are required." 8 50
- return 1
- }
- # Validate power is a number between 1-100
- if ! [[ "$power" =~ ^[0-9]+$ ]] || [[ "$power" -lt 1 || "$power" -gt 100 ]]; then
- $DIALOG --msgbox "Power must be a number between 1 and 100." 8 50
- return 1
- fi
- local sql="INSERT INTO roles (name, power) VALUES ('$name', $power);"
- echo "$sql" | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
- mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" >>"$LOG_FILE" 2>&1 || {
- error_log=$(tail -20 "$LOG_FILE")
- $DIALOG --title "Error" --msgbox "Failed to create role.\n\nError:\n${error_log}" 20 76
- return 1
- }
- $DIALOG --msgbox "Role '$name' created successfully!" 8 50
- }
- delete_role() {
- # Fetch roles from the database
- local roles_list
- roles_list=$(podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
- mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" \
- -e "SELECT id, name, power FROM roles ORDER BY power DESC;" -s -N 2>>"$LOG_FILE") || {
- $DIALOG --msgbox "Failed to fetch roles from database." 10 60
- return
- }
- # Build role selection menu
- local role_options=()
- while IFS=$'\t' read -r role_id role_name role_power; do
- role_options+=("$role_id" "$role_name (power: $role_power)")
- done <<< "$roles_list"
- if [[ ${#role_options[@]} -eq 0 ]]; then
- $DIALOG --msgbox "No roles found in database." 10 60
- return
- fi
- # Select role to delete
- $DIALOG --menu "Select role to delete:" 20 70 10 "${role_options[@]}" 2>"$TMP_FILE" || return
- local selected_role_id
- selected_role_id=$(<"$TMP_FILE")
-
- # Get role name for confirmation
- local selected_role_name
- selected_role_name=$(echo "$roles_list" | awk -v id="$selected_role_id" -F'\t' '$1 == id {print $2}')
- # Check if any users are using this role
- local user_count
- user_count=$(podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
- mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" \
- -e "SELECT COUNT(*) FROM users WHERE role_id = $selected_role_id;" -s -N 2>>"$LOG_FILE")
-
- if [[ "$user_count" -gt 0 ]]; then
- $DIALOG --msgbox "Cannot delete role '$selected_role_name'.\n$user_count user(s) are currently assigned this role." 10 60
- return
- fi
- # Confirm deletion
- $DIALOG --yesno "Are you sure you want to delete role '$selected_role_name' (ID: $selected_role_id)?\n\nThis action cannot be undone." 10 60 || return
- # Delete role
- local sql="DELETE FROM roles WHERE id = $selected_role_id;"
-
- echo "$sql" | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
- mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" >>"$LOG_FILE" 2>&1 || {
- error_log=$(tail -20 "$LOG_FILE")
- $DIALOG --title "Error" --msgbox "Failed to delete role.\n\nError:\n${error_log}" 20 76
- return
- }
- $DIALOG --msgbox "Role '$selected_role_name' deleted successfully!" 8 60
- }
- create_user() {
- # Get available roles
- local roles_list
- roles_list=$(podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
- mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" \
- -e "SELECT id, name, power FROM roles ORDER BY power DESC;" -s -N 2>>"$LOG_FILE") || {
- $DIALOG --msgbox "Failed to fetch roles from database.\nEnsure schema is imported and roles exist." 10 60
- return
- }
- # Build role selection menu
- local role_options=()
- while IFS=$'\t' read -r role_id role_name role_power; do
- role_options+=("$role_id" "$role_name (power: $role_power)")
- done <<< "$roles_list"
- if [[ ${#role_options[@]} -eq 0 ]]; then
- $DIALOG --msgbox "No roles found in database.\nPlease create a role first." 10 60
- return
- fi
- # Select role
- $DIALOG --menu "Select user role" 15 60 5 "${role_options[@]}" 2>"$TMP_FILE" || return
- local selected_role_id
- selected_role_id=$(<"$TMP_FILE")
- # Get user details
- $DIALOG --form "Create BeepZone user" 14 70 5 \
- "Name (full name)" 1 1 "" 1 20 40 40 \
- "Username" 2 1 "" 2 20 40 40 \
- "Password" 3 1 "" 3 20 40 40 \
- "Email" 4 1 "" 4 20 40 40 \
- "Phone" 5 1 "" 5 20 40 40 2>"$TMP_FILE" || return
- local name username password email phone
- {
- read -r name
- read -r username
- read -r password
- read -r email
- read -r phone
- } < "$TMP_FILE"
- if [[ -z "$name" || -z "$username" || -z "$password" ]]; then
- $DIALOG --msgbox "Name, username, and password are required." 8 60
- return
- fi
- # Hash password with bcrypt (cost 12)
- local password_hash
-
- # Try htpasswd first (native Unix tool)
- if command -v htpasswd >/dev/null 2>&1; then
- password_hash=$(htpasswd -nbB -C 12 "$username" "$password" 2>>"$LOG_FILE" | cut -d: -f2)
- # Fall back to Python bcrypt
- elif command -v python3 >/dev/null 2>&1; then
- password_hash=$(python3 -c "import bcrypt; print(bcrypt.hashpw('$password'.encode('utf-8'), bcrypt.gensalt(12)).decode('utf-8'))" 2>>"$LOG_FILE")
- fi
-
- if [[ -z "$password_hash" ]]; then
- $DIALOG --msgbox "Error: Failed to hash password. No bcrypt tool found.\n\nInstall options:\n- htpasswd: brew install httpd (macOS) or apt install apache2-utils (Linux)\n- Python bcrypt: pip3 install bcrypt" 12 70
- return
- fi
- # Insert user with hashed password
- local sql
- sql="INSERT INTO users (name, username, password, role_id, email, phone, active) VALUES
- ('$name', '$username', '$password_hash', $selected_role_id, "
- [[ -n "$email" ]] && sql+="'$email'" || sql+="NULL"
- sql+=", "
- [[ -n "$phone" ]] && sql+="'$phone'" || sql+="NULL"
- sql+=", 1);"
- echo "$sql" | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
- mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" >>"$LOG_FILE" 2>&1 || {
- error_log=$(tail -20 "$LOG_FILE")
- $DIALOG --title "Error" --msgbox "Failed to create user.\n\nError:\n${error_log}" 20 76
- return
- }
- $DIALOG --msgbox "User '$username' created successfully!" 8 60
- }
- delete_user() {
- # Fetch users from the database
- local users_list
- users_list=$(podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
- mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" \
- -e "SELECT id, username, name FROM users ORDER BY id;" -s -N 2>>"$LOG_FILE") || {
- $DIALOG --msgbox "Failed to fetch users from database." 10 60
- return
- }
- # Build user selection menu
- local user_options=()
- while IFS=$'\t' read -r user_id username name; do
- user_options+=("$user_id" "$username - $name")
- done <<< "$users_list"
- if [[ ${#user_options[@]} -eq 0 ]]; then
- $DIALOG --msgbox "No users found in database." 10 60
- return
- fi
- # Select user to delete
- $DIALOG --menu "Select user to delete:" 20 70 10 "${user_options[@]}" 2>"$TMP_FILE" || return
- local selected_user_id
- selected_user_id=$(<"$TMP_FILE")
-
- # Get username for confirmation
- local selected_username
- selected_username=$(echo "$users_list" | awk -v id="$selected_user_id" -F'\t' '$1 == id {print $2}')
- # Confirm deletion
- $DIALOG --yesno "Are you sure you want to delete user '$selected_username' (ID: $selected_user_id)?\n\nThis action cannot be undone." 10 60 || return
- # Delete user
- local sql="DELETE FROM users WHERE id = $selected_user_id;"
-
- echo "$sql" | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
- mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" >>"$LOG_FILE" 2>&1 || {
- error_log=$(tail -20 "$LOG_FILE")
- $DIALOG --title "Error" --msgbox "Failed to delete user.\n\nError:\n${error_log}" 20 76
- return
- }
- $DIALOG --msgbox "User '$selected_username' deleted successfully!" 8 60
- }
- clone_if_missing() {
- local repo_url="$1" dest_dir="$2"
- if [[ -d "$dest_dir/.git" ]]; then
- return
- fi
- git clone "$repo_url" "$dest_dir" >>"$LOG_FILE" 2>&1 || {
- error_log=$(tail -20 "$LOG_FILE")
- $DIALOG --title "Error" --msgbox "Failed to clone $repo_url.\n\nError:\n${error_log}" 20 76
- return 1
- }
- }
- setup_seckelapi() {
- local sources_dir="$WORK_DIR/backend/seckelapi/sources"
- local config_dir="$WORK_DIR/backend/seckelapi/config"
- local sources_basics_config="$sources_dir/config/basics.toml"
-
- # Check if sources are already cloned
- if [[ ! -d "$sources_dir/.git" ]]; then
- mkdir -p "$sources_dir"
- clone_if_missing "$SECKELAPI_REPO" "$sources_dir" || return
- fi
- # Ask about config update
- $DIALOG --clear \
- --title "SeckelAPI Configuration" \
- --menu "How do you want to handle the config?" 12 70 2 \
- 1 "Auto-update from database settings" \
- 2 "Manually edit config file" 2>"$CHOICE_FILE"
-
- [[ ! -s "$CHOICE_FILE" ]] && return 0
- config_choice=$(<"$CHOICE_FILE")
-
- # Ensure sources config directory exists and copy all template configs
- mkdir -p "$sources_dir/config"
-
- # Always copy all template configs from config/ to sources/config/
- for conf_file in "$config_dir"/*.toml; do
- conf_name=$(basename "$conf_file")
- cp "$conf_file" "$sources_dir/config/$conf_name" 2>>"$LOG_FILE"
- done
-
- if [[ "$config_choice" == "1" ]]; then
- # Auto-update basics.toml in sources with database settings (only [database] section)
- if [[ -f "$sources_basics_config" ]]; then
- # Determine database host - use host.containers.internal for container deployments
- local db_host_value="$DB_HOST"
-
- sed -i.bak \
- -e '/^\[database\]/,/^\[/ {
- /^host = /s|= .*|= "'"$db_host_value"'"|
- /^port = /s|= .*|= '"$DB_PORT"'|
- /^database = /s|= .*|= "'"$DB_NAME"'"|
- /^username = /s|= .*|= "'"$DB_USER"'"|
- /^password = /s|= .*|= "'"$DB_PASS"'"|
- }' \
- "$sources_basics_config"
- $DIALOG --msgbox "Config updated with database settings from .env" 8 60
- else
- $DIALOG --msgbox "Error: basics.toml not found at $sources_basics_config" 8 60
- return 1
- fi
- else
- # Open config file for manual editing (in sources/config/)
- if [[ -f "$sources_basics_config" ]]; then
- ${EDITOR:-nano} "$sources_basics_config"
- else
- $DIALOG --msgbox "Error: basics.toml not found at $sources_basics_config" 8 60
- return 1
- fi
- fi
- # Ask about build and deployment
- $DIALOG --clear \
- --title "Build & Deploy SeckelAPI" \
- --menu "Choose an action:" 12 70 2 \
- 1 "Build and deploy to podman container" \
- 2 "Build natively on host" 2>"$CHOICE_FILE"
-
- [[ ! -s "$CHOICE_FILE" ]] && return 0
- build_choice=$(<"$CHOICE_FILE")
-
- if [[ "$build_choice" == "2" ]]; then
- # Native host build with live output
- clear
- echo "Building SeckelAPI..."
- echo "===================="
- echo ""
-
- if (cd "$sources_dir" && cargo build --release); then
- # Copy config files to the build output directory
- local target_dir="$sources_dir/target/release"
- mkdir -p "$target_dir/config"
- cp "$sources_dir/config"/*.toml "$target_dir/config/" 2>>"$LOG_FILE"
-
- echo ""
- echo "Build completed successfully!"
- echo "Binary location: $target_dir/seckelapi"
- echo "Config location: $target_dir/config/"
- read -p "Press Enter to continue..."
- else
- echo ""
- echo "Build failed! Check the output above for errors."
- read -p "Press Enter to continue..."
- return 1
- fi
- elif [[ "$build_choice" == "1" ]]; then
- # Build and deploy to podman container
- clear
- echo "Building SeckelAPI container..."
- echo "================================"
- echo ""
-
- # Get the host gateway IP for container to access host services
- # For Podman, we'll use the gateway IP from the default bridge network
- local host_gateway="host.containers.internal"
-
- # Update database host for container networking
- if [[ -f "$sources_basics_config" ]]; then
- echo "Updating database host to: $host_gateway"
- sed -i.container-bak \
- -e '/^\[database\]/,/^\[/ {
- /^host = /s|= .*|= "'"$host_gateway"'"|
- }' \
- "$sources_basics_config"
- fi
-
- local container_name="beepzone-seckelapi"
- local image_name="beepzone-seckelapi:latest"
- local containerfile="$WORK_DIR/backend/seckelapi/Containerfile"
-
- # Stop and remove existing container if running
- if podman ps -a --format "{{.Names}}" | grep -q "^${container_name}$"; then
- echo "Stopping and removing existing container..."
- podman stop "$container_name" 2>/dev/null || true
- podman rm "$container_name" 2>/dev/null || true
- fi
-
- # Build container image
- echo "Building container image..."
- if podman build -t "$image_name" -f "$containerfile" "$WORK_DIR/backend/seckelapi"; then
- echo ""
- echo "Container image built successfully!"
- echo ""
-
- # Ask to run the container
- if $DIALOG --yesno "Start the SeckelAPI container now?" 8 50; then
- echo "Starting container..."
-
- # Run container with port mapping and host gateway
- if podman run -d \
- --name "$container_name" \
- --add-host host.containers.internal:host-gateway \
- -p 5777:5777 \
- "$image_name"; then
-
- echo ""
- echo "Container started successfully!"
- echo "Container name: $container_name"
- echo "API listening on: http://0.0.0.0:5777"
- echo ""
- echo "Useful commands:"
- echo " podman logs $container_name - View logs"
- echo " podman stop $container_name - Stop container"
- echo " podman start $container_name - Start container"
- echo " podman restart $container_name - Restart container"
- read -p "Press Enter to continue..."
- else
- echo ""
- echo "Failed to start container!"
- read -p "Press Enter to continue..."
- return 1
- fi
- fi
- else
- echo ""
- echo "Container build failed! Check the output above for errors."
- read -p "Press Enter to continue..."
- return 1
- fi
- fi
- }
- setup_seckelapi_native() {
- # 1. Check Pre-requisites
- $DIALOG --msgbox "Pre-requisites for Native SeckelAPI Setup:\n\n1. Rust toolchain (rustup, cargo)\n2. build-essential (gcc, etc.)\n3. pkg-config\n4. libssl-dev\n5. Sudo privileges (for systemd service)\n\nEnsure these are met before proceeding." 14 60
- local sources_dir="$WORK_DIR/backend/seckelapi/sources"
- local config_dir="$WORK_DIR/backend/seckelapi/config"
- local sources_basics_config="$sources_dir/config/basics.toml"
-
- # 2. Clone sources if missing
- if [[ ! -d "$sources_dir/.git" ]]; then
- mkdir -p "$sources_dir"
- clone_if_missing "$SECKELAPI_REPO" "$sources_dir" || return
- fi
- # 3. Configure basics.toml
- # Ensure sources config directory exists and copy all template configs
- mkdir -p "$sources_dir/config"
- for conf_file in "$config_dir"/*.toml; do
- conf_name=$(basename "$conf_file")
- cp "$conf_file" "$sources_dir/config/$conf_name" 2>>"$LOG_FILE"
- done
-
- if [[ -f "$sources_basics_config" ]]; then
- # Update with DB settings from .env (localhost since it's native)
- sed -i.bak \
- -e '/^\[database\]/,/^\[/ {
- /^host = /s|= .*|= "'"$DB_HOST"'"|
- /^port = /s|= .*|= '"$DB_PORT"'|
- /^database = /s|= .*|= "'"$DB_NAME"'"|
- /^username = /s|= .*|= "'"$DB_USER"'"|
- /^password = /s|= .*|= "'"$DB_PASS"'"|
- }' \
- "$sources_basics_config"
- else
- $DIALOG --msgbox "Error: basics.toml not found at $sources_basics_config" 8 60
- return 1
- fi
- # 4. Build Release
- clear
- echo "Building SeckelAPI (Native)..."
- echo "============================"
- echo ""
-
- if (cd "$sources_dir" && cargo build --release); then
- # Copy config files to the build output directory
- local target_dir="$sources_dir/target/release"
- mkdir -p "$target_dir/config"
- cp "$sources_dir/config"/*.toml "$target_dir/config/" 2>>"$LOG_FILE"
-
- echo ""
- echo "Build completed successfully!"
- else
- echo ""
- echo "Build failed! Check the output above for errors."
- read -p "Press Enter to continue..."
- return 1
- fi
- # 5. Install Systemd Service
- if $DIALOG --yesno "Do you want to install SeckelAPI as a systemd service?" 8 60; then
- local service_name="beepzone-seckelapi"
- local service_file="/tmp/${service_name}.service"
- local current_user
- current_user=$(whoami)
-
- # Working directory for the binary
- local working_dir="$sources_dir/target/release"
- local exec_path="$working_dir/seckelapi"
- cat > "$service_file" <<EOF
- [Unit]
- Description=BeepZone SeckelAPI Service
- After=network.target mariadb.service
- Requires=mariadb.service
- [Service]
- Type=simple
- User=${current_user}
- WorkingDirectory=${working_dir}
- ExecStart=${exec_path}
- Restart=always
- RestartSec=10
- Environment=RUST_LOG=info
- [Install]
- WantedBy=multi-user.target
- EOF
- echo "Installing systemd service..."
- sudo mv "$service_file" "/etc/systemd/system/${service_name}.service"
- sudo systemctl daemon-reload
- sudo systemctl enable "${service_name}"
-
- if sudo systemctl start "${service_name}"; then
- $DIALOG --msgbox "Service '${service_name}' installed and started successfully!" 8 60
- else
- $DIALOG --msgbox "Failed to start service '${service_name}'. Check 'sudo systemctl status ${service_name}'" 10 70
- fi
- fi
- }
- update_from_git_masters() {
- local seckelapi_sources="$WORK_DIR/backend/seckelapi/sources"
- local client_sources="$WORK_DIR/frontend/desktop-client/sources"
- if $DIALOG --yesno "This will run 'git pull' in both backend and frontend source directories.\n\nContinue?" 10 60; then
- clear
- echo "Updating sources from git masters..."
- echo "===================================="
- echo ""
- # Update SeckelAPI
- if [[ -d "$seckelapi_sources/.git" ]]; then
- echo "Updating SeckelAPI..."
- if (cd "$seckelapi_sources" && git pull); then
- echo "SeckelAPI updated successfully."
- else
- echo "Failed to update SeckelAPI."
- fi
- else
- echo "SeckelAPI sources not found or not a git repository. Skipping."
- fi
- echo ""
- # Update Desktop Client
- if [[ -d "$client_sources/.git" ]]; then
- echo "Updating Desktop Client..."
- if (cd "$client_sources" && git pull); then
- echo "Desktop Client updated successfully."
- else
- echo "Failed to update Desktop Client."
- fi
- else
- echo "Desktop Client sources not found or not a git repository. Skipping."
- fi
- echo ""
- read -p "Press Enter to continue..."
- fi
- }
- clean_sources() {
- $DIALOG --yesno "This will delete all sources directories including hidden files:\n\n- backend/seckelapi/sources\n- frontend/desktop-client/sources\n\nAre you sure?" 12 70 || return
-
- local seckelapi_sources="$WORK_DIR/backend/seckelapi/sources"
- local client_sources="$WORK_DIR/frontend/desktop-client/sources"
-
- # Clean SeckelAPI sources
- if [[ -d "$seckelapi_sources" ]]; then
- find "$seckelapi_sources" -mindepth 1 -delete 2>>"$LOG_FILE"
- if [[ $? -eq 0 ]]; then
- echo "SeckelAPI sources contents removed" >>"$LOG_FILE"
- fi
- fi
-
- # Clean desktop client sources
- if [[ -d "$client_sources" ]]; then
- find "$client_sources" -mindepth 1 -delete 2>>"$LOG_FILE"
- if [[ $? -eq 0 ]]; then
- echo "Desktop client sources contents removed" >>"$LOG_FILE"
- fi
- fi
-
- $DIALOG --msgbox "All sources cleaned!" 7 50
-
- # Ask to re-clone
- if $DIALOG --yesno "Do you want to pull fresh sources now?" 7 50; then
- # Clone SeckelAPI
- mkdir -p "$(dirname "$seckelapi_sources")"
- if clone_if_missing "$SECKELAPI_REPO" "$seckelapi_sources"; then
- $DIALOG --msgbox "SeckelAPI sources cloned successfully!" 7 50
- else
- $DIALOG --msgbox "Failed to clone SeckelAPI sources. Check log." 7 50
- return 1
- fi
-
- # Clone desktop client
- mkdir -p "$(dirname "$client_sources")"
- if clone_if_missing "$CLIENT_REPO" "$client_sources"; then
- $DIALOG --msgbox "Desktop client sources cloned successfully!" 7 50
- else
- $DIALOG --msgbox "Failed to clone desktop client sources. Check log." 7 50
- return 1
- fi
-
- $DIALOG --msgbox "All sources pulled fresh!" 7 50
- fi
- }
- build_desktop_client() {
- local sources_dir="$WORK_DIR/frontend/desktop-client/sources"
-
- # Check if sources are already cloned
- if [[ ! -d "$sources_dir/.git" ]]; then
- mkdir -p "$sources_dir"
- clone_if_missing "$CLIENT_REPO" "$sources_dir" || return
- fi
- if $DIALOG --yesno "Build BeepZone desktop client in release mode now?" 8 70; then
- clear
- echo "Building BeepZone Desktop Client..."
- echo "===================================="
- echo ""
-
- if (cd "$sources_dir" && cargo build --release); then
- echo ""
- echo "Build completed successfully!"
- echo "Binary location: $sources_dir/target/release/"
- read -p "Press Enter to continue..."
- else
- echo ""
- echo "Build failed! Check the output above for errors."
- read -p "Press Enter to continue..."
- return 1
- fi
- fi
- }
- ask_main_menu
|