1
0

beepzone-helper.ps1 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. # BeepZone Setup Helper for Windows
  2. # PowerShell version - no WSL needed
  3. $ErrorActionPreference = "Stop"
  4. $SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path
  5. $WORK_DIR = $SCRIPT_DIR
  6. $LOG_FILE = "$env:TEMP\beepzone-helper.log"
  7. $ENV_FILE = Join-Path $SCRIPT_DIR ".env"
  8. # Initialize log
  9. "" | Out-File $LOG_FILE -Force
  10. # Default configuration
  11. $config = @{
  12. BEEPZONE_DB_CONTAINER_NAME = "beepzone-mariadb"
  13. BEEPZONE_DB_IMAGE = "mariadb:12"
  14. DB_HOST = "127.0.0.1"
  15. DB_PORT = "3306"
  16. DB_NAME = "beepzone"
  17. DB_USER = "beepzone"
  18. DB_PASS = "beepzone"
  19. DB_ROOT_PASSWORD = "root"
  20. SECKELAPI_REPO = "https://git.teleco.ch/crt/seckelapi.git"
  21. CLIENT_REPO = "https://git.teleco.ch/crt/beepzone-client-egui-emo.git"
  22. DEPLOYMENT_TYPE = "clean"
  23. }
  24. # Load existing .env if present
  25. if (Test-Path $ENV_FILE) {
  26. Get-Content $ENV_FILE | ForEach-Object {
  27. if ($_ -match '^([^=]+)="?([^"]*)"?$') {
  28. $config[$matches[1]] = $matches[2]
  29. }
  30. }
  31. }
  32. function Save-Config {
  33. @"
  34. # BeepZone Setup Configuration
  35. # Auto-generated by beepzone-helper.ps1
  36. BEEPZONE_DB_CONTAINER_NAME="$($config.BEEPZONE_DB_CONTAINER_NAME)"
  37. BEEPZONE_DB_IMAGE="$($config.BEEPZONE_DB_IMAGE)"
  38. DB_HOST="$($config.DB_HOST)"
  39. DB_PORT="$($config.DB_PORT)"
  40. DB_NAME="$($config.DB_NAME)"
  41. DB_USER="$($config.DB_USER)"
  42. DB_PASS="$($config.DB_PASS)"
  43. DB_ROOT_PASSWORD="$($config.DB_ROOT_PASSWORD)"
  44. SECKELAPI_REPO="$($config.SECKELAPI_REPO)"
  45. CLIENT_REPO="$($config.CLIENT_REPO)"
  46. DEPLOYMENT_TYPE="$($config.DEPLOYMENT_TYPE)"
  47. "@ | Out-File $ENV_FILE -Encoding UTF8
  48. }
  49. function Show-Menu {
  50. param([string]$Title, [array]$Options)
  51. Write-Host "`n=== $Title ===" -ForegroundColor Cyan
  52. for ($i = 0; $i -lt $Options.Count; $i++) {
  53. Write-Host " [$($i+1)] $($Options[$i])"
  54. }
  55. Write-Host ""
  56. $choice = Read-Host "Choose an option (1-$($Options.Count))"
  57. return [int]$choice
  58. }
  59. function Run-Command {
  60. param(
  61. [string]$Command,
  62. [array]$Arguments,
  63. [string]$SuccessMessage,
  64. [string]$ErrorMessage,
  65. [switch]$ShowOutput,
  66. [switch]$LiveOutput
  67. )
  68. Write-Host "Running: $Command $($Arguments -join ' ')" -ForegroundColor DarkGray
  69. Write-Host ""
  70. if ($LiveOutput) {
  71. # Show output in real-time for long-running commands
  72. $process = Start-Process -FilePath $Command -ArgumentList $Arguments -NoNewWindow -Wait -PassThru
  73. $exitCode = $process.ExitCode
  74. # Log the command execution
  75. "$Command $($Arguments -join ' ')" | Out-File $LOG_FILE -Append
  76. "Exit Code: $exitCode" | Out-File $LOG_FILE -Append
  77. Write-Host ""
  78. if ($exitCode -ne 0) {
  79. Write-Host "$ErrorMessage (Exit Code: $exitCode)" -ForegroundColor Red
  80. Write-Host "Full log: $LOG_FILE" -ForegroundColor DarkGray
  81. return $false
  82. } else {
  83. if ($SuccessMessage) {
  84. Write-Host $SuccessMessage -ForegroundColor Green
  85. }
  86. return $true
  87. }
  88. } else {
  89. # Capture output for commands where we want to parse it
  90. $tempOutput = New-TemporaryFile
  91. $tempError = New-TemporaryFile
  92. try {
  93. $process = Start-Process -FilePath $Command -ArgumentList $Arguments -NoNewWindow -Wait -PassThru `
  94. -RedirectStandardOutput $tempOutput.FullName -RedirectStandardError $tempError.FullName
  95. $exitCode = $process.ExitCode
  96. $stdOut = Get-Content $tempOutput.FullName -Raw -ErrorAction SilentlyContinue
  97. $stdErr = Get-Content $tempError.FullName -Raw -ErrorAction SilentlyContinue
  98. # Log everything
  99. if ($stdOut) { $stdOut | Out-File $LOG_FILE -Append }
  100. if ($stdErr) { $stdErr | Out-File $LOG_FILE -Append }
  101. # Show informational stderr messages if requested
  102. if ($ShowOutput -and $stdErr) {
  103. $stdErr.Split("`n") | ForEach-Object {
  104. if ($_.Trim()) { Write-Host " $_" -ForegroundColor DarkGray }
  105. }
  106. }
  107. if ($exitCode -ne 0) {
  108. Write-Host "`n$ErrorMessage" -ForegroundColor Red
  109. Write-Host "Exit Code: $exitCode" -ForegroundColor Yellow
  110. if ($stdOut) {
  111. Write-Host "Output:" -ForegroundColor Yellow
  112. $stdOut.Split("`n") | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  113. }
  114. if ($stdErr) {
  115. Write-Host "Error:" -ForegroundColor Yellow
  116. $stdErr.Split("`n") | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  117. }
  118. Write-Host "`nFull log: $LOG_FILE" -ForegroundColor DarkGray
  119. return $false
  120. } else {
  121. if ($SuccessMessage) {
  122. Write-Host $SuccessMessage -ForegroundColor Green
  123. }
  124. # Show stdout if it contains useful info (like container ID)
  125. if ($stdOut -and $stdOut.Trim().Length -lt 100) {
  126. Write-Host " → $($stdOut.Trim())" -ForegroundColor DarkGray
  127. }
  128. return $true
  129. }
  130. } finally {
  131. Remove-Item $tempOutput -ErrorAction SilentlyContinue
  132. Remove-Item $tempError -ErrorAction SilentlyContinue
  133. }
  134. }
  135. }
  136. function Test-Dependencies {
  137. $missing = @()
  138. if (-not (Get-Command podman -ErrorAction SilentlyContinue)) {
  139. $missing += "Podman (install Podman Desktop from podman.io)"
  140. }
  141. # Check for MySQL/MariaDB client in PATH and common installation locations
  142. $mysqlFound = $false
  143. $script:MYSQL_CLIENT = ""
  144. # First check if already in PATH
  145. foreach ($cmd in @("mysql", "mariadb")) {
  146. if (Get-Command $cmd -ErrorAction SilentlyContinue) {
  147. $script:MYSQL_CLIENT = $cmd
  148. $mysqlFound = $true
  149. break
  150. }
  151. }
  152. # If not in PATH, check common Windows installation locations
  153. if (-not $mysqlFound) {
  154. $searchPaths = @(
  155. "C:\Program Files\MariaDB*\bin\mysql.exe",
  156. "C:\Program Files\MariaDB*\bin\mariadb.exe",
  157. "C:\Program Files\MySQL\MySQL Server*\bin\mysql.exe",
  158. "C:\Program Files (x86)\MariaDB*\bin\mysql.exe",
  159. "C:\Program Files (x86)\MySQL\MySQL Server*\bin\mysql.exe"
  160. )
  161. foreach ($pattern in $searchPaths) {
  162. $found = Get-ChildItem $pattern -ErrorAction SilentlyContinue | Select-Object -First 1
  163. if ($found) {
  164. $script:MYSQL_CLIENT = $found.FullName
  165. $mysqlFound = $true
  166. # Add to PATH for this session
  167. $binDir = Split-Path $found.FullName
  168. $env:Path = "$binDir;$env:Path"
  169. Write-Host "Found MySQL/MariaDB at: $($found.FullName)" -ForegroundColor Green
  170. break
  171. }
  172. }
  173. }
  174. if (-not $mysqlFound) {
  175. $missing += "MySQL/MariaDB client (winget install Oracle.MySQL or MariaDB.MariaDB)"
  176. }
  177. if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
  178. $missing += "Git (winget install Git.Git)"
  179. }
  180. if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
  181. $missing += "Rust/Cargo (https://rustup.rs)"
  182. }
  183. if ($missing.Count -gt 0) {
  184. Write-Host "`nMissing dependencies:" -ForegroundColor Red
  185. $missing | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow }
  186. Write-Host "`nPlease install the missing tools and try again.`n"
  187. exit 1
  188. }
  189. }
  190. function Configure-Database {
  191. Write-Host "`n=== MariaDB Container Configuration ===" -ForegroundColor Cyan
  192. $config.DB_HOST = Read-Host "DB Host [$($config.DB_HOST)]"
  193. if ([string]::IsNullOrWhiteSpace($config.DB_HOST)) { $config.DB_HOST = "127.0.0.1" }
  194. $port = Read-Host "DB Port [$($config.DB_PORT)]"
  195. if ($port) { $config.DB_PORT = $port }
  196. $name = Read-Host "DB Name [$($config.DB_NAME)]"
  197. if ($name) { $config.DB_NAME = $name }
  198. $user = Read-Host "DB User [$($config.DB_USER)]"
  199. if ($user) { $config.DB_USER = $user }
  200. $pass = Read-Host "DB Password [$($config.DB_PASS)]"
  201. if ($pass) { $config.DB_PASS = $pass }
  202. $rootPass = Read-Host "Root Password [$($config.DB_ROOT_PASSWORD)]"
  203. if ($rootPass) { $config.DB_ROOT_PASSWORD = $rootPass }
  204. Save-Config
  205. # Check if container exists
  206. $exists = podman ps -a --format "{{.Names}}" | Select-String -Pattern "^$($config.BEEPZONE_DB_CONTAINER_NAME)$" -Quiet
  207. if ($exists) {
  208. $restart = Read-Host "`nContainer '$($config.BEEPZONE_DB_CONTAINER_NAME)' exists. Start/restart it? (y/n)"
  209. if ($restart -eq 'y') {
  210. $null = Run-Command -Command "podman" -Arguments @("start", $config.BEEPZONE_DB_CONTAINER_NAME) `
  211. -SuccessMessage "Container started successfully!" `
  212. -ErrorMessage "Failed to start container"
  213. }
  214. return
  215. }
  216. # Create new container
  217. Write-Host "`nCreating new MariaDB container..." -ForegroundColor Yellow
  218. $cmd = @(
  219. "run", "-d",
  220. "--name", $config.BEEPZONE_DB_CONTAINER_NAME,
  221. "-e", "MARIADB_ROOT_PASSWORD=$($config.DB_ROOT_PASSWORD)",
  222. "-e", "MARIADB_DATABASE=$($config.DB_NAME)",
  223. "-e", "MARIADB_USER=$($config.DB_USER)",
  224. "-e", "MARIADB_PASSWORD=$($config.DB_PASS)",
  225. "-p", "$($config.DB_PORT):3306",
  226. $config.BEEPZONE_DB_IMAGE
  227. )
  228. $success = Run-Command -Command "podman" -Arguments $cmd `
  229. -SuccessMessage "MariaDB container started successfully!" `
  230. -ErrorMessage "Failed to start MariaDB container"
  231. if (-not $success) {
  232. Read-Host "`nPress Enter to continue"
  233. }
  234. }
  235. function Import-Database {
  236. $schemaFile = Join-Path $WORK_DIR "backend\database\schema\beepzone-schema-dump.sql"
  237. $fullDumpFile = Join-Path $WORK_DIR "backend\database\dev\beepzone-full-dump.sql"
  238. $choice = Show-Menu "Database Import" @(
  239. "Full dump (schema + data)",
  240. "Clean schema only (no data)"
  241. )
  242. $file = if ($choice -eq 1) { $fullDumpFile } else { $schemaFile }
  243. if (-not (Test-Path $file)) {
  244. Write-Host "File not found: $file" -ForegroundColor Red
  245. Read-Host "Press Enter to continue"
  246. return
  247. }
  248. $confirm = Read-Host "`nThis will DROP and recreate the database. Continue? (y/n)"
  249. if ($confirm -ne 'y') { return }
  250. Write-Host "Importing database..." -ForegroundColor Yellow
  251. $sql = @"
  252. DROP DATABASE IF EXISTS ``$($config.DB_NAME)``;
  253. CREATE DATABASE ``$($config.DB_NAME)`` CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci;
  254. USE ``$($config.DB_NAME)``;
  255. SET FOREIGN_KEY_CHECKS=0;
  256. $(Get-Content $file -Raw)
  257. SET FOREIGN_KEY_CHECKS=1;
  258. "@
  259. # Import via podman exec
  260. Write-Host "Importing SQL..." -ForegroundColor Yellow
  261. $tempFile = New-TemporaryFile
  262. $sql | Out-File $tempFile.FullName -Encoding UTF8
  263. $importCmd = "podman exec -i $($config.BEEPZONE_DB_CONTAINER_NAME) mariadb -h $($config.DB_HOST) -P $($config.DB_PORT) -uroot -p`"$($config.DB_ROOT_PASSWORD)`" < `"$($tempFile.FullName)`""
  264. $output = cmd /c $importCmd 2`>`&1
  265. $exitCode = $LASTEXITCODE
  266. Remove-Item $tempFile -ErrorAction SilentlyContinue
  267. $output | Out-File $LOG_FILE -Append
  268. if ($exitCode -ne 0) {
  269. Write-Host "`nDatabase import failed!" -ForegroundColor Red
  270. Write-Host "Error output:" -ForegroundColor Yellow
  271. $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  272. Write-Host "`nFull log: $LOG_FILE" -ForegroundColor DarkGray
  273. } else {
  274. Write-Host "Database imported successfully!" -ForegroundColor Green
  275. if ($choice -eq 2) {
  276. Write-Host "`nRecommendation: Create an Admin role (power 100) and Admin user next." -ForegroundColor Cyan
  277. }
  278. $config.DEPLOYMENT_TYPE = if ($choice -eq 1) { "dev" } else { "clean" }
  279. Save-Config
  280. }
  281. Read-Host "`nPress Enter to continue"
  282. }
  283. function Manage-Users {
  284. while ($true) {
  285. $choice = Show-Menu "Manage Users & Roles" @(
  286. "Create a role",
  287. "Create a user",
  288. "Delete a role",
  289. "Delete a user",
  290. "Back to main menu"
  291. )
  292. switch ($choice) {
  293. 1 { Create-Role }
  294. 2 { Create-User }
  295. 3 { Delete-Role }
  296. 4 { Delete-User }
  297. 5 { return }
  298. }
  299. }
  300. }
  301. function Create-Role {
  302. Write-Host "`n=== Create Role ===" -ForegroundColor Cyan
  303. $name = Read-Host "Role name"
  304. $power = Read-Host "Power (1-100)"
  305. if ([string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($power)) {
  306. Write-Host "Name and power are required!" -ForegroundColor Red
  307. Read-Host "Press Enter to continue"
  308. return
  309. }
  310. if (-not ($power -match '^\d+$') -or [int]$power -lt 1 -or [int]$power -gt 100) {
  311. Write-Host "Power must be 1-100!" -ForegroundColor Red
  312. Read-Host "Press Enter to continue"
  313. return
  314. }
  315. $sql = "INSERT INTO roles (name, power) VALUES ('$name', $power);"
  316. $ErrorActionPreference = 'Continue'
  317. $output = $sql | podman exec -i $config.BEEPZONE_DB_CONTAINER_NAME mariadb -h $config.DB_HOST -P $config.DB_PORT -uroot -p"$($config.DB_ROOT_PASSWORD)" $config.DB_NAME 2>&1 | Where-Object { $_ -notmatch '^(mysql:|mariadb:)' }
  318. $ErrorActionPreference = 'Stop'
  319. $output | Out-File $LOG_FILE -Append
  320. if ($LASTEXITCODE -ne 0) {
  321. Write-Host "`nFailed to create role!" -ForegroundColor Red
  322. Write-Host "Error output:" -ForegroundColor Yellow
  323. $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  324. } else {
  325. Write-Host "Role '$name' created successfully!" -ForegroundColor Green
  326. }
  327. Read-Host "Press Enter to continue"
  328. }
  329. function Delete-Role {
  330. Write-Host "`nFetching roles..." -ForegroundColor Yellow
  331. $ErrorActionPreference = 'Continue'
  332. $roles = podman exec -i $config.BEEPZONE_DB_CONTAINER_NAME mariadb -h $config.DB_HOST -P $config.DB_PORT -uroot -p"$($config.DB_ROOT_PASSWORD)" $config.DB_NAME -e "SELECT id, name, power FROM roles ORDER BY power DESC;" -s -N 2>&1 | Where-Object { $_ -notmatch '^(mysql:|mariadb:)' }
  333. $ErrorActionPreference = 'Stop'
  334. if (-not $roles) {
  335. Write-Host "No roles found!" -ForegroundColor Red
  336. Read-Host "Press Enter to continue"
  337. return
  338. }
  339. Write-Host "`n=== Select Role to Delete ===" -ForegroundColor Cyan
  340. $roleList = @()
  341. $i = 1
  342. $roles | ForEach-Object {
  343. $parts = $_ -split "`t"
  344. Write-Host " [$i] $($parts[1]) (power: $($parts[2]))"
  345. $roleList += @{ Id = $parts[0]; Name = $parts[1]; Power = $parts[2] }
  346. $i++
  347. }
  348. $choice = Read-Host "`nSelect role (1-$($roleList.Count))"
  349. $selected = $roleList[[int]$choice - 1]
  350. $confirm = Read-Host "Delete role '$($selected.Name)'? (y/n)"
  351. if ($confirm -ne 'y') { return }
  352. $ErrorActionPreference = 'Continue'
  353. $output = "DELETE FROM roles WHERE id = $($selected.Id);" | podman exec -i $config.BEEPZONE_DB_CONTAINER_NAME mariadb -h $config.DB_HOST -P $config.DB_PORT -uroot -p"$($config.DB_ROOT_PASSWORD)" $config.DB_NAME 2>&1 | Where-Object { $_ -notmatch '^(mysql:|mariadb:)' }
  354. $ErrorActionPreference = 'Stop'
  355. $output | Out-File $LOG_FILE -Append
  356. if ($LASTEXITCODE -ne 0) {
  357. Write-Host "`nFailed to delete role!" -ForegroundColor Red
  358. Write-Host "Error output:" -ForegroundColor Yellow
  359. $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  360. } else {
  361. Write-Host "Role deleted!" -ForegroundColor Green
  362. }
  363. Read-Host "Press Enter to continue"
  364. }
  365. function Create-User {
  366. Write-Host "`nFetching roles..." -ForegroundColor Yellow
  367. $ErrorActionPreference = 'Continue'
  368. $roles = podman exec -i $config.BEEPZONE_DB_CONTAINER_NAME mariadb -h $config.DB_HOST -P $config.DB_PORT -uroot -p"$($config.DB_ROOT_PASSWORD)" $config.DB_NAME -e "SELECT id, name, power FROM roles ORDER BY power DESC;" -s -N 2>&1 | Where-Object { $_ -notmatch '^(mysql:|mariadb:)' }
  369. $ErrorActionPreference = 'Stop'
  370. if (-not $roles) {
  371. Write-Host "No roles found! Create a role first." -ForegroundColor Red
  372. Read-Host "Press Enter to continue"
  373. return
  374. }
  375. Write-Host "`n=== Select Role ===" -ForegroundColor Cyan
  376. $roleList = @()
  377. $i = 1
  378. $roles | ForEach-Object {
  379. $parts = $_ -split "`t"
  380. Write-Host " [$i] $($parts[1]) (power: $($parts[2]))"
  381. $roleList += @{ Id = $parts[0]; Name = $parts[1] }
  382. $i++
  383. }
  384. $roleChoice = Read-Host "`nSelect role (1-$($roleList.Count))"
  385. $selectedRole = $roleList[[int]$roleChoice - 1]
  386. Write-Host "`n=== Create User ===" -ForegroundColor Cyan
  387. $name = Read-Host "Full name"
  388. $username = Read-Host "Username"
  389. $password = Read-Host "Password" -AsSecureString
  390. $passwordPlain = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
  391. $email = Read-Host "Email (optional)"
  392. $phone = Read-Host "Phone (optional)"
  393. if ([string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($username) -or [string]::IsNullOrWhiteSpace($passwordPlain)) {
  394. Write-Host "Name, username, and password are required!" -ForegroundColor Red
  395. Read-Host "Press Enter to continue"
  396. return
  397. }
  398. # Hash password with bcrypt cost 12 using Rust (since cargo is already available)
  399. Write-Host "Hashing password..." -ForegroundColor Yellow
  400. $bcryptToolPath = Join-Path $env:TEMP "beepzone_bcrypt_tool.exe"
  401. # Build tiny bcrypt tool if not already built
  402. if (-not (Test-Path $bcryptToolPath)) {
  403. Write-Host "Building bcrypt tool (one-time setup)..." -ForegroundColor Yellow
  404. $tempDir = Join-Path $env:TEMP "bcrypt_hasher"
  405. New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
  406. # Create minimal Cargo.toml
  407. @"
  408. [package]
  409. name = "bcrypt_hasher"
  410. version = "0.1.0"
  411. edition = "2021"
  412. [dependencies]
  413. bcrypt = "0.15"
  414. "@ | Out-File (Join-Path $tempDir "Cargo.toml") -Encoding UTF8
  415. # Create main.rs
  416. @"
  417. use std::env;
  418. fn main() {
  419. let args: Vec<String> = env::args().collect();
  420. if args.len() != 2 {
  421. eprintln!("Usage: bcrypt_hasher <password>");
  422. std::process::exit(1);
  423. }
  424. match bcrypt::hash(&args[1], 12) {
  425. Ok(hash) => println!("{}", hash),
  426. Err(e) => {
  427. eprintln!("Error: {}", e);
  428. std::process::exit(1);
  429. }
  430. }
  431. }
  432. "@ | Out-File (Join-Path $tempDir "main.rs") -Encoding UTF8
  433. # Create src directory and move main.rs
  434. $srcDir = Join-Path $tempDir "src"
  435. New-Item -ItemType Directory -Force -Path $srcDir | Out-Null
  436. Move-Item (Join-Path $tempDir "main.rs") (Join-Path $srcDir "main.rs") -Force
  437. # Build it
  438. Push-Location $tempDir
  439. Write-Host " Compiling bcrypt tool..." -ForegroundColor DarkGray
  440. cargo build --release --quiet 2>&1 | Out-Null
  441. Pop-Location
  442. # Copy to temp location
  443. $builtExe = Join-Path $tempDir "target\release\bcrypt_hasher.exe"
  444. if (Test-Path $builtExe) {
  445. Copy-Item $builtExe $bcryptToolPath -Force
  446. Write-Host " Bcrypt tool ready!" -ForegroundColor Green
  447. } else {
  448. Write-Host " Failed to build bcrypt tool" -ForegroundColor Red
  449. }
  450. # Cleanup build directory
  451. Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
  452. }
  453. # Hash the password
  454. if (Test-Path $bcryptToolPath) {
  455. try {
  456. $passwordHash = & $bcryptToolPath $passwordPlain
  457. if ($LASTEXITCODE -eq 0 -and $passwordHash -match '^\$2[ab]\$12\$') {
  458. Write-Host "Password hashed successfully!" -ForegroundColor Green
  459. } else {
  460. Write-Host "`nError: Failed to hash password" -ForegroundColor Red
  461. Read-Host "Press Enter to continue"
  462. return
  463. }
  464. } catch {
  465. Write-Host "`nError: Failed to hash password: $_" -ForegroundColor Red
  466. Read-Host "Press Enter to continue"
  467. return
  468. }
  469. } else {
  470. Write-Host "`nError: Could not build bcrypt tool. Make sure cargo is working." -ForegroundColor Red
  471. Read-Host "Press Enter to continue"
  472. return
  473. }
  474. $emailVal = if ($email) { "'$email'" } else { "NULL" }
  475. $phoneVal = if ($phone) { "'$phone'" } else { "NULL" }
  476. $sql = "INSERT INTO users (name, username, password, role_id, email, phone, active) VALUES ('$name', '$username', '$passwordHash', $($selectedRole.Id), $emailVal, $phoneVal, 1);"
  477. $ErrorActionPreference = 'Continue'
  478. $output = $sql | podman exec -i $config.BEEPZONE_DB_CONTAINER_NAME mariadb -h $config.DB_HOST -P $config.DB_PORT -uroot -p"$($config.DB_ROOT_PASSWORD)" $config.DB_NAME 2>&1 | Where-Object { $_ -notmatch '^(mysql:|mariadb:)' }
  479. $ErrorActionPreference = 'Stop'
  480. $output | Out-File $LOG_FILE -Append
  481. if ($LASTEXITCODE -ne 0) {
  482. Write-Host "`nFailed to create user!" -ForegroundColor Red
  483. Write-Host "Error output:" -ForegroundColor Yellow
  484. $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  485. } else {
  486. Write-Host "User '$username' created successfully!" -ForegroundColor Green
  487. }
  488. Read-Host "Press Enter to continue"
  489. }
  490. function Delete-User {
  491. Write-Host "`nFetching users..." -ForegroundColor Yellow
  492. $ErrorActionPreference = 'Continue'
  493. $users = podman exec -i $config.BEEPZONE_DB_CONTAINER_NAME mariadb -h $config.DB_HOST -P $config.DB_PORT -uroot -p"$($config.DB_ROOT_PASSWORD)" $config.DB_NAME -e "SELECT id, username, name FROM users ORDER BY id;" -s -N 2>&1 | Where-Object { $_ -notmatch '^(mysql:|mariadb:)' }
  494. $ErrorActionPreference = 'Stop'
  495. if (-not $users) {
  496. Write-Host "No users found!" -ForegroundColor Red
  497. Read-Host "Press Enter to continue"
  498. return
  499. }
  500. Write-Host "`n=== Select User to Delete ===" -ForegroundColor Cyan
  501. $userList = @()
  502. $i = 1
  503. $users | ForEach-Object {
  504. $parts = $_ -split "`t"
  505. Write-Host " [$i] $($parts[1]) - $($parts[2])"
  506. $userList += @{ Id = $parts[0]; Username = $parts[1] }
  507. $i++
  508. }
  509. $choice = Read-Host "`nSelect user (1-$($userList.Count))"
  510. $selected = $userList[[int]$choice - 1]
  511. $confirm = Read-Host "Delete user '$($selected.Username)'? (y/n)"
  512. if ($confirm -ne 'y') { return }
  513. $ErrorActionPreference = 'Continue'
  514. $output = "DELETE FROM users WHERE id = $($selected.Id);" | podman exec -i $config.BEEPZONE_DB_CONTAINER_NAME mariadb -h $config.DB_HOST -P $config.DB_PORT -uroot -p"$($config.DB_ROOT_PASSWORD)" $config.DB_NAME 2>&1 | Where-Object { $_ -notmatch '^(mysql:|mariadb:)' }
  515. $ErrorActionPreference = 'Stop'
  516. $output | Out-File $LOG_FILE -Append
  517. if ($LASTEXITCODE -ne 0) {
  518. Write-Host "`nFailed to delete user!" -ForegroundColor Red
  519. Write-Host "Error output:" -ForegroundColor Yellow
  520. $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  521. } else {
  522. Write-Host "User deleted!" -ForegroundColor Green
  523. }
  524. Read-Host "Press Enter to continue"
  525. }
  526. function Setup-SeckelAPI {
  527. $sourcesDir = Join-Path $WORK_DIR "backend\seckelapi\sources"
  528. $configDir = Join-Path $WORK_DIR "backend\seckelapi\config"
  529. if (-not (Test-Path (Join-Path $sourcesDir ".git"))) {
  530. Write-Host "Cloning SeckelAPI repository..." -ForegroundColor Yellow
  531. $parentDir = Split-Path $sourcesDir -Parent
  532. New-Item -ItemType Directory -Force -Path $parentDir | Out-Null
  533. $success = Run-Command -Command "git" -Arguments @("clone", $config.SECKELAPI_REPO, $sourcesDir) `
  534. -SuccessMessage "Repository cloned successfully!" `
  535. -ErrorMessage "Failed to clone SeckelAPI repository" `
  536. -ShowOutput
  537. if (-not $success) {
  538. Read-Host "`nPress Enter to continue"
  539. return
  540. }
  541. }
  542. # Copy config files
  543. New-Item -ItemType Directory -Force -Path (Join-Path $sourcesDir "config") | Out-Null
  544. Get-ChildItem "$configDir\*.toml" | ForEach-Object {
  545. Copy-Item $_.FullName (Join-Path $sourcesDir "config\$($_.Name)") -Force
  546. }
  547. # Update config with database settings for container
  548. $basicsConfig = Join-Path $sourcesDir "config\basics.toml"
  549. if (Test-Path $basicsConfig) {
  550. Write-Host "Updating config for container deployment..." -ForegroundColor Yellow
  551. # Read line by line and only update values in [database] section
  552. $lines = Get-Content $basicsConfig
  553. $inDatabaseSection = $false
  554. $newLines = @()
  555. foreach ($line in $lines) {
  556. if ($line -match '^\[database\]') {
  557. $inDatabaseSection = $true
  558. $newLines += $line
  559. }
  560. elseif ($line -match '^\[.*\]') {
  561. # Entering a different section
  562. $inDatabaseSection = $false
  563. $newLines += $line
  564. }
  565. elseif ($inDatabaseSection) {
  566. # We're in [database] section, update relevant values
  567. if ($line -match '^host\s*=') {
  568. $newLines += 'host = "host.containers.internal"'
  569. }
  570. elseif ($line -match '^port\s*=') {
  571. $newLines += "port = $($config.DB_PORT)"
  572. }
  573. elseif ($line -match '^database\s*=') {
  574. $newLines += "database = `"$($config.DB_NAME)`""
  575. }
  576. elseif ($line -match '^username\s*=') {
  577. $newLines += "username = `"$($config.DB_USER)`""
  578. }
  579. elseif ($line -match '^password\s*=') {
  580. $newLines += "password = `"$($config.DB_PASS)`""
  581. }
  582. else {
  583. $newLines += $line
  584. }
  585. }
  586. else {
  587. $newLines += $line
  588. }
  589. }
  590. $newLines | Out-File $basicsConfig -Encoding UTF8
  591. Write-Host "Config updated!" -ForegroundColor Green
  592. }
  593. # Build and deploy container
  594. $choice = Show-Menu "SeckelAPI Container Build" @(
  595. "Build and run in podman container",
  596. "Skip for now"
  597. )
  598. if ($choice -eq 1) {
  599. $containerName = "beepzone-seckelapi"
  600. $imageName = "beepzone-seckelapi:latest"
  601. $containerfile = Join-Path $WORK_DIR "backend\seckelapi\Containerfile"
  602. # Stop and remove existing container
  603. $existing = podman ps -a --format "{{.Names}}" | Select-String -Pattern "^${containerName}$" -Quiet
  604. if ($existing) {
  605. Write-Host "Stopping existing container..." -ForegroundColor Yellow
  606. podman stop $containerName 2>&1 | Out-Null
  607. podman rm $containerName 2>&1 | Out-Null
  608. }
  609. # Build container
  610. Write-Host "`nBuilding SeckelAPI container..." -ForegroundColor Yellow
  611. $success = Run-Command -Command "podman" -Arguments @("build", "-t", $imageName, "-f", $containerfile, (Join-Path $WORK_DIR "backend\seckelapi")) `
  612. -SuccessMessage "Container image built successfully!" `
  613. -ErrorMessage "Container build failed!" `
  614. -LiveOutput
  615. if ($success) {
  616. $run = Read-Host "`nStart the container now? (y/n)"
  617. if ($run -eq 'y') {
  618. Write-Host "Starting container..." -ForegroundColor Yellow
  619. $success = Run-Command -Command "podman" -Arguments @("run", "-d", "--name", $containerName, "--add-host", "host.containers.internal:host-gateway", "-p", "5777:5777", $imageName) `
  620. -SuccessMessage "Container started successfully!" `
  621. -ErrorMessage "Failed to start container!"
  622. if ($success) {
  623. Write-Host "`nSeckelAPI is running at: http://localhost:5777" -ForegroundColor Cyan
  624. Write-Host "`nUseful commands:" -ForegroundColor DarkGray
  625. Write-Host " podman logs $containerName - View logs" -ForegroundColor DarkGray
  626. Write-Host " podman stop $containerName - Stop container" -ForegroundColor DarkGray
  627. Write-Host " podman start $containerName - Start container" -ForegroundColor DarkGray
  628. Write-Host " podman restart $containerName - Restart container" -ForegroundColor DarkGray
  629. }
  630. }
  631. }
  632. }
  633. Read-Host "`nPress Enter to continue"
  634. }
  635. function Build-DesktopClient {
  636. $sourcesDir = Join-Path $WORK_DIR "frontend\desktop-client\sources"
  637. if (-not (Test-Path (Join-Path $sourcesDir ".git"))) {
  638. Write-Host "Cloning client repository..." -ForegroundColor Yellow
  639. $parentDir = Split-Path $sourcesDir -Parent
  640. New-Item -ItemType Directory -Force -Path $parentDir | Out-Null
  641. $success = Run-Command -Command "git" -Arguments @("clone", $config.CLIENT_REPO, $sourcesDir) `
  642. -SuccessMessage "Repository cloned successfully!" `
  643. -ErrorMessage "Failed to clone client repository" `
  644. -ShowOutput
  645. if (-not $success) {
  646. Read-Host "`nPress Enter to continue"
  647. return
  648. }
  649. }
  650. $confirm = Read-Host "`nBuild BeepZone desktop client for Windows? (y/n)"
  651. if ($confirm -ne 'y') { return }
  652. Write-Host "Building desktop client..." -ForegroundColor Yellow
  653. Push-Location $sourcesDir
  654. $success = Run-Command -Command "cargo" -Arguments @("build", "--release") `
  655. -SuccessMessage "" `
  656. -ErrorMessage "Build failed!" `
  657. -LiveOutput
  658. Pop-Location
  659. if ($success) {
  660. Write-Host "`nBuild complete!" -ForegroundColor Green
  661. Write-Host "Binary: $sourcesDir\target\release\" -ForegroundColor Cyan
  662. }
  663. Read-Host "Press Enter to continue"
  664. }
  665. # Main
  666. Clear-Host
  667. Write-Host @"
  668. #########################################
  669. # BeepZone Setup Helper (Windows) #
  670. #########################################
  671. "@ -ForegroundColor Cyan
  672. Test-Dependencies
  673. while ($true) {
  674. $choice = Show-Menu "BeepZone Setup" @(
  675. "Configure & run MariaDB (podman)",
  676. "Import DB schema & data",
  677. "Manage users & roles",
  678. "Configure & setup SeckelAPI",
  679. "Build desktop client",
  680. "Quit"
  681. )
  682. switch ($choice) {
  683. 1 { Configure-Database }
  684. 2 { Import-Database }
  685. 3 { Manage-Users }
  686. 4 { Setup-SeckelAPI }
  687. 5 { Build-DesktopClient }
  688. 6 { exit 0 }
  689. }
  690. }