Pārlūkot izejas kodu

made it somewhat run on windows kinda

crt 1 mēnesi atpakaļ
vecāks
revīzija
8f4e7ae7e0
4 mainītis faili ar 915 papildinājumiem un 2 dzēšanām
  1. 85 0
      README-WIN.md
  2. 2 2
      README.md
  3. 6 0
      backend/seckelapi/.containerignore
  4. 822 0
      beepzone-helper.ps1

+ 85 - 0
README-WIN.md

@@ -0,0 +1,85 @@
+# BeepZone Setup Guide for Windows
+
+Windows-specific setup instructions for BeepZone Inventory System using PowerShell.
+
+## what you need
+
+install these tools (download from official websites, winget is unreliable):
+
+### required dependencies:
+1. **Podman Desktop** (for containers)
+   - download from: https://podman-desktop.io/downloads
+   - includes podman CLI and WSL2 machine
+   - after install, open Podman Desktop and initialize the machine
+
+2. **Rust & Cargo** (for building binaries)
+   - download rustup from: https://rustup.rs/
+   - run the installer and follow prompts
+   - open Visual Studio Installer via Windows menu
+   - select modify
+   - make sure "Desktop Developement with C++" and "Linux, Mac, and Embedded Developement with C++" are checked
+   - press the modify button with windows shield of deceipt
+   - restart terminal after installation
+
+3. **Git** (for cloning repositories)
+   - download from: https://git-scm.com/download/win
+   - use default settings during installation other than default editor ... i recommend nano as vim is for more advanded linux nerds id say
+
+4. **MariaDB Client** (for database access)
+    - download from: https://mariadb.org/download/
+    - during install make sure to not install database instance lol unless you want to run a database on your laptop
+    - script auto-detects at: `C:\Program Files\MariaDB *\bin\mysql.exe`
+
+## how to use
+
+1. open PowerShell in the BeepZone directory
+2. allow script execution (one-time):
+   ```powershell
+   Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
+   ```
+3. run the helper script:
+   ```powershell
+   .\beepzone-helper.ps1
+   ```
+
+## what the script does
+
+provides an interactive menu for:
+- running a podman container with mariadb and
+    - letting you configure access details
+    - importing the schema
+    - managing users and roles in the db (create/delete both with bcrypt password hashing)
+    - optionally import some seeding data
+- compiling and setting up SeckelAPI as a podman container
+    - uses proper port mapping 5777:5777 and host gateway for db access
+    - auto-configures database connection settings
+- compiling the desktop client natively on Windows
+    - produces native `.exe` binary for Windows
+
+## setup workflow
+
+1. **configure & run mariadb container**
+   - set database credentials
+   - creates and starts mariadb:12 container
+   - exposes port 3306
+
+2. **import database schema**
+   - choose between:
+     - full dump (includes live dev sample data, admin:admin123 are the logins btw)
+     - clean schema (empty tables)
+   - automatically creates database and imports
+
+3. **create admin user & role**
+   - create a role with power level 100 (admin)
+   - create your first adminier user
+   - experience me compiling a fucking bcrypt tool that ive embedded into the powershell code because windows is stinky and wont let me encrypt the passwords for use in the db bruh
+
+4. **setup SeckelAPI backend**
+   - automatically clones SeckelAPI source from https://git.teleco.ch/crt/seckelapi.git
+   - auto updates config for container networking (once, after that it dont for some reason on my computer)
+   - builds container image and starts on port 5777
+
+5. **build desktop client**
+   - clones client source from https://git.teleco.ch/crt/beepzone-client-egui-emo.git
+   - compiles native Windows binary with cargo
+   - executable location: `frontend\desktop-client\sources\target\release\` (ignore the debug terminal that opens along with the app, will be removed someday tm)

+ 2 - 2
README.md

@@ -3,6 +3,8 @@ Huh so you want to actually try beepzone? (Oh god)
 
 Well then since I have no setup guides tbh I'll provide a bash script here to build and get you started, do not use this in Prod like at all I dont recommend my own software for such things in general tbh.
 
+**Windows Users**: See [README-WIN.md](README-WIN.md) for Windows specific setup instructions using PowerShell.
+
 ## Whats in this repository?
 - Database Schema and Dumps from Dev Environement
 - Helper Script to get you started and do some basic user management as no client can do so yet lol
@@ -25,8 +27,6 @@ someday once i consider em release worthy it will also obviously be able to comp
 
 a real installer that is not just a setup bash script is planned but thats far into the future idk when 
 
-Setup Scripts currently do not support windows
-
 requirements for script:
 - mac
     - podman installed and configured (podman-desktop recommended for dev work)

+ 6 - 0
backend/seckelapi/.containerignore

@@ -0,0 +1,6 @@
+sources/target/
+sources/.git/
+sources/**/*.log
+sources/.env
+.git/
+*.log

+ 822 - 0
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 }
+    }
+}