1
0

beepzone-helper.ps1 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  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. # First check if already in PATH
  144. foreach ($cmd in @("mysql", "mariadb")) {
  145. if (Get-Command $cmd -ErrorAction SilentlyContinue) {
  146. $mysqlFound = $true
  147. break
  148. }
  149. }
  150. # If not in PATH, check common Windows installation locations
  151. if (-not $mysqlFound) {
  152. $searchPaths = @(
  153. "C:\Program Files\MariaDB*\bin\mysql.exe",
  154. "C:\Program Files\MariaDB*\bin\mariadb.exe",
  155. "C:\Program Files\MySQL\MySQL Server*\bin\mysql.exe",
  156. "C:\Program Files (x86)\MariaDB*\bin\mysql.exe",
  157. "C:\Program Files (x86)\MySQL\MySQL Server*\bin\mysql.exe"
  158. )
  159. foreach ($pattern in $searchPaths) {
  160. $found = Get-ChildItem $pattern -ErrorAction SilentlyContinue | Select-Object -First 1
  161. if ($found) {
  162. $mysqlFound = $true
  163. # Add to PATH for this session
  164. $binDir = Split-Path $found.FullName
  165. $env:Path = "$binDir;$env:Path"
  166. Write-Host "Found MySQL/MariaDB at: $($found.FullName)" -ForegroundColor Green
  167. break
  168. }
  169. }
  170. }
  171. if (-not $mysqlFound) {
  172. $missing += "MySQL/MariaDB client"
  173. }
  174. if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
  175. $missing += "Git"
  176. }
  177. if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
  178. $missing += "Rust/Cargo (https://rustup.rs)"
  179. }
  180. if ($missing.Count -gt 0) {
  181. Write-Host "`nMissing dependencies:" -ForegroundColor Red
  182. $missing | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow }
  183. Write-Host "`nPlease install the missing tools and try again.`n"
  184. exit 1
  185. }
  186. }
  187. function Configure-Database {
  188. Write-Host "`n=== MariaDB Container Configuration ===" -ForegroundColor Cyan
  189. $config.DB_HOST = Read-Host "DB Host [$($config.DB_HOST)]"
  190. if ([string]::IsNullOrWhiteSpace($config.DB_HOST)) { $config.DB_HOST = "127.0.0.1" }
  191. $port = Read-Host "DB Port [$($config.DB_PORT)]"
  192. if ($port) { $config.DB_PORT = $port }
  193. $name = Read-Host "DB Name [$($config.DB_NAME)]"
  194. if ($name) { $config.DB_NAME = $name }
  195. $user = Read-Host "DB User [$($config.DB_USER)]"
  196. if ($user) { $config.DB_USER = $user }
  197. $pass = Read-Host "DB Password [$($config.DB_PASS)]"
  198. if ($pass) { $config.DB_PASS = $pass }
  199. $rootPass = Read-Host "Root Password [$($config.DB_ROOT_PASSWORD)]"
  200. if ($rootPass) { $config.DB_ROOT_PASSWORD = $rootPass }
  201. Save-Config
  202. # Check if container exists
  203. $exists = podman ps -a --format "{{.Names}}" | Select-String -Pattern "^$($config.BEEPZONE_DB_CONTAINER_NAME)$" -Quiet
  204. if ($exists) {
  205. $restart = Read-Host "`nContainer '$($config.BEEPZONE_DB_CONTAINER_NAME)' exists. Start/restart it? (y/n)"
  206. if ($restart -eq 'y') {
  207. $null = Run-Command -Command "podman" -Arguments @("start", $config.BEEPZONE_DB_CONTAINER_NAME) `
  208. -SuccessMessage "Container started successfully!" `
  209. -ErrorMessage "Failed to start container"
  210. }
  211. return
  212. }
  213. # Create new container
  214. Write-Host "`nCreating new MariaDB container..." -ForegroundColor Yellow
  215. $cmd = @(
  216. "run", "-d",
  217. "--name", $config.BEEPZONE_DB_CONTAINER_NAME,
  218. "-e", "MARIADB_ROOT_PASSWORD=$($config.DB_ROOT_PASSWORD)",
  219. "-e", "MARIADB_DATABASE=$($config.DB_NAME)",
  220. "-e", "MARIADB_USER=$($config.DB_USER)",
  221. "-e", "MARIADB_PASSWORD=$($config.DB_PASS)",
  222. "-p", "$($config.DB_PORT):3306",
  223. $config.BEEPZONE_DB_IMAGE
  224. )
  225. $success = Run-Command -Command "podman" -Arguments $cmd `
  226. -SuccessMessage "MariaDB container started successfully!" `
  227. -ErrorMessage "Failed to start MariaDB container"
  228. if (-not $success) {
  229. Read-Host "`nPress Enter to continue"
  230. }
  231. }
  232. function Import-Database {
  233. $schemaFile = Join-Path $WORK_DIR "backend\database\schema\beepzone-schema-dump.sql"
  234. $fullDumpFile = Join-Path $WORK_DIR "backend\database\dev\beepzone-full-dump.sql"
  235. $choice = Show-Menu "Database Import" @(
  236. "Full dump (schema + data)",
  237. "Clean schema only (no data)"
  238. )
  239. $file = if ($choice -eq 1) { $fullDumpFile } else { $schemaFile }
  240. if (-not (Test-Path $file)) {
  241. Write-Host "File not found: $file" -ForegroundColor Red
  242. Read-Host "Press Enter to continue"
  243. return
  244. }
  245. $confirm = Read-Host "`nThis will DROP and recreate the database. Continue? (y/n)"
  246. if ($confirm -ne 'y') { return }
  247. Write-Host "Importing database..." -ForegroundColor Yellow
  248. $sql = @"
  249. DROP DATABASE IF EXISTS ``$($config.DB_NAME)``;
  250. CREATE DATABASE ``$($config.DB_NAME)`` CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci;
  251. USE ``$($config.DB_NAME)``;
  252. SET FOREIGN_KEY_CHECKS=0;
  253. $(Get-Content $file -Raw)
  254. SET FOREIGN_KEY_CHECKS=1;
  255. "@
  256. # Import via podman exec
  257. Write-Host "Importing SQL..." -ForegroundColor Yellow
  258. $tempFile = New-TemporaryFile
  259. $sql | Out-File $tempFile.FullName -Encoding UTF8
  260. $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)`""
  261. $output = cmd /c $importCmd 2`>`&1
  262. $exitCode = $LASTEXITCODE
  263. Remove-Item $tempFile -ErrorAction SilentlyContinue
  264. $output | Out-File $LOG_FILE -Append
  265. if ($exitCode -ne 0) {
  266. Write-Host "`nDatabase import failed!" -ForegroundColor Red
  267. Write-Host "Error output:" -ForegroundColor Yellow
  268. $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  269. Write-Host "`nFull log: $LOG_FILE" -ForegroundColor DarkGray
  270. } else {
  271. Write-Host "Database imported successfully!" -ForegroundColor Green
  272. if ($choice -eq 2) {
  273. Write-Host "`nRecommendation: Create an Admin role (power 100) and Admin user next." -ForegroundColor Cyan
  274. }
  275. $config.DEPLOYMENT_TYPE = if ($choice -eq 1) { "dev" } else { "clean" }
  276. Save-Config
  277. }
  278. Read-Host "`nPress Enter to continue"
  279. }
  280. function Manage-Users {
  281. while ($true) {
  282. $choice = Show-Menu "Manage Users & Roles" @(
  283. "Create a role",
  284. "Create a user",
  285. "Delete a role",
  286. "Delete a user",
  287. "Back to main menu"
  288. )
  289. switch ($choice) {
  290. 1 { Create-Role }
  291. 2 { Create-User }
  292. 3 { Delete-Role }
  293. 4 { Delete-User }
  294. 5 { return }
  295. }
  296. }
  297. }
  298. function Create-Role {
  299. Write-Host "`n=== Create Role ===" -ForegroundColor Cyan
  300. $name = Read-Host "Role name"
  301. $power = Read-Host "Power (1-100)"
  302. if ([string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($power)) {
  303. Write-Host "Name and power are required!" -ForegroundColor Red
  304. Read-Host "Press Enter to continue"
  305. return
  306. }
  307. if (-not ($power -match '^\d+$') -or [int]$power -lt 1 -or [int]$power -gt 100) {
  308. Write-Host "Power must be 1-100!" -ForegroundColor Red
  309. Read-Host "Press Enter to continue"
  310. return
  311. }
  312. $sql = "INSERT INTO roles (name, power) VALUES ('$name', $power);"
  313. $ErrorActionPreference = 'Continue'
  314. $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:)' }
  315. $ErrorActionPreference = 'Stop'
  316. $output | Out-File $LOG_FILE -Append
  317. if ($LASTEXITCODE -ne 0) {
  318. Write-Host "`nFailed to create role!" -ForegroundColor Red
  319. Write-Host "Error output:" -ForegroundColor Yellow
  320. $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  321. } else {
  322. Write-Host "Role '$name' created successfully!" -ForegroundColor Green
  323. }
  324. Read-Host "Press Enter to continue"
  325. }
  326. function Delete-Role {
  327. Write-Host "`nFetching roles..." -ForegroundColor Yellow
  328. $ErrorActionPreference = 'Continue'
  329. $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:)' }
  330. $ErrorActionPreference = 'Stop'
  331. if (-not $roles) {
  332. Write-Host "No roles found!" -ForegroundColor Red
  333. Read-Host "Press Enter to continue"
  334. return
  335. }
  336. Write-Host "`n=== Select Role to Delete ===" -ForegroundColor Cyan
  337. $roleList = @()
  338. $i = 1
  339. $roles | ForEach-Object {
  340. $parts = $_ -split "`t"
  341. Write-Host " [$i] $($parts[1]) (power: $($parts[2]))"
  342. $roleList += @{ Id = $parts[0]; Name = $parts[1]; Power = $parts[2] }
  343. $i++
  344. }
  345. $choice = Read-Host "`nSelect role (1-$($roleList.Count))"
  346. $selected = $roleList[[int]$choice - 1]
  347. $confirm = Read-Host "Delete role '$($selected.Name)'? (y/n)"
  348. if ($confirm -ne 'y') { return }
  349. $ErrorActionPreference = 'Continue'
  350. $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:)' }
  351. $ErrorActionPreference = 'Stop'
  352. $output | Out-File $LOG_FILE -Append
  353. if ($LASTEXITCODE -ne 0) {
  354. Write-Host "`nFailed to delete role!" -ForegroundColor Red
  355. Write-Host "Error output:" -ForegroundColor Yellow
  356. $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  357. } else {
  358. Write-Host "Role deleted!" -ForegroundColor Green
  359. }
  360. Read-Host "Press Enter to continue"
  361. }
  362. function Create-User {
  363. Write-Host "`nFetching roles..." -ForegroundColor Yellow
  364. $ErrorActionPreference = 'Continue'
  365. $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:)' }
  366. $ErrorActionPreference = 'Stop'
  367. if (-not $roles) {
  368. Write-Host "No roles found! Create a role first." -ForegroundColor Red
  369. Read-Host "Press Enter to continue"
  370. return
  371. }
  372. Write-Host "`n=== Select Role ===" -ForegroundColor Cyan
  373. $roleList = @()
  374. $i = 1
  375. $roles | ForEach-Object {
  376. $parts = $_ -split "`t"
  377. Write-Host " [$i] $($parts[1]) (power: $($parts[2]))"
  378. $roleList += @{ Id = $parts[0]; Name = $parts[1] }
  379. $i++
  380. }
  381. $roleChoice = Read-Host "`nSelect role (1-$($roleList.Count))"
  382. $selectedRole = $roleList[[int]$roleChoice - 1]
  383. Write-Host "`n=== Create User ===" -ForegroundColor Cyan
  384. $name = Read-Host "Full name"
  385. $username = Read-Host "Username"
  386. $password = Read-Host "Password" -AsSecureString
  387. $passwordPlain = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
  388. $email = Read-Host "Email (optional)"
  389. $phone = Read-Host "Phone (optional)"
  390. if ([string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($username) -or [string]::IsNullOrWhiteSpace($passwordPlain)) {
  391. Write-Host "Name, username, and password are required!" -ForegroundColor Red
  392. Read-Host "Press Enter to continue"
  393. return
  394. }
  395. # Hash password with bcrypt cost 12 using Rust (since cargo is already available)
  396. Write-Host "Hashing password..." -ForegroundColor Yellow
  397. $bcryptToolPath = Join-Path $env:TEMP "beepzone_bcrypt_tool.exe"
  398. # Build tiny bcrypt tool if not already built
  399. if (-not (Test-Path $bcryptToolPath)) {
  400. Write-Host "Building bcrypt tool (one-time setup)..." -ForegroundColor Yellow
  401. $tempDir = Join-Path $env:TEMP "bcrypt_hasher"
  402. New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
  403. # Create minimal Cargo.toml
  404. @"
  405. [package]
  406. name = "bcrypt_hasher"
  407. version = "0.1.0"
  408. edition = "2021"
  409. [dependencies]
  410. bcrypt = "0.15"
  411. "@ | Out-File (Join-Path $tempDir "Cargo.toml") -Encoding UTF8
  412. # Create main.rs
  413. @"
  414. use std::env;
  415. fn main() {
  416. let args: Vec<String> = env::args().collect();
  417. if args.len() != 2 {
  418. eprintln!("Usage: bcrypt_hasher <password>");
  419. std::process::exit(1);
  420. }
  421. match bcrypt::hash(&args[1], 12) {
  422. Ok(hash) => println!("{}", hash),
  423. Err(e) => {
  424. eprintln!("Error: {}", e);
  425. std::process::exit(1);
  426. }
  427. }
  428. }
  429. "@ | Out-File (Join-Path $tempDir "main.rs") -Encoding UTF8
  430. # Create src directory and move main.rs
  431. $srcDir = Join-Path $tempDir "src"
  432. New-Item -ItemType Directory -Force -Path $srcDir | Out-Null
  433. Move-Item (Join-Path $tempDir "main.rs") (Join-Path $srcDir "main.rs") -Force
  434. # Build it
  435. Push-Location $tempDir
  436. Write-Host " Compiling bcrypt tool..." -ForegroundColor DarkGray
  437. cargo build --release --quiet 2>&1 | Out-Null
  438. Pop-Location
  439. # Copy to temp location
  440. $builtExe = Join-Path $tempDir "target\release\bcrypt_hasher.exe"
  441. if (Test-Path $builtExe) {
  442. Copy-Item $builtExe $bcryptToolPath -Force
  443. Write-Host " Bcrypt tool ready!" -ForegroundColor Green
  444. } else {
  445. Write-Host " Failed to build bcrypt tool" -ForegroundColor Red
  446. }
  447. # Cleanup build directory
  448. Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
  449. }
  450. # Hash the password
  451. if (Test-Path $bcryptToolPath) {
  452. try {
  453. $passwordHash = & $bcryptToolPath $passwordPlain
  454. if ($LASTEXITCODE -eq 0 -and $passwordHash -match '^\$2[ab]\$12\$') {
  455. Write-Host "Password hashed successfully!" -ForegroundColor Green
  456. } else {
  457. Write-Host "`nError: Failed to hash password" -ForegroundColor Red
  458. Read-Host "Press Enter to continue"
  459. return
  460. }
  461. } catch {
  462. Write-Host "`nError: Failed to hash password: $_" -ForegroundColor Red
  463. Read-Host "Press Enter to continue"
  464. return
  465. }
  466. } else {
  467. Write-Host "`nError: Could not build bcrypt tool. Make sure cargo is working." -ForegroundColor Red
  468. Read-Host "Press Enter to continue"
  469. return
  470. }
  471. $emailVal = if ($email) { "'$email'" } else { "NULL" }
  472. $phoneVal = if ($phone) { "'$phone'" } else { "NULL" }
  473. $sql = "INSERT INTO users (name, username, password, role_id, email, phone, active) VALUES ('$name', '$username', '$passwordHash', $($selectedRole.Id), $emailVal, $phoneVal, 1);"
  474. $ErrorActionPreference = 'Continue'
  475. $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:)' }
  476. $ErrorActionPreference = 'Stop'
  477. $output | Out-File $LOG_FILE -Append
  478. if ($LASTEXITCODE -ne 0) {
  479. Write-Host "`nFailed to create user!" -ForegroundColor Red
  480. Write-Host "Error output:" -ForegroundColor Yellow
  481. $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  482. } else {
  483. Write-Host "User '$username' created successfully!" -ForegroundColor Green
  484. }
  485. Read-Host "Press Enter to continue"
  486. }
  487. function Delete-User {
  488. Write-Host "`nFetching users..." -ForegroundColor Yellow
  489. $ErrorActionPreference = 'Continue'
  490. $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:)' }
  491. $ErrorActionPreference = 'Stop'
  492. if (-not $users) {
  493. Write-Host "No users found!" -ForegroundColor Red
  494. Read-Host "Press Enter to continue"
  495. return
  496. }
  497. Write-Host "`n=== Select User to Delete ===" -ForegroundColor Cyan
  498. $userList = @()
  499. $i = 1
  500. $users | ForEach-Object {
  501. $parts = $_ -split "`t"
  502. Write-Host " [$i] $($parts[1]) - $($parts[2])"
  503. $userList += @{ Id = $parts[0]; Username = $parts[1] }
  504. $i++
  505. }
  506. $choice = Read-Host "`nSelect user (1-$($userList.Count))"
  507. $selected = $userList[[int]$choice - 1]
  508. $confirm = Read-Host "Delete user '$($selected.Username)'? (y/n)"
  509. if ($confirm -ne 'y') { return }
  510. $ErrorActionPreference = 'Continue'
  511. $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:)' }
  512. $ErrorActionPreference = 'Stop'
  513. $output | Out-File $LOG_FILE -Append
  514. if ($LASTEXITCODE -ne 0) {
  515. Write-Host "`nFailed to delete user!" -ForegroundColor Red
  516. Write-Host "Error output:" -ForegroundColor Yellow
  517. $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
  518. } else {
  519. Write-Host "User deleted!" -ForegroundColor Green
  520. }
  521. Read-Host "Press Enter to continue"
  522. }
  523. function Setup-SeckelAPI {
  524. $sourcesDir = Join-Path $WORK_DIR "backend\seckelapi\sources"
  525. $configDir = Join-Path $WORK_DIR "backend\seckelapi\config"
  526. if (-not (Test-Path (Join-Path $sourcesDir ".git"))) {
  527. Write-Host "Cloning SeckelAPI repository..." -ForegroundColor Yellow
  528. $parentDir = Split-Path $sourcesDir -Parent
  529. New-Item -ItemType Directory -Force -Path $parentDir | Out-Null
  530. $success = Run-Command -Command "git" -Arguments @("clone", $config.SECKELAPI_REPO, $sourcesDir) `
  531. -SuccessMessage "Repository cloned successfully!" `
  532. -ErrorMessage "Failed to clone SeckelAPI repository" `
  533. -ShowOutput
  534. if (-not $success) {
  535. Read-Host "`nPress Enter to continue"
  536. return
  537. }
  538. }
  539. # Copy config files
  540. New-Item -ItemType Directory -Force -Path (Join-Path $sourcesDir "config") | Out-Null
  541. Get-ChildItem "$configDir\*.toml" | ForEach-Object {
  542. Copy-Item $_.FullName (Join-Path $sourcesDir "config\$($_.Name)") -Force
  543. }
  544. # Update config with database settings for container
  545. $basicsConfig = Join-Path $sourcesDir "config\basics.toml"
  546. if (Test-Path $basicsConfig) {
  547. Write-Host "Updating config for container deployment..." -ForegroundColor Yellow
  548. # Read line by line and only update values in [database] section
  549. $lines = Get-Content $basicsConfig
  550. $inDatabaseSection = $false
  551. $newLines = @()
  552. foreach ($line in $lines) {
  553. if ($line -match '^\[database\]') {
  554. $inDatabaseSection = $true
  555. $newLines += $line
  556. }
  557. elseif ($line -match '^\[.*\]') {
  558. # Entering a different section
  559. $inDatabaseSection = $false
  560. $newLines += $line
  561. }
  562. elseif ($inDatabaseSection) {
  563. # We're in [database] section, update relevant values
  564. if ($line -match '^host\s*=') {
  565. $newLines += 'host = "host.containers.internal"'
  566. }
  567. elseif ($line -match '^port\s*=') {
  568. $newLines += "port = $($config.DB_PORT)"
  569. }
  570. elseif ($line -match '^database\s*=') {
  571. $newLines += "database = `"$($config.DB_NAME)`""
  572. }
  573. elseif ($line -match '^username\s*=') {
  574. $newLines += "username = `"$($config.DB_USER)`""
  575. }
  576. elseif ($line -match '^password\s*=') {
  577. $newLines += "password = `"$($config.DB_PASS)`""
  578. }
  579. else {
  580. $newLines += $line
  581. }
  582. }
  583. else {
  584. $newLines += $line
  585. }
  586. }
  587. $newLines | Out-File $basicsConfig -Encoding UTF8
  588. Write-Host "Config updated!" -ForegroundColor Green
  589. }
  590. # Build and deploy container
  591. $choice = Show-Menu "SeckelAPI Container Build" @(
  592. "Build and run in podman container",
  593. "Skip for now"
  594. )
  595. if ($choice -eq 1) {
  596. $containerName = "beepzone-seckelapi"
  597. $imageName = "beepzone-seckelapi:latest"
  598. $containerfile = Join-Path $WORK_DIR "backend\seckelapi\Containerfile"
  599. # Stop and remove existing container
  600. $existing = podman ps -a --format "{{.Names}}" | Select-String -Pattern "^${containerName}$" -Quiet
  601. if ($existing) {
  602. Write-Host "Stopping existing container..." -ForegroundColor Yellow
  603. podman stop $containerName 2>&1 | Out-Null
  604. podman rm $containerName 2>&1 | Out-Null
  605. }
  606. # Build container
  607. Write-Host "`nBuilding SeckelAPI container..." -ForegroundColor Yellow
  608. $success = Run-Command -Command "podman" -Arguments @("build", "-t", $imageName, "-f", $containerfile, (Join-Path $WORK_DIR "backend\seckelapi")) `
  609. -SuccessMessage "Container image built successfully!" `
  610. -ErrorMessage "Container build failed!" `
  611. -LiveOutput
  612. if ($success) {
  613. $run = Read-Host "`nStart the container now? (y/n)"
  614. if ($run -eq 'y') {
  615. Write-Host "Starting container..." -ForegroundColor Yellow
  616. $success = Run-Command -Command "podman" -Arguments @("run", "-d", "--name", $containerName, "--add-host", "host.containers.internal:host-gateway", "-p", "5777:5777", $imageName) `
  617. -SuccessMessage "Container started successfully!" `
  618. -ErrorMessage "Failed to start container!"
  619. if ($success) {
  620. Write-Host "`nSeckelAPI is running at: http://localhost:5777" -ForegroundColor Cyan
  621. Write-Host "`nUseful commands:" -ForegroundColor DarkGray
  622. Write-Host " podman logs $containerName - View logs" -ForegroundColor DarkGray
  623. Write-Host " podman stop $containerName - Stop container" -ForegroundColor DarkGray
  624. Write-Host " podman start $containerName - Start container" -ForegroundColor DarkGray
  625. Write-Host " podman restart $containerName - Restart container" -ForegroundColor DarkGray
  626. }
  627. }
  628. }
  629. }
  630. Read-Host "`nPress Enter to continue"
  631. }
  632. function Clean-Sources {
  633. Write-Host "`n=== Clean Sources ==="-ForegroundColor Cyan
  634. Write-Host "This will delete all sources directories including hidden files." -ForegroundColor Yellow
  635. Write-Host " - backend\seckelapi\sources" -ForegroundColor Yellow
  636. Write-Host " - frontend\desktop-client\sources" -ForegroundColor Yellow
  637. $confirm = Read-Host "`nAre you sure you want to clean all sources? (y/n)"
  638. if ($confirm -ne 'y') {
  639. Write-Host "Cancelled." -ForegroundColor Gray
  640. Read-Host "Press Enter to continue"
  641. return
  642. }
  643. $seckelapiSources = Join-Path $WORK_DIR "backend\seckelapi\sources"
  644. $clientSources = Join-Path $WORK_DIR "frontend\desktop-client\sources"
  645. # Clean SeckelAPI sources
  646. if (Test-Path $seckelapiSources) {
  647. Write-Host "`nRemoving SeckelAPI sources contents..." -ForegroundColor Yellow
  648. Get-ChildItem -Path $seckelapiSources -Force | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
  649. Write-Host " SeckelAPI sources cleaned" -ForegroundColor Green
  650. } else {
  651. Write-Host " SeckelAPI sources folder not found" -ForegroundColor Gray
  652. }
  653. # Clean desktop client sources
  654. if (Test-Path $clientSources) {
  655. Write-Host "Removing desktop client sources contents..." -ForegroundColor Yellow
  656. Get-ChildItem -Path $clientSources -Force | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
  657. Write-Host " Desktop client sources cleaned" -ForegroundColor Green
  658. } else {
  659. Write-Host " Desktop client sources folder not found" -ForegroundColor Gray
  660. }
  661. Write-Host "`nAll sources cleaned!" -ForegroundColor Green
  662. # Ask to re-clone
  663. $reclone = Read-Host "`nDo you want to pull fresh sources now? (y/n)"
  664. if ($reclone -eq 'y') {
  665. Write-Host "`nCloning SeckelAPI..." -ForegroundColor Yellow
  666. $parentDir = Split-Path $seckelapiSources -Parent
  667. New-Item -ItemType Directory -Force -Path $parentDir | Out-Null
  668. $success = Run-Command -Command "git" -Arguments @("clone", $config.SECKELAPI_REPO, $seckelapiSources) `
  669. -SuccessMessage "SeckelAPI cloned successfully!" `
  670. -ErrorMessage "Failed to clone SeckelAPI" `
  671. -ShowOutput
  672. Write-Host "`nCloning desktop client..." -ForegroundColor Yellow
  673. $parentDir = Split-Path $clientSources -Parent
  674. New-Item -ItemType Directory -Force -Path $parentDir | Out-Null
  675. $success = Run-Command -Command "git" -Arguments @("clone", $config.CLIENT_REPO, $clientSources) `
  676. -SuccessMessage "Desktop client cloned successfully!" `
  677. -ErrorMessage "Failed to clone desktop client" `
  678. -ShowOutput
  679. Write-Host "`nAll sources pulled fresh!" -ForegroundColor Green
  680. }
  681. Read-Host "`nPress Enter to continue"
  682. }
  683. function Build-DesktopClient {
  684. $sourcesDir = Join-Path $WORK_DIR "frontend\desktop-client\sources"
  685. if (-not (Test-Path (Join-Path $sourcesDir ".git"))) {
  686. Write-Host "Cloning client repository..." -ForegroundColor Yellow
  687. $parentDir = Split-Path $sourcesDir -Parent
  688. New-Item -ItemType Directory -Force -Path $parentDir | Out-Null
  689. $success = Run-Command -Command "git" -Arguments @("clone", $config.CLIENT_REPO, $sourcesDir) `
  690. -SuccessMessage "Repository cloned successfully!" `
  691. -ErrorMessage "Failed to clone client repository" `
  692. -ShowOutput
  693. if (-not $success) {
  694. Read-Host "`nPress Enter to continue"
  695. return
  696. }
  697. }
  698. $confirm = Read-Host "`nBuild BeepZone desktop client for Windows? (y/n)"
  699. if ($confirm -ne 'y') { return }
  700. Write-Host "Building desktop client..." -ForegroundColor Yellow
  701. Push-Location $sourcesDir
  702. $success = Run-Command -Command "cargo" -Arguments @("build", "--release") `
  703. -SuccessMessage "" `
  704. -ErrorMessage "Build failed!" `
  705. -LiveOutput
  706. Pop-Location
  707. if ($success) {
  708. Write-Host "`nBuild complete!" -ForegroundColor Green
  709. Write-Host "Binary: $sourcesDir\target\release\" -ForegroundColor Cyan
  710. }
  711. Read-Host "Press Enter to continue"
  712. }
  713. # Main
  714. Clear-Host
  715. Write-Host @"
  716. #########################################
  717. # BeepZone Setup Helper (Windows) #
  718. #########################################
  719. "@ -ForegroundColor Cyan
  720. Test-Dependencies
  721. while ($true) {
  722. $choice = Show-Menu "BeepZone Setup" @(
  723. "Configure & run MariaDB (podman)",
  724. "Import DB schema & data",
  725. "Manage users & roles",
  726. "Configure & setup SeckelAPI",
  727. "Build desktop client",
  728. "Clean sources",
  729. "Quit"
  730. )
  731. switch ($choice) {
  732. 1 { Configure-Database }
  733. 2 { Import-Database }
  734. 3 { Manage-Users }
  735. 4 { Setup-SeckelAPI }
  736. 5 { Build-DesktopClient }
  737. 6 { Clean-Sources }
  738. 7 { exit 0 }
  739. }
  740. }