beepzone-helper.sh 34 KB

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