aboutsummaryrefslogtreecommitdiff
path: root/beepzone-helper.ps1
diff options
context:
space:
mode:
authorcrt <crt@teleco.ch>2025-12-13 22:08:54 +0100
committercrt <crt@teleco.ch>2025-12-13 22:08:54 +0100
commit8f4e7ae7e0fffa27b22c17e94a57ec6ecb28f96d (patch)
tree94ae93079e4299d33de7b75f2e7408710b8e91b6 /beepzone-helper.ps1
parentff187ef944b0aacb379eb0dbec2d185633cf3b71 (diff)
made it somewhat run on windows kinda
Diffstat (limited to 'beepzone-helper.ps1')
-rw-r--r--beepzone-helper.ps1822
1 files changed, 822 insertions, 0 deletions
diff --git a/beepzone-helper.ps1 b/beepzone-helper.ps1
new file mode 100644
index 0000000..ffdd523
--- /dev/null
+++ b/beepzone-helper.ps1
@@ -0,0 +1,822 @@
+# BeepZone Setup Helper for Windows
+# PowerShell version - no WSL needed
+
+$ErrorActionPreference = "Stop"
+$SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path
+$WORK_DIR = $SCRIPT_DIR
+$LOG_FILE = "$env:TEMP\beepzone-helper.log"
+$ENV_FILE = Join-Path $SCRIPT_DIR ".env"
+
+# Initialize log
+"" | Out-File $LOG_FILE -Force
+
+# Default configuration
+$config = @{
+ BEEPZONE_DB_CONTAINER_NAME = "beepzone-mariadb"
+ BEEPZONE_DB_IMAGE = "mariadb:12"
+ DB_HOST = "127.0.0.1"
+ DB_PORT = "3306"
+ DB_NAME = "beepzone"
+ DB_USER = "beepzone"
+ DB_PASS = "beepzone"
+ DB_ROOT_PASSWORD = "root"
+ SECKELAPI_REPO = "https://git.teleco.ch/crt/seckelapi.git"
+ CLIENT_REPO = "https://git.teleco.ch/crt/beepzone-client-egui-emo.git"
+ DEPLOYMENT_TYPE = "clean"
+}
+
+# Load existing .env if present
+if (Test-Path $ENV_FILE) {
+ Get-Content $ENV_FILE | ForEach-Object {
+ if ($_ -match '^([^=]+)="?([^"]*)"?$') {
+ $config[$matches[1]] = $matches[2]
+ }
+ }
+}
+
+function Save-Config {
+ @"
+# BeepZone Setup Configuration
+# Auto-generated by beepzone-helper.ps1
+
+BEEPZONE_DB_CONTAINER_NAME="$($config.BEEPZONE_DB_CONTAINER_NAME)"
+BEEPZONE_DB_IMAGE="$($config.BEEPZONE_DB_IMAGE)"
+DB_HOST="$($config.DB_HOST)"
+DB_PORT="$($config.DB_PORT)"
+DB_NAME="$($config.DB_NAME)"
+DB_USER="$($config.DB_USER)"
+DB_PASS="$($config.DB_PASS)"
+DB_ROOT_PASSWORD="$($config.DB_ROOT_PASSWORD)"
+SECKELAPI_REPO="$($config.SECKELAPI_REPO)"
+CLIENT_REPO="$($config.CLIENT_REPO)"
+DEPLOYMENT_TYPE="$($config.DEPLOYMENT_TYPE)"
+"@ | Out-File $ENV_FILE -Encoding UTF8
+}
+
+function Show-Menu {
+ param([string]$Title, [array]$Options)
+
+ Write-Host "`n=== $Title ===" -ForegroundColor Cyan
+ for ($i = 0; $i -lt $Options.Count; $i++) {
+ Write-Host " [$($i+1)] $($Options[$i])"
+ }
+ Write-Host ""
+ $choice = Read-Host "Choose an option (1-$($Options.Count))"
+ return [int]$choice
+}
+
+function Run-Command {
+ param(
+ [string]$Command,
+ [array]$Arguments,
+ [string]$SuccessMessage,
+ [string]$ErrorMessage,
+ [switch]$ShowOutput,
+ [switch]$LiveOutput
+ )
+
+ Write-Host "Running: $Command $($Arguments -join ' ')" -ForegroundColor DarkGray
+ Write-Host ""
+
+ if ($LiveOutput) {
+ # Show output in real-time for long-running commands
+ $process = Start-Process -FilePath $Command -ArgumentList $Arguments -NoNewWindow -Wait -PassThru
+ $exitCode = $process.ExitCode
+
+ # Log the command execution
+ "$Command $($Arguments -join ' ')" | Out-File $LOG_FILE -Append
+ "Exit Code: $exitCode" | Out-File $LOG_FILE -Append
+
+ Write-Host ""
+ if ($exitCode -ne 0) {
+ Write-Host "$ErrorMessage (Exit Code: $exitCode)" -ForegroundColor Red
+ Write-Host "Full log: $LOG_FILE" -ForegroundColor DarkGray
+ return $false
+ } else {
+ if ($SuccessMessage) {
+ Write-Host $SuccessMessage -ForegroundColor Green
+ }
+ return $true
+ }
+ } else {
+ # Capture output for commands where we want to parse it
+ $tempOutput = New-TemporaryFile
+ $tempError = New-TemporaryFile
+
+ try {
+ $process = Start-Process -FilePath $Command -ArgumentList $Arguments -NoNewWindow -Wait -PassThru `
+ -RedirectStandardOutput $tempOutput.FullName -RedirectStandardError $tempError.FullName
+
+ $exitCode = $process.ExitCode
+ $stdOut = Get-Content $tempOutput.FullName -Raw -ErrorAction SilentlyContinue
+ $stdErr = Get-Content $tempError.FullName -Raw -ErrorAction SilentlyContinue
+
+ # Log everything
+ if ($stdOut) { $stdOut | Out-File $LOG_FILE -Append }
+ if ($stdErr) { $stdErr | Out-File $LOG_FILE -Append }
+
+ # Show informational stderr messages if requested
+ if ($ShowOutput -and $stdErr) {
+ $stdErr.Split("`n") | ForEach-Object {
+ if ($_.Trim()) { Write-Host " $_" -ForegroundColor DarkGray }
+ }
+ }
+
+ if ($exitCode -ne 0) {
+ Write-Host "`n$ErrorMessage" -ForegroundColor Red
+ Write-Host "Exit Code: $exitCode" -ForegroundColor Yellow
+ if ($stdOut) {
+ Write-Host "Output:" -ForegroundColor Yellow
+ $stdOut.Split("`n") | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
+ }
+ if ($stdErr) {
+ Write-Host "Error:" -ForegroundColor Yellow
+ $stdErr.Split("`n") | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
+ }
+ Write-Host "`nFull log: $LOG_FILE" -ForegroundColor DarkGray
+ return $false
+ } else {
+ if ($SuccessMessage) {
+ Write-Host $SuccessMessage -ForegroundColor Green
+ }
+ # Show stdout if it contains useful info (like container ID)
+ if ($stdOut -and $stdOut.Trim().Length -lt 100) {
+ Write-Host " → $($stdOut.Trim())" -ForegroundColor DarkGray
+ }
+ return $true
+ }
+ } finally {
+ Remove-Item $tempOutput -ErrorAction SilentlyContinue
+ Remove-Item $tempError -ErrorAction SilentlyContinue
+ }
+ }
+}
+function Test-Dependencies {
+ $missing = @()
+
+ if (-not (Get-Command podman -ErrorAction SilentlyContinue)) {
+ $missing += "Podman (install Podman Desktop from podman.io)"
+ }
+
+ # Check for MySQL/MariaDB client in PATH and common installation locations
+ $mysqlFound = $false
+ $script:MYSQL_CLIENT = ""
+
+ # First check if already in PATH
+ foreach ($cmd in @("mysql", "mariadb")) {
+ if (Get-Command $cmd -ErrorAction SilentlyContinue) {
+ $script:MYSQL_CLIENT = $cmd
+ $mysqlFound = $true
+ break
+ }
+ }
+
+ # If not in PATH, check common Windows installation locations
+ if (-not $mysqlFound) {
+ $searchPaths = @(
+ "C:\Program Files\MariaDB*\bin\mysql.exe",
+ "C:\Program Files\MariaDB*\bin\mariadb.exe",
+ "C:\Program Files\MySQL\MySQL Server*\bin\mysql.exe",
+ "C:\Program Files (x86)\MariaDB*\bin\mysql.exe",
+ "C:\Program Files (x86)\MySQL\MySQL Server*\bin\mysql.exe"
+ )
+
+ foreach ($pattern in $searchPaths) {
+ $found = Get-ChildItem $pattern -ErrorAction SilentlyContinue | Select-Object -First 1
+ if ($found) {
+ $script:MYSQL_CLIENT = $found.FullName
+ $mysqlFound = $true
+ # Add to PATH for this session
+ $binDir = Split-Path $found.FullName
+ $env:Path = "$binDir;$env:Path"
+ Write-Host "Found MySQL/MariaDB at: $($found.FullName)" -ForegroundColor Green
+ break
+ }
+ }
+ }
+
+ if (-not $mysqlFound) {
+ $missing += "MySQL/MariaDB client (winget install Oracle.MySQL or MariaDB.MariaDB)"
+ }
+
+ if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
+ $missing += "Git (winget install Git.Git)"
+ }
+
+ if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
+ $missing += "Rust/Cargo (https://rustup.rs)"
+ }
+
+ if ($missing.Count -gt 0) {
+ Write-Host "`nMissing dependencies:" -ForegroundColor Red
+ $missing | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow }
+ Write-Host "`nPlease install the missing tools and try again.`n"
+ exit 1
+ }
+}
+
+function Configure-Database {
+ Write-Host "`n=== MariaDB Container Configuration ===" -ForegroundColor Cyan
+
+ $config.DB_HOST = Read-Host "DB Host [$($config.DB_HOST)]"
+ if ([string]::IsNullOrWhiteSpace($config.DB_HOST)) { $config.DB_HOST = "127.0.0.1" }
+
+ $port = Read-Host "DB Port [$($config.DB_PORT)]"
+ if ($port) { $config.DB_PORT = $port }
+
+ $name = Read-Host "DB Name [$($config.DB_NAME)]"
+ if ($name) { $config.DB_NAME = $name }
+
+ $user = Read-Host "DB User [$($config.DB_USER)]"
+ if ($user) { $config.DB_USER = $user }
+
+ $pass = Read-Host "DB Password [$($config.DB_PASS)]"
+ if ($pass) { $config.DB_PASS = $pass }
+
+ $rootPass = Read-Host "Root Password [$($config.DB_ROOT_PASSWORD)]"
+ if ($rootPass) { $config.DB_ROOT_PASSWORD = $rootPass }
+
+ Save-Config
+
+ # Check if container exists
+ $exists = podman ps -a --format "{{.Names}}" | Select-String -Pattern "^$($config.BEEPZONE_DB_CONTAINER_NAME)$" -Quiet
+
+ if ($exists) {
+ $restart = Read-Host "`nContainer '$($config.BEEPZONE_DB_CONTAINER_NAME)' exists. Start/restart it? (y/n)"
+ if ($restart -eq 'y') {
+ $null = Run-Command -Command "podman" -Arguments @("start", $config.BEEPZONE_DB_CONTAINER_NAME) `
+ -SuccessMessage "Container started successfully!" `
+ -ErrorMessage "Failed to start container"
+ }
+ return
+ }
+
+ # Create new container
+ Write-Host "`nCreating new MariaDB container..." -ForegroundColor Yellow
+
+ $cmd = @(
+ "run", "-d",
+ "--name", $config.BEEPZONE_DB_CONTAINER_NAME,
+ "-e", "MARIADB_ROOT_PASSWORD=$($config.DB_ROOT_PASSWORD)",
+ "-e", "MARIADB_DATABASE=$($config.DB_NAME)",
+ "-e", "MARIADB_USER=$($config.DB_USER)",
+ "-e", "MARIADB_PASSWORD=$($config.DB_PASS)",
+ "-p", "$($config.DB_PORT):3306",
+ $config.BEEPZONE_DB_IMAGE
+ )
+
+ $success = Run-Command -Command "podman" -Arguments $cmd `
+ -SuccessMessage "MariaDB container started successfully!" `
+ -ErrorMessage "Failed to start MariaDB container"
+
+ if (-not $success) {
+ Read-Host "`nPress Enter to continue"
+ }
+}
+
+function Import-Database {
+ $schemaFile = Join-Path $WORK_DIR "backend\database\schema\beepzone-schema-dump.sql"
+ $fullDumpFile = Join-Path $WORK_DIR "backend\database\dev\beepzone-full-dump.sql"
+
+ $choice = Show-Menu "Database Import" @(
+ "Full dump (schema + data)",
+ "Clean schema only (no data)"
+ )
+
+ $file = if ($choice -eq 1) { $fullDumpFile } else { $schemaFile }
+
+ if (-not (Test-Path $file)) {
+ Write-Host "File not found: $file" -ForegroundColor Red
+ Read-Host "Press Enter to continue"
+ return
+ }
+
+ $confirm = Read-Host "`nThis will DROP and recreate the database. Continue? (y/n)"
+ if ($confirm -ne 'y') { return }
+
+ Write-Host "Importing database..." -ForegroundColor Yellow
+
+ $sql = @"
+DROP DATABASE IF EXISTS ``$($config.DB_NAME)``;
+CREATE DATABASE ``$($config.DB_NAME)`` CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci;
+USE ``$($config.DB_NAME)``;
+SET FOREIGN_KEY_CHECKS=0;
+$(Get-Content $file -Raw)
+SET FOREIGN_KEY_CHECKS=1;
+"@
+
+ # Import via podman exec
+ Write-Host "Importing SQL..." -ForegroundColor Yellow
+
+ $tempFile = New-TemporaryFile
+ $sql | Out-File $tempFile.FullName -Encoding UTF8
+
+ $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)`""
+ $output = cmd /c $importCmd 2`>`&1
+ $exitCode = $LASTEXITCODE
+
+ Remove-Item $tempFile -ErrorAction SilentlyContinue
+ $output | Out-File $LOG_FILE -Append
+
+ if ($exitCode -ne 0) {
+ Write-Host "`nDatabase import failed!" -ForegroundColor Red
+ Write-Host "Error output:" -ForegroundColor Yellow
+ $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
+ Write-Host "`nFull log: $LOG_FILE" -ForegroundColor DarkGray
+ } else {
+ Write-Host "Database imported successfully!" -ForegroundColor Green
+
+ if ($choice -eq 2) {
+ Write-Host "`nRecommendation: Create an Admin role (power 100) and Admin user next." -ForegroundColor Cyan
+ }
+
+ $config.DEPLOYMENT_TYPE = if ($choice -eq 1) { "dev" } else { "clean" }
+ Save-Config
+ }
+
+ Read-Host "`nPress Enter to continue"
+}
+
+function Manage-Users {
+ while ($true) {
+ $choice = Show-Menu "Manage Users & Roles" @(
+ "Create a role",
+ "Create a user",
+ "Delete a role",
+ "Delete a user",
+ "Back to main menu"
+ )
+
+ switch ($choice) {
+ 1 { Create-Role }
+ 2 { Create-User }
+ 3 { Delete-Role }
+ 4 { Delete-User }
+ 5 { return }
+ }
+ }
+}
+
+function Create-Role {
+ Write-Host "`n=== Create Role ===" -ForegroundColor Cyan
+ $name = Read-Host "Role name"
+ $power = Read-Host "Power (1-100)"
+
+ if ([string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($power)) {
+ Write-Host "Name and power are required!" -ForegroundColor Red
+ Read-Host "Press Enter to continue"
+ return
+ }
+
+ if (-not ($power -match '^\d+$') -or [int]$power -lt 1 -or [int]$power -gt 100) {
+ Write-Host "Power must be 1-100!" -ForegroundColor Red
+ Read-Host "Press Enter to continue"
+ return
+ }
+
+ $sql = "INSERT INTO roles (name, power) VALUES ('$name', $power);"
+
+ $ErrorActionPreference = 'Continue'
+ $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:)' }
+ $ErrorActionPreference = 'Stop'
+ $output | Out-File $LOG_FILE -Append
+
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host "`nFailed to create role!" -ForegroundColor Red
+ Write-Host "Error output:" -ForegroundColor Yellow
+ $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
+ } else {
+ Write-Host "Role '$name' created successfully!" -ForegroundColor Green
+ }
+
+ Read-Host "Press Enter to continue"
+}
+
+function Delete-Role {
+ Write-Host "`nFetching roles..." -ForegroundColor Yellow
+ $ErrorActionPreference = 'Continue'
+ $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:)' }
+ $ErrorActionPreference = 'Stop'
+
+ if (-not $roles) {
+ Write-Host "No roles found!" -ForegroundColor Red
+ Read-Host "Press Enter to continue"
+ return
+ }
+
+ Write-Host "`n=== Select Role to Delete ===" -ForegroundColor Cyan
+ $roleList = @()
+ $i = 1
+ $roles | ForEach-Object {
+ $parts = $_ -split "`t"
+ Write-Host " [$i] $($parts[1]) (power: $($parts[2]))"
+ $roleList += @{ Id = $parts[0]; Name = $parts[1]; Power = $parts[2] }
+ $i++
+ }
+
+ $choice = Read-Host "`nSelect role (1-$($roleList.Count))"
+ $selected = $roleList[[int]$choice - 1]
+
+ $confirm = Read-Host "Delete role '$($selected.Name)'? (y/n)"
+ if ($confirm -ne 'y') { return }
+
+ $ErrorActionPreference = 'Continue'
+ $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:)' }
+ $ErrorActionPreference = 'Stop'
+ $output | Out-File $LOG_FILE -Append
+
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host "`nFailed to delete role!" -ForegroundColor Red
+ Write-Host "Error output:" -ForegroundColor Yellow
+ $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
+ } else {
+ Write-Host "Role deleted!" -ForegroundColor Green
+ }
+
+ Read-Host "Press Enter to continue"
+}
+
+function Create-User {
+ Write-Host "`nFetching roles..." -ForegroundColor Yellow
+ $ErrorActionPreference = 'Continue'
+ $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:)' }
+ $ErrorActionPreference = 'Stop'
+
+ if (-not $roles) {
+ Write-Host "No roles found! Create a role first." -ForegroundColor Red
+ Read-Host "Press Enter to continue"
+ return
+ }
+
+ Write-Host "`n=== Select Role ===" -ForegroundColor Cyan
+ $roleList = @()
+ $i = 1
+ $roles | ForEach-Object {
+ $parts = $_ -split "`t"
+ Write-Host " [$i] $($parts[1]) (power: $($parts[2]))"
+ $roleList += @{ Id = $parts[0]; Name = $parts[1] }
+ $i++
+ }
+
+ $roleChoice = Read-Host "`nSelect role (1-$($roleList.Count))"
+ $selectedRole = $roleList[[int]$roleChoice - 1]
+
+ Write-Host "`n=== Create User ===" -ForegroundColor Cyan
+ $name = Read-Host "Full name"
+ $username = Read-Host "Username"
+ $password = Read-Host "Password" -AsSecureString
+ $passwordPlain = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
+ $email = Read-Host "Email (optional)"
+ $phone = Read-Host "Phone (optional)"
+
+ if ([string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($username) -or [string]::IsNullOrWhiteSpace($passwordPlain)) {
+ Write-Host "Name, username, and password are required!" -ForegroundColor Red
+ Read-Host "Press Enter to continue"
+ return
+ }
+
+ # Hash password with bcrypt cost 12 using Rust (since cargo is already available)
+ Write-Host "Hashing password..." -ForegroundColor Yellow
+
+ $bcryptToolPath = Join-Path $env:TEMP "beepzone_bcrypt_tool.exe"
+
+ # Build tiny bcrypt tool if not already built
+ if (-not (Test-Path $bcryptToolPath)) {
+ Write-Host "Building bcrypt tool (one-time setup)..." -ForegroundColor Yellow
+
+ $tempDir = Join-Path $env:TEMP "bcrypt_hasher"
+ New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
+
+ # Create minimal Cargo.toml
+ @"
+[package]
+name = "bcrypt_hasher"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+bcrypt = "0.15"
+"@ | Out-File (Join-Path $tempDir "Cargo.toml") -Encoding UTF8
+
+ # Create main.rs
+ @"
+use std::env;
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+ if args.len() != 2 {
+ eprintln!("Usage: bcrypt_hasher <password>");
+ std::process::exit(1);
+ }
+
+ match bcrypt::hash(&args[1], 12) {
+ Ok(hash) => println!("{}", hash),
+ Err(e) => {
+ eprintln!("Error: {}", e);
+ std::process::exit(1);
+ }
+ }
+}
+"@ | Out-File (Join-Path $tempDir "main.rs") -Encoding UTF8
+
+ # Create src directory and move main.rs
+ $srcDir = Join-Path $tempDir "src"
+ New-Item -ItemType Directory -Force -Path $srcDir | Out-Null
+ Move-Item (Join-Path $tempDir "main.rs") (Join-Path $srcDir "main.rs") -Force
+
+ # Build it
+ Push-Location $tempDir
+ Write-Host " Compiling bcrypt tool..." -ForegroundColor DarkGray
+ cargo build --release --quiet 2>&1 | Out-Null
+ Pop-Location
+
+ # Copy to temp location
+ $builtExe = Join-Path $tempDir "target\release\bcrypt_hasher.exe"
+ if (Test-Path $builtExe) {
+ Copy-Item $builtExe $bcryptToolPath -Force
+ Write-Host " Bcrypt tool ready!" -ForegroundColor Green
+ } else {
+ Write-Host " Failed to build bcrypt tool" -ForegroundColor Red
+ }
+
+ # Cleanup build directory
+ Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
+ }
+
+ # Hash the password
+ if (Test-Path $bcryptToolPath) {
+ try {
+ $passwordHash = & $bcryptToolPath $passwordPlain
+ if ($LASTEXITCODE -eq 0 -and $passwordHash -match '^\$2[ab]\$12\$') {
+ Write-Host "Password hashed successfully!" -ForegroundColor Green
+ } else {
+ Write-Host "`nError: Failed to hash password" -ForegroundColor Red
+ Read-Host "Press Enter to continue"
+ return
+ }
+ } catch {
+ Write-Host "`nError: Failed to hash password: $_" -ForegroundColor Red
+ Read-Host "Press Enter to continue"
+ return
+ }
+ } else {
+ Write-Host "`nError: Could not build bcrypt tool. Make sure cargo is working." -ForegroundColor Red
+ Read-Host "Press Enter to continue"
+ return
+ }
+
+ $emailVal = if ($email) { "'$email'" } else { "NULL" }
+ $phoneVal = if ($phone) { "'$phone'" } else { "NULL" }
+
+ $sql = "INSERT INTO users (name, username, password, role_id, email, phone, active) VALUES ('$name', '$username', '$passwordHash', $($selectedRole.Id), $emailVal, $phoneVal, 1);"
+
+ $ErrorActionPreference = 'Continue'
+ $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:)' }
+ $ErrorActionPreference = 'Stop'
+ $output | Out-File $LOG_FILE -Append
+
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host "`nFailed to create user!" -ForegroundColor Red
+ Write-Host "Error output:" -ForegroundColor Yellow
+ $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
+ } else {
+ Write-Host "User '$username' created successfully!" -ForegroundColor Green
+ }
+
+ Read-Host "Press Enter to continue"
+}
+
+function Delete-User {
+ Write-Host "`nFetching users..." -ForegroundColor Yellow
+ $ErrorActionPreference = 'Continue'
+ $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:)' }
+ $ErrorActionPreference = 'Stop'
+
+ if (-not $users) {
+ Write-Host "No users found!" -ForegroundColor Red
+ Read-Host "Press Enter to continue"
+ return
+ }
+
+ Write-Host "`n=== Select User to Delete ===" -ForegroundColor Cyan
+ $userList = @()
+ $i = 1
+ $users | ForEach-Object {
+ $parts = $_ -split "`t"
+ Write-Host " [$i] $($parts[1]) - $($parts[2])"
+ $userList += @{ Id = $parts[0]; Username = $parts[1] }
+ $i++
+ }
+
+ $choice = Read-Host "`nSelect user (1-$($userList.Count))"
+ $selected = $userList[[int]$choice - 1]
+
+ $confirm = Read-Host "Delete user '$($selected.Username)'? (y/n)"
+ if ($confirm -ne 'y') { return }
+
+ $ErrorActionPreference = 'Continue'
+ $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:)' }
+ $ErrorActionPreference = 'Stop'
+ $output | Out-File $LOG_FILE -Append
+
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host "`nFailed to delete user!" -ForegroundColor Red
+ Write-Host "Error output:" -ForegroundColor Yellow
+ $output | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
+ } else {
+ Write-Host "User deleted!" -ForegroundColor Green
+ }
+
+ Read-Host "Press Enter to continue"
+}
+
+function Setup-SeckelAPI {
+ $sourcesDir = Join-Path $WORK_DIR "backend\seckelapi\sources"
+ $configDir = Join-Path $WORK_DIR "backend\seckelapi\config"
+
+ if (-not (Test-Path (Join-Path $sourcesDir ".git"))) {
+ Write-Host "Cloning SeckelAPI repository..." -ForegroundColor Yellow
+ $parentDir = Split-Path $sourcesDir -Parent
+ New-Item -ItemType Directory -Force -Path $parentDir | Out-Null
+ $success = Run-Command -Command "git" -Arguments @("clone", $config.SECKELAPI_REPO, $sourcesDir) `
+ -SuccessMessage "Repository cloned successfully!" `
+ -ErrorMessage "Failed to clone SeckelAPI repository" `
+ -ShowOutput
+ if (-not $success) {
+ Read-Host "`nPress Enter to continue"
+ return
+ }
+ }
+
+ # Copy config files
+ New-Item -ItemType Directory -Force -Path (Join-Path $sourcesDir "config") | Out-Null
+ Get-ChildItem "$configDir\*.toml" | ForEach-Object {
+ Copy-Item $_.FullName (Join-Path $sourcesDir "config\$($_.Name)") -Force
+ }
+
+ # Update config with database settings for container
+ $basicsConfig = Join-Path $sourcesDir "config\basics.toml"
+ if (Test-Path $basicsConfig) {
+ Write-Host "Updating config for container deployment..." -ForegroundColor Yellow
+
+ # Read line by line and only update values in [database] section
+ $lines = Get-Content $basicsConfig
+ $inDatabaseSection = $false
+ $newLines = @()
+
+ foreach ($line in $lines) {
+ if ($line -match '^\[database\]') {
+ $inDatabaseSection = $true
+ $newLines += $line
+ }
+ elseif ($line -match '^\[.*\]') {
+ # Entering a different section
+ $inDatabaseSection = $false
+ $newLines += $line
+ }
+ elseif ($inDatabaseSection) {
+ # We're in [database] section, update relevant values
+ if ($line -match '^host\s*=') {
+ $newLines += 'host = "host.containers.internal"'
+ }
+ elseif ($line -match '^port\s*=') {
+ $newLines += "port = $($config.DB_PORT)"
+ }
+ elseif ($line -match '^database\s*=') {
+ $newLines += "database = `"$($config.DB_NAME)`""
+ }
+ elseif ($line -match '^username\s*=') {
+ $newLines += "username = `"$($config.DB_USER)`""
+ }
+ elseif ($line -match '^password\s*=') {
+ $newLines += "password = `"$($config.DB_PASS)`""
+ }
+ else {
+ $newLines += $line
+ }
+ }
+ else {
+ $newLines += $line
+ }
+ }
+
+ $newLines | Out-File $basicsConfig -Encoding UTF8
+ Write-Host "Config updated!" -ForegroundColor Green
+ }
+
+ # Build and deploy container
+ $choice = Show-Menu "SeckelAPI Container Build" @(
+ "Build and run in podman container",
+ "Skip for now"
+ )
+
+ if ($choice -eq 1) {
+ $containerName = "beepzone-seckelapi"
+ $imageName = "beepzone-seckelapi:latest"
+ $containerfile = Join-Path $WORK_DIR "backend\seckelapi\Containerfile"
+
+ # Stop and remove existing container
+ $existing = podman ps -a --format "{{.Names}}" | Select-String -Pattern "^${containerName}$" -Quiet
+ if ($existing) {
+ Write-Host "Stopping existing container..." -ForegroundColor Yellow
+ podman stop $containerName 2>&1 | Out-Null
+ podman rm $containerName 2>&1 | Out-Null
+ }
+
+ # Build container
+ Write-Host "`nBuilding SeckelAPI container..." -ForegroundColor Yellow
+ $success = Run-Command -Command "podman" -Arguments @("build", "-t", $imageName, "-f", $containerfile, (Join-Path $WORK_DIR "backend\seckelapi")) `
+ -SuccessMessage "Container image built successfully!" `
+ -ErrorMessage "Container build failed!" `
+ -LiveOutput
+
+ if ($success) {
+ $run = Read-Host "`nStart the container now? (y/n)"
+ if ($run -eq 'y') {
+ Write-Host "Starting container..." -ForegroundColor Yellow
+ $success = Run-Command -Command "podman" -Arguments @("run", "-d", "--name", $containerName, "--add-host", "host.containers.internal:host-gateway", "-p", "5777:5777", $imageName) `
+ -SuccessMessage "Container started successfully!" `
+ -ErrorMessage "Failed to start container!"
+
+ if ($success) {
+ Write-Host "`nSeckelAPI is running at: http://localhost:5777" -ForegroundColor Cyan
+ Write-Host "`nUseful commands:" -ForegroundColor DarkGray
+ Write-Host " podman logs $containerName - View logs" -ForegroundColor DarkGray
+ Write-Host " podman stop $containerName - Stop container" -ForegroundColor DarkGray
+ Write-Host " podman start $containerName - Start container" -ForegroundColor DarkGray
+ Write-Host " podman restart $containerName - Restart container" -ForegroundColor DarkGray
+ }
+ }
+ }
+ }
+
+ Read-Host "`nPress Enter to continue"
+}
+
+function Build-DesktopClient {
+ $sourcesDir = Join-Path $WORK_DIR "frontend\desktop-client\sources"
+
+ if (-not (Test-Path (Join-Path $sourcesDir ".git"))) {
+ Write-Host "Cloning client repository..." -ForegroundColor Yellow
+ $parentDir = Split-Path $sourcesDir -Parent
+ New-Item -ItemType Directory -Force -Path $parentDir | Out-Null
+ $success = Run-Command -Command "git" -Arguments @("clone", $config.CLIENT_REPO, $sourcesDir) `
+ -SuccessMessage "Repository cloned successfully!" `
+ -ErrorMessage "Failed to clone client repository" `
+ -ShowOutput
+ if (-not $success) {
+ Read-Host "`nPress Enter to continue"
+ return
+ }
+ }
+
+ $confirm = Read-Host "`nBuild BeepZone desktop client for Windows? (y/n)"
+ if ($confirm -ne 'y') { return }
+
+ Write-Host "Building desktop client..." -ForegroundColor Yellow
+ Push-Location $sourcesDir
+
+ $success = Run-Command -Command "cargo" -Arguments @("build", "--release") `
+ -SuccessMessage "" `
+ -ErrorMessage "Build failed!" `
+ -LiveOutput
+
+ Pop-Location
+
+ if ($success) {
+ Write-Host "`nBuild complete!" -ForegroundColor Green
+ Write-Host "Binary: $sourcesDir\target\release\" -ForegroundColor Cyan
+ }
+
+ Read-Host "Press Enter to continue"
+}
+
+# Main
+Clear-Host
+Write-Host @"
+#########################################
+# BeepZone Setup Helper (Windows) #
+#########################################
+"@ -ForegroundColor Cyan
+
+Test-Dependencies
+
+while ($true) {
+ $choice = Show-Menu "BeepZone Setup" @(
+ "Configure & run MariaDB (podman)",
+ "Import DB schema & data",
+ "Manage users & roles",
+ "Configure & setup SeckelAPI",
+ "Build desktop client",
+ "Quit"
+ )
+
+ switch ($choice) {
+ 1 { Configure-Database }
+ 2 { Import-Database }
+ 3 { Manage-Users }
+ 4 { Setup-SeckelAPI }
+ 5 { Build-DesktopClient }
+ 6 { exit 0 }
+ }
+}