beepzone-helper.sh 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. # Simple TUI-based BeepZone setup helper using `dialog`.
  4. # Targets macOS + Debian-ish Linux (podman, rustup mysql-client already installed).
  5. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  6. WORK_DIR="${SCRIPT_DIR}"
  7. : "${DIALOG:=dialog}"
  8. if ! command -v "$DIALOG" >/dev/null 2>&1; then
  9. echo "\n[ERROR] 'dialog' is not installed or not in PATH." >&2
  10. echo "On macOS: brew install dialog" >&2
  11. echo "On Debian: sudo apt-get install dialog" >&2
  12. exit 1
  13. fi
  14. if ! command -v podman >/dev/null 2>&1; then
  15. "$DIALOG" --title "BeepZone Setup" --msgbox "podman is required but not found in PATH.\nPlease install and configure podman (podman-desktop recommended)." 10 60
  16. exit 1
  17. fi
  18. # Check for MariaDB/MySQL client - try common brew locations on macOS
  19. MYSQL_CLIENT=""
  20. if command -v mariadb >/dev/null 2>&1; then
  21. MYSQL_CLIENT="mariadb"
  22. elif command -v mysql >/dev/null 2>&1; then
  23. MYSQL_CLIENT="mysql"
  24. elif [[ -x /opt/homebrew/opt/mysql-client/bin/mysql ]]; then
  25. MYSQL_CLIENT="/opt/homebrew/opt/mysql-client/bin/mysql"
  26. export PATH="/opt/homebrew/opt/mysql-client/bin:$PATH"
  27. elif [[ -x /opt/homebrew/opt/mariadb/bin/mariadb ]]; then
  28. MYSQL_CLIENT="/opt/homebrew/opt/mariadb/bin/mariadb"
  29. export PATH="/opt/homebrew/opt/mariadb/bin:$PATH"
  30. elif [[ -x /opt/homebrew/bin/mariadb ]]; then
  31. MYSQL_CLIENT="/opt/homebrew/bin/mariadb"
  32. export PATH="/opt/homebrew/bin:$PATH"
  33. elif [[ -x /opt/homebrew/bin/mysql ]]; then
  34. MYSQL_CLIENT="/opt/homebrew/bin/mysql"
  35. export PATH="/opt/homebrew/bin:$PATH"
  36. elif [[ -x /usr/local/opt/mysql-client/bin/mysql ]]; then
  37. MYSQL_CLIENT="/usr/local/opt/mysql-client/bin/mysql"
  38. export PATH="/usr/local/opt/mysql-client/bin:$PATH"
  39. elif [[ -x /usr/local/bin/mariadb ]]; then
  40. MYSQL_CLIENT="/usr/local/bin/mariadb"
  41. export PATH="/usr/local/bin:$PATH"
  42. elif [[ -x /usr/local/bin/mysql ]]; then
  43. MYSQL_CLIENT="/usr/local/bin/mysql"
  44. export PATH="/usr/local/bin:$PATH"
  45. else
  46. "$DIALOG" --title "BeepZone Setup" --msgbox "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
  47. exit 1
  48. fi
  49. TMP_FILE="$(mktemp)"
  50. CHOICE_FILE="$(mktemp)"
  51. trap 'rm -f "$TMP_FILE" "$CHOICE_FILE"' EXIT
  52. LOG_FILE="/tmp/beepzone-helper.log"
  53. > "$LOG_FILE"
  54. ENV_FILE="$SCRIPT_DIR/.env"
  55. # Load existing settings from .env if present
  56. if [[ -f "$ENV_FILE" ]]; then
  57. source "$ENV_FILE"
  58. fi
  59. # Set defaults if not loaded from .env
  60. : "${BEEPZONE_DB_CONTAINER_NAME:=beepzone-mariadb}"
  61. : "${BEEPZONE_DB_IMAGE:=mariadb:12}"
  62. : "${DB_HOST:=127.0.0.1}"
  63. : "${DB_PORT:=3306}"
  64. : "${DB_NAME:=beepzone}"
  65. : "${DB_USER:=beepzone}"
  66. : "${DB_PASS:=beepzone}"
  67. : "${DB_ROOT_PASSWORD:=root}"
  68. : "${SECKELAPI_REPO:=https://git.teleco.ch/crt/seckelapi.git}"
  69. : "${CLIENT_REPO:=https://git.teleco.ch/crt/beepzone-client-egui-emo.git}"
  70. : "${DEPLOYMENT_TYPE:=clean}"
  71. save_env() {
  72. cat > "$ENV_FILE" << EOF
  73. # BeepZone Setup Configuration
  74. # Auto-generated by beepzone-helper.sh
  75. BEEPZONE_DB_CONTAINER_NAME="$BEEPZONE_DB_CONTAINER_NAME"
  76. BEEPZONE_DB_IMAGE="$BEEPZONE_DB_IMAGE"
  77. DB_HOST="$DB_HOST"
  78. DB_PORT="$DB_PORT"
  79. DB_NAME="$DB_NAME"
  80. DB_USER="$DB_USER"
  81. DB_PASS="$DB_PASS"
  82. DB_ROOT_PASSWORD="$DB_ROOT_PASSWORD"
  83. SECKELAPI_REPO="$SECKELAPI_REPO"
  84. CLIENT_REPO="$CLIENT_REPO"
  85. DEPLOYMENT_TYPE="$DEPLOYMENT_TYPE"
  86. EOF
  87. }
  88. ask_main_menu() {
  89. while true; do
  90. $DIALOG --clear \
  91. --title "BeepZone Setup" \
  92. --menu "Choose an action" 17 76 7 \
  93. 1 "Configure & run MariaDB (podman)" \
  94. 2 "Import DB schema & data" \
  95. 3 "Manage users & roles" \
  96. 4 "Configure & setup SeckelAPI" \
  97. 5 "Build desktop client" \
  98. 6 "Update from git masters" \
  99. 7 "Clean sources" \
  100. 8 "Quit" 2>"$CHOICE_FILE"
  101. choice=$(<"$CHOICE_FILE")
  102. case $choice in
  103. 1) configure_and_run_db ;;
  104. 2) import_schema_and_seed ;;
  105. 3) manage_users_and_roles ;;
  106. 4) setup_seckelapi ;;
  107. 5) build_desktop_client ;;
  108. 6) update_from_git_masters ;;
  109. 7) clean_sources ;;
  110. 8) exit 0 ;;
  111. esac
  112. done
  113. }
  114. configure_and_run_db() {
  115. $DIALOG --form "MariaDB container configuration" 18 70 8 \
  116. "DB Host" 1 1 "$DB_HOST" 1 18 30 30 \
  117. "DB Port" 2 1 "$DB_PORT" 2 18 30 30 \
  118. "DB Name" 3 1 "$DB_NAME" 3 18 30 30 \
  119. "DB User" 4 1 "$DB_USER" 4 18 30 30 \
  120. "DB Password" 5 1 "$DB_PASS" 5 18 30 30 \
  121. "Root Password" 6 1 "$DB_ROOT_PASSWORD" 6 18 30 30 \
  122. "Container Name" 7 1 "$BEEPZONE_DB_CONTAINER_NAME" 7 18 30 30 \
  123. "Image" 8 1 "$BEEPZONE_DB_IMAGE" 8 18 30 30 2>"$TMP_FILE" || return
  124. # Parse dialog output (8 lines, one per field)
  125. {
  126. read -r DB_HOST
  127. read -r DB_PORT
  128. read -r DB_NAME
  129. read -r DB_USER
  130. read -r DB_PASS
  131. read -r DB_ROOT_PASSWORD
  132. read -r BEEPZONE_DB_CONTAINER_NAME
  133. read -r BEEPZONE_DB_IMAGE
  134. } < "$TMP_FILE"
  135. save_env
  136. if podman ps -a --format '{{.Names}}' | grep -q "^${BEEPZONE_DB_CONTAINER_NAME}$"; then
  137. $DIALOG --yesno "Container '$BEEPZONE_DB_CONTAINER_NAME' already exists.\n\nStart (or restart) it now?" 10 60
  138. if [[ $? -eq 0 ]]; then
  139. podman start "$BEEPZONE_DB_CONTAINER_NAME" >/dev/null 2>&1 || podman restart "$BEEPZONE_DB_CONTAINER_NAME" >/dev/null 2>&1 || true
  140. $DIALOG --msgbox "Container '$BEEPZONE_DB_CONTAINER_NAME' started (or already running)." 7 60
  141. fi
  142. return
  143. fi
  144. run_cmd=(
  145. podman run -d
  146. --name "${BEEPZONE_DB_CONTAINER_NAME}"
  147. -e "MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}"
  148. -e "MARIADB_DATABASE=${DB_NAME}"
  149. -e "MARIADB_USER=${DB_USER}"
  150. -e "MARIADB_PASSWORD=${DB_PASS}"
  151. -p "${DB_PORT}:3306"
  152. "${BEEPZONE_DB_IMAGE}"
  153. )
  154. pretty_cmd="podman run -d \\
  155. --name ${BEEPZONE_DB_CONTAINER_NAME} \\
  156. -e MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD} \\
  157. -e MARIADB_DATABASE=${DB_NAME} \\
  158. -e MARIADB_USER=${DB_USER} \\
  159. -e MARIADB_PASSWORD=${DB_PASS} \\
  160. -p ${DB_PORT}:3306 ${BEEPZONE_DB_IMAGE}"
  161. if $DIALOG --yesno "About to run:\n\n${pretty_cmd}\n\nProceed?" 17 76; then
  162. if "${run_cmd[@]}" >>"$LOG_FILE" 2>&1; then
  163. $DIALOG --msgbox "MariaDB container '${BEEPZONE_DB_CONTAINER_NAME}' started successfully." 8 70
  164. else
  165. error_log=$(tail -20 "$LOG_FILE")
  166. $DIALOG --title "Error" --msgbox "Failed to start MariaDB container.\n\nError:\n${error_log}" 20 76
  167. fi
  168. fi
  169. }
  170. import_schema_and_seed() {
  171. local import_type schema_file full_dump_file
  172. schema_file="$WORK_DIR/backend/database/schema/beepzone-schema-dump.sql"
  173. full_dump_file="$WORK_DIR/backend/database/dev/beepzone-full-dump.sql"
  174. # Ask what type of import
  175. $DIALOG --clear \
  176. --title "Database Import" \
  177. --menu "Select import type" 12 70 2 \
  178. 1 "Full dump (schema + data in one file)" \
  179. 2 "Clean schema only (no data)" 2>"$CHOICE_FILE" || return
  180. import_type=$(<"$CHOICE_FILE")
  181. if [[ "$import_type" == "1" ]]; then
  182. # Full dump import
  183. if [[ ! -f "$full_dump_file" ]]; then
  184. $DIALOG --msgbox "full dump file not found at:\n$full_dump_file" 10 70
  185. return
  186. fi
  187. $DIALOG --yesno "import full database dump from:\n$full_dump_file\n\nThis will DROP and recreate the database.\n\nProceed?" 12 70 || return
  188. {
  189. echo "DROP DATABASE IF EXISTS \`$DB_NAME\`;"
  190. echo "CREATE DATABASE \`$DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci;"
  191. echo "USE \`$DB_NAME\`;"
  192. echo "SET FOREIGN_KEY_CHECKS=0;"
  193. cat "$full_dump_file"
  194. echo "SET FOREIGN_KEY_CHECKS=1;"
  195. } | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
  196. mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" >>"$LOG_FILE" 2>&1 || {
  197. error_log=$(tail -30 "$LOG_FILE")
  198. $DIALOG --title "Error" --msgbox "full dump import failed.\n\nError:\n${error_log}" 22 76
  199. return
  200. }
  201. DEPLOYMENT_TYPE="dev"
  202. save_env
  203. $DIALOG --msgbox "full database dump imported successfully!" 8 70
  204. else
  205. # Clean schema import
  206. if [[ ! -f "$schema_file" ]]; then
  207. $DIALOG --msgbox "schema file not found at:\n$schema_file" 10 60
  208. return
  209. fi
  210. $DIALOG --yesno "import clean schema from:\n$schema_file\n\nThis will DROP and recreate the database.\n\nProceed?" 12 70 || return
  211. {
  212. echo "DROP DATABASE IF EXISTS \`$DB_NAME\`;"
  213. echo "CREATE DATABASE \`$DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci;"
  214. echo "USE \`$DB_NAME\`;"
  215. echo "SET FOREIGN_KEY_CHECKS=0;"
  216. cat "$schema_file"
  217. echo "SET FOREIGN_KEY_CHECKS=1;"
  218. } | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
  219. mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" >>"$LOG_FILE" 2>&1 || {
  220. error_log=$(tail -30 "$LOG_FILE")
  221. $DIALOG --title "Error" --msgbox "schema import failed.\n\nError:\n${error_log}" 22 76
  222. return
  223. }
  224. DEPLOYMENT_TYPE="clean"
  225. save_env
  226. $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
  227. fi
  228. }
  229. manage_users_and_roles() {
  230. while true; do
  231. $DIALOG --clear \
  232. --title "Manage Users & Roles" \
  233. --menu "Choose an action:" 17 60 6 \
  234. 1 "Create a role" \
  235. 2 "Create a user" \
  236. 3 "Delete a role" \
  237. 4 "Delete a user" \
  238. 5 "Back to main menu" 2>"$CHOICE_FILE"
  239. [[ ! -s "$CHOICE_FILE" ]] && return 0
  240. choice=$(<"$CHOICE_FILE")
  241. case $choice in
  242. 1) create_role ;;
  243. 2) create_user ;;
  244. 3) delete_role ;;
  245. 4) delete_user ;;
  246. 5) return 0 ;;
  247. esac
  248. done
  249. }
  250. create_role() {
  251. $DIALOG --title "Create Role" \
  252. --form "Enter role details:" 12 60 2 \
  253. "Role name:" 1 1 "" 1 20 30 0 \
  254. "Power (1-100):" 2 1 "" 2 20 10 0 \
  255. 2>"$TMP_FILE"
  256. [[ ! -s "$TMP_FILE" ]] && return 1
  257. local name power
  258. {
  259. read -r name
  260. read -r power
  261. } < "$TMP_FILE"
  262. [[ -z "$name" || -z "$power" ]] && {
  263. $DIALOG --msgbox "Role name and power are required." 8 50
  264. return 1
  265. }
  266. # Validate power is a number between 1-100
  267. if ! [[ "$power" =~ ^[0-9]+$ ]] || [[ "$power" -lt 1 || "$power" -gt 100 ]]; then
  268. $DIALOG --msgbox "Power must be a number between 1 and 100." 8 50
  269. return 1
  270. fi
  271. local sql="INSERT INTO roles (name, power) VALUES ('$name', $power);"
  272. echo "$sql" | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
  273. mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" >>"$LOG_FILE" 2>&1 || {
  274. error_log=$(tail -20 "$LOG_FILE")
  275. $DIALOG --title "Error" --msgbox "Failed to create role.\n\nError:\n${error_log}" 20 76
  276. return 1
  277. }
  278. $DIALOG --msgbox "Role '$name' created successfully!" 8 50
  279. }
  280. delete_role() {
  281. # Fetch roles from the database
  282. local roles_list
  283. roles_list=$(podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
  284. mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" \
  285. -e "SELECT id, name, power FROM roles ORDER BY power DESC;" -s -N 2>>"$LOG_FILE") || {
  286. $DIALOG --msgbox "Failed to fetch roles from database." 10 60
  287. return
  288. }
  289. # Build role selection menu
  290. local role_options=()
  291. while IFS=$'\t' read -r role_id role_name role_power; do
  292. role_options+=("$role_id" "$role_name (power: $role_power)")
  293. done <<< "$roles_list"
  294. if [[ ${#role_options[@]} -eq 0 ]]; then
  295. $DIALOG --msgbox "No roles found in database." 10 60
  296. return
  297. fi
  298. # Select role to delete
  299. $DIALOG --menu "Select role to delete:" 20 70 10 "${role_options[@]}" 2>"$TMP_FILE" || return
  300. local selected_role_id
  301. selected_role_id=$(<"$TMP_FILE")
  302. # Get role name for confirmation
  303. local selected_role_name
  304. selected_role_name=$(echo "$roles_list" | awk -v id="$selected_role_id" -F'\t' '$1 == id {print $2}')
  305. # Check if any users are using this role
  306. local user_count
  307. user_count=$(podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
  308. mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" \
  309. -e "SELECT COUNT(*) FROM users WHERE role_id = $selected_role_id;" -s -N 2>>"$LOG_FILE")
  310. if [[ "$user_count" -gt 0 ]]; then
  311. $DIALOG --msgbox "Cannot delete role '$selected_role_name'.\n$user_count user(s) are currently assigned this role." 10 60
  312. return
  313. fi
  314. # Confirm deletion
  315. $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
  316. # Delete role
  317. local sql="DELETE FROM roles WHERE id = $selected_role_id;"
  318. echo "$sql" | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
  319. mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" >>"$LOG_FILE" 2>&1 || {
  320. error_log=$(tail -20 "$LOG_FILE")
  321. $DIALOG --title "Error" --msgbox "Failed to delete role.\n\nError:\n${error_log}" 20 76
  322. return
  323. }
  324. $DIALOG --msgbox "Role '$selected_role_name' deleted successfully!" 8 60
  325. }
  326. create_user() {
  327. # Get available roles
  328. local roles_list
  329. roles_list=$(podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
  330. mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" \
  331. -e "SELECT id, name, power FROM roles ORDER BY power DESC;" -s -N 2>>"$LOG_FILE") || {
  332. $DIALOG --msgbox "Failed to fetch roles from database.\nEnsure schema is imported and roles exist." 10 60
  333. return
  334. }
  335. # Build role selection menu
  336. local role_options=()
  337. while IFS=$'\t' read -r role_id role_name role_power; do
  338. role_options+=("$role_id" "$role_name (power: $role_power)")
  339. done <<< "$roles_list"
  340. if [[ ${#role_options[@]} -eq 0 ]]; then
  341. $DIALOG --msgbox "No roles found in database.\nPlease create a role first." 10 60
  342. return
  343. fi
  344. # Select role
  345. $DIALOG --menu "Select user role" 15 60 5 "${role_options[@]}" 2>"$TMP_FILE" || return
  346. local selected_role_id
  347. selected_role_id=$(<"$TMP_FILE")
  348. # Get user details
  349. $DIALOG --form "Create BeepZone user" 14 70 5 \
  350. "Name (full name)" 1 1 "" 1 20 40 40 \
  351. "Username" 2 1 "" 2 20 40 40 \
  352. "Password" 3 1 "" 3 20 40 40 \
  353. "Email" 4 1 "" 4 20 40 40 \
  354. "Phone" 5 1 "" 5 20 40 40 2>"$TMP_FILE" || return
  355. local name username password email phone
  356. {
  357. read -r name
  358. read -r username
  359. read -r password
  360. read -r email
  361. read -r phone
  362. } < "$TMP_FILE"
  363. if [[ -z "$name" || -z "$username" || -z "$password" ]]; then
  364. $DIALOG --msgbox "Name, username, and password are required." 8 60
  365. return
  366. fi
  367. # Hash password with bcrypt (cost 12)
  368. local password_hash
  369. # Try htpasswd first (native Unix tool)
  370. if command -v htpasswd >/dev/null 2>&1; then
  371. password_hash=$(htpasswd -nbB -C 12 "$username" "$password" 2>>"$LOG_FILE" | cut -d: -f2)
  372. # Fall back to Python bcrypt
  373. elif command -v python3 >/dev/null 2>&1; then
  374. password_hash=$(python3 -c "import bcrypt; print(bcrypt.hashpw('$password'.encode('utf-8'), bcrypt.gensalt(12)).decode('utf-8'))" 2>>"$LOG_FILE")
  375. fi
  376. if [[ -z "$password_hash" ]]; then
  377. $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
  378. return
  379. fi
  380. # Insert user with hashed password
  381. local sql
  382. sql="INSERT INTO users (name, username, password, role_id, email, phone, active) VALUES
  383. ('$name', '$username', '$password_hash', $selected_role_id, "
  384. [[ -n "$email" ]] && sql+="'$email'" || sql+="NULL"
  385. sql+=", "
  386. [[ -n "$phone" ]] && sql+="'$phone'" || sql+="NULL"
  387. sql+=", 1);"
  388. echo "$sql" | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
  389. mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" >>"$LOG_FILE" 2>&1 || {
  390. error_log=$(tail -20 "$LOG_FILE")
  391. $DIALOG --title "Error" --msgbox "Failed to create user.\n\nError:\n${error_log}" 20 76
  392. return
  393. }
  394. $DIALOG --msgbox "User '$username' created successfully!" 8 60
  395. }
  396. delete_user() {
  397. # Fetch users from the database
  398. local users_list
  399. users_list=$(podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
  400. mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" \
  401. -e "SELECT id, username, name FROM users ORDER BY id;" -s -N 2>>"$LOG_FILE") || {
  402. $DIALOG --msgbox "Failed to fetch users from database." 10 60
  403. return
  404. }
  405. # Build user selection menu
  406. local user_options=()
  407. while IFS=$'\t' read -r user_id username name; do
  408. user_options+=("$user_id" "$username - $name")
  409. done <<< "$users_list"
  410. if [[ ${#user_options[@]} -eq 0 ]]; then
  411. $DIALOG --msgbox "No users found in database." 10 60
  412. return
  413. fi
  414. # Select user to delete
  415. $DIALOG --menu "Select user to delete:" 20 70 10 "${user_options[@]}" 2>"$TMP_FILE" || return
  416. local selected_user_id
  417. selected_user_id=$(<"$TMP_FILE")
  418. # Get username for confirmation
  419. local selected_username
  420. selected_username=$(echo "$users_list" | awk -v id="$selected_user_id" -F'\t' '$1 == id {print $2}')
  421. # Confirm deletion
  422. $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
  423. # Delete user
  424. local sql="DELETE FROM users WHERE id = $selected_user_id;"
  425. echo "$sql" | podman exec -i "$BEEPZONE_DB_CONTAINER_NAME" \
  426. mariadb -h"$DB_HOST" -P"$DB_PORT" -uroot -p"$DB_ROOT_PASSWORD" "$DB_NAME" >>"$LOG_FILE" 2>&1 || {
  427. error_log=$(tail -20 "$LOG_FILE")
  428. $DIALOG --title "Error" --msgbox "Failed to delete user.\n\nError:\n${error_log}" 20 76
  429. return
  430. }
  431. $DIALOG --msgbox "User '$selected_username' deleted successfully!" 8 60
  432. }
  433. clone_if_missing() {
  434. local repo_url="$1" dest_dir="$2"
  435. if [[ -d "$dest_dir/.git" ]]; then
  436. return
  437. fi
  438. git clone "$repo_url" "$dest_dir" >>"$LOG_FILE" 2>&1 || {
  439. error_log=$(tail -20 "$LOG_FILE")
  440. $DIALOG --title "Error" --msgbox "Failed to clone $repo_url.\n\nError:\n${error_log}" 20 76
  441. return 1
  442. }
  443. }
  444. setup_seckelapi() {
  445. local sources_dir="$WORK_DIR/backend/seckelapi/sources"
  446. local config_dir="$WORK_DIR/backend/seckelapi/config"
  447. local sources_basics_config="$sources_dir/config/basics.toml"
  448. # Check if sources are already cloned
  449. if [[ ! -d "$sources_dir/.git" ]]; then
  450. mkdir -p "$sources_dir"
  451. clone_if_missing "$SECKELAPI_REPO" "$sources_dir" || return
  452. fi
  453. # Ask about config update
  454. $DIALOG --clear \
  455. --title "SeckelAPI Configuration" \
  456. --menu "How do you want to handle the config?" 12 70 2 \
  457. 1 "Auto-update from database settings" \
  458. 2 "Manually edit config file" 2>"$CHOICE_FILE"
  459. [[ ! -s "$CHOICE_FILE" ]] && return 0
  460. config_choice=$(<"$CHOICE_FILE")
  461. # Ensure sources config directory exists and copy all template configs
  462. mkdir -p "$sources_dir/config"
  463. # Always copy all template configs from config/ to sources/config/
  464. for conf_file in "$config_dir"/*.toml; do
  465. conf_name=$(basename "$conf_file")
  466. cp "$conf_file" "$sources_dir/config/$conf_name" 2>>"$LOG_FILE"
  467. done
  468. if [[ "$config_choice" == "1" ]]; then
  469. # Auto-update basics.toml in sources with database settings (only [database] section)
  470. if [[ -f "$sources_basics_config" ]]; then
  471. # Determine database host - use host.containers.internal for container deployments
  472. local db_host_value="$DB_HOST"
  473. sed -i.bak \
  474. -e '/^\[database\]/,/^\[/ {
  475. /^host = /s|= .*|= "'"$db_host_value"'"|
  476. /^port = /s|= .*|= '"$DB_PORT"'|
  477. /^database = /s|= .*|= "'"$DB_NAME"'"|
  478. /^username = /s|= .*|= "'"$DB_USER"'"|
  479. /^password = /s|= .*|= "'"$DB_PASS"'"|
  480. }' \
  481. "$sources_basics_config"
  482. $DIALOG --msgbox "Config updated with database settings from .env" 8 60
  483. else
  484. $DIALOG --msgbox "Error: basics.toml not found at $sources_basics_config" 8 60
  485. return 1
  486. fi
  487. else
  488. # Open config file for manual editing (in sources/config/)
  489. if [[ -f "$sources_basics_config" ]]; then
  490. ${EDITOR:-nano} "$sources_basics_config"
  491. else
  492. $DIALOG --msgbox "Error: basics.toml not found at $sources_basics_config" 8 60
  493. return 1
  494. fi
  495. fi
  496. # Ask about build and deployment
  497. $DIALOG --clear \
  498. --title "Build & Deploy SeckelAPI" \
  499. --menu "Choose an action:" 12 70 2 \
  500. 1 "Build and deploy to podman container" \
  501. 2 "Build natively on host" 2>"$CHOICE_FILE"
  502. [[ ! -s "$CHOICE_FILE" ]] && return 0
  503. build_choice=$(<"$CHOICE_FILE")
  504. if [[ "$build_choice" == "2" ]]; then
  505. # Native host build with live output
  506. clear
  507. echo "Building SeckelAPI..."
  508. echo "===================="
  509. echo ""
  510. if (cd "$sources_dir" && cargo build --release); then
  511. # Copy config files to the build output directory
  512. local target_dir="$sources_dir/target/release"
  513. mkdir -p "$target_dir/config"
  514. cp "$sources_dir/config"/*.toml "$target_dir/config/" 2>>"$LOG_FILE"
  515. echo ""
  516. echo "Build completed successfully!"
  517. echo "Binary location: $target_dir/seckelapi"
  518. echo "Config location: $target_dir/config/"
  519. read -p "Press Enter to continue..."
  520. else
  521. echo ""
  522. echo "Build failed! Check the output above for errors."
  523. read -p "Press Enter to continue..."
  524. return 1
  525. fi
  526. elif [[ "$build_choice" == "1" ]]; then
  527. # Build and deploy to podman container
  528. clear
  529. echo "Building SeckelAPI container..."
  530. echo "================================"
  531. echo ""
  532. # Get the host gateway IP for container to access host services
  533. # For Podman, we'll use the gateway IP from the default bridge network
  534. local host_gateway="host.containers.internal"
  535. # Update database host for container networking
  536. if [[ -f "$sources_basics_config" ]]; then
  537. echo "Updating database host to: $host_gateway"
  538. sed -i.container-bak \
  539. -e '/^\[database\]/,/^\[/ {
  540. /^host = /s|= .*|= "'"$host_gateway"'"|
  541. }' \
  542. "$sources_basics_config"
  543. fi
  544. local container_name="beepzone-seckelapi"
  545. local image_name="beepzone-seckelapi:latest"
  546. local containerfile="$WORK_DIR/backend/seckelapi/Containerfile"
  547. # Stop and remove existing container if running
  548. if podman ps -a --format "{{.Names}}" | grep -q "^${container_name}$"; then
  549. echo "Stopping and removing existing container..."
  550. podman stop "$container_name" 2>/dev/null || true
  551. podman rm "$container_name" 2>/dev/null || true
  552. fi
  553. # Build container image
  554. echo "Building container image..."
  555. if podman build -t "$image_name" -f "$containerfile" "$WORK_DIR/backend/seckelapi"; then
  556. echo ""
  557. echo "Container image built successfully!"
  558. echo ""
  559. # Ask to run the container
  560. if $DIALOG --yesno "Start the SeckelAPI container now?" 8 50; then
  561. echo "Starting container..."
  562. # Run container with port mapping and host gateway
  563. if podman run -d \
  564. --name "$container_name" \
  565. --add-host host.containers.internal:host-gateway \
  566. -p 5777:5777 \
  567. "$image_name"; then
  568. echo ""
  569. echo "Container started successfully!"
  570. echo "Container name: $container_name"
  571. echo "API listening on: http://0.0.0.0:5777"
  572. echo ""
  573. echo "Useful commands:"
  574. echo " podman logs $container_name - View logs"
  575. echo " podman stop $container_name - Stop container"
  576. echo " podman start $container_name - Start container"
  577. echo " podman restart $container_name - Restart container"
  578. read -p "Press Enter to continue..."
  579. else
  580. echo ""
  581. echo "Failed to start container!"
  582. read -p "Press Enter to continue..."
  583. return 1
  584. fi
  585. fi
  586. else
  587. echo ""
  588. echo "Container build failed! Check the output above for errors."
  589. read -p "Press Enter to continue..."
  590. return 1
  591. fi
  592. fi
  593. }
  594. update_from_git_masters() {
  595. local seckelapi_sources="$WORK_DIR/backend/seckelapi/sources"
  596. local client_sources="$WORK_DIR/frontend/desktop-client/sources"
  597. if $DIALOG --yesno "This will run 'git pull' in both backend and frontend source directories.\n\nContinue?" 10 60; then
  598. clear
  599. echo "Updating sources from git masters..."
  600. echo "===================================="
  601. echo ""
  602. # Update SeckelAPI
  603. if [[ -d "$seckelapi_sources/.git" ]]; then
  604. echo "Updating SeckelAPI..."
  605. if (cd "$seckelapi_sources" && git pull); then
  606. echo "SeckelAPI updated successfully."
  607. else
  608. echo "Failed to update SeckelAPI."
  609. fi
  610. else
  611. echo "SeckelAPI sources not found or not a git repository. Skipping."
  612. fi
  613. echo ""
  614. # Update Desktop Client
  615. if [[ -d "$client_sources/.git" ]]; then
  616. echo "Updating Desktop Client..."
  617. if (cd "$client_sources" && git pull); then
  618. echo "Desktop Client updated successfully."
  619. else
  620. echo "Failed to update Desktop Client."
  621. fi
  622. else
  623. echo "Desktop Client sources not found or not a git repository. Skipping."
  624. fi
  625. echo ""
  626. read -p "Press Enter to continue..."
  627. fi
  628. }
  629. clean_sources() {
  630. $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
  631. local seckelapi_sources="$WORK_DIR/backend/seckelapi/sources"
  632. local client_sources="$WORK_DIR/frontend/desktop-client/sources"
  633. # Clean SeckelAPI sources
  634. if [[ -d "$seckelapi_sources" ]]; then
  635. find "$seckelapi_sources" -mindepth 1 -delete 2>>"$LOG_FILE"
  636. if [[ $? -eq 0 ]]; then
  637. echo "SeckelAPI sources contents removed" >>"$LOG_FILE"
  638. fi
  639. fi
  640. # Clean desktop client sources
  641. if [[ -d "$client_sources" ]]; then
  642. find "$client_sources" -mindepth 1 -delete 2>>"$LOG_FILE"
  643. if [[ $? -eq 0 ]]; then
  644. echo "Desktop client sources contents removed" >>"$LOG_FILE"
  645. fi
  646. fi
  647. $DIALOG --msgbox "All sources cleaned!" 7 50
  648. # Ask to re-clone
  649. if $DIALOG --yesno "Do you want to pull fresh sources now?" 7 50; then
  650. # Clone SeckelAPI
  651. mkdir -p "$(dirname "$seckelapi_sources")"
  652. if clone_if_missing "$SECKELAPI_REPO" "$seckelapi_sources"; then
  653. $DIALOG --msgbox "SeckelAPI sources cloned successfully!" 7 50
  654. else
  655. $DIALOG --msgbox "Failed to clone SeckelAPI sources. Check log." 7 50
  656. return 1
  657. fi
  658. # Clone desktop client
  659. mkdir -p "$(dirname "$client_sources")"
  660. if clone_if_missing "$CLIENT_REPO" "$client_sources"; then
  661. $DIALOG --msgbox "Desktop client sources cloned successfully!" 7 50
  662. else
  663. $DIALOG --msgbox "Failed to clone desktop client sources. Check log." 7 50
  664. return 1
  665. fi
  666. $DIALOG --msgbox "All sources pulled fresh!" 7 50
  667. fi
  668. }
  669. build_desktop_client() {
  670. local sources_dir="$WORK_DIR/frontend/desktop-client/sources"
  671. # Check if sources are already cloned
  672. if [[ ! -d "$sources_dir/.git" ]]; then
  673. mkdir -p "$sources_dir"
  674. clone_if_missing "$CLIENT_REPO" "$sources_dir" || return
  675. fi
  676. if $DIALOG --yesno "Build BeepZone desktop client in release mode now?" 8 70; then
  677. clear
  678. echo "Building BeepZone Desktop Client..."
  679. echo "===================================="
  680. echo ""
  681. if (cd "$sources_dir" && cargo build --release); then
  682. echo ""
  683. echo "Build completed successfully!"
  684. echo "Binary location: $sources_dir/target/release/"
  685. read -p "Press Enter to continue..."
  686. else
  687. echo ""
  688. echo "Build failed! Check the output above for errors."
  689. read -p "Press Enter to continue..."
  690. return 1
  691. fi
  692. fi
  693. }
  694. ask_main_menu