17 domain design docs covering architecture, accounts, inventory, rentals, lessons, repairs, POS, payments, batch repairs, delivery, billing, accounting, deployment, licensing, installer, and backend tech architecture. Plus implementation roadmap (doc 18) and personnel management (doc 19). Key design decisions documented: - company/location model (multi-tenant + multi-location) - member entity (renamed from student to support multiple adults) - Stripe vs Global Payments billing ownership differences - User/location/terminal licensing model - Valkey 8 instead of Redis
289 lines
19 KiB
Markdown
289 lines
19 KiB
Markdown
Music Store Management Platform
|
|
|
|
Self-Hosted Windows Installer — PowerShell Design
|
|
|
|
Version 1.0 | Phase 1: PowerShell | Phase 2: Inno Setup .exe wrapper
|
|
|
|
|
|
|
|
# 1. Overview
|
|
|
|
The Windows self-hosted installer is delivered in two phases. Phase 1 (current) uses a PowerShell script — appropriate for beta customers and IT-managed deployments. Phase 2 wraps the same PowerShell logic in an Inno Setup .exe for a consumer-grade double-click experience. No logic changes are required between phases — only packaging changes.
|
|
|
|
|
|
|
|
Phase
|
|
|
|
Delivery
|
|
|
|
Target
|
|
|
|
Phase 1
|
|
|
|
install.ps1 script
|
|
|
|
Beta customers, IT-managed stores, technically capable staff
|
|
|
|
Phase 2
|
|
|
|
setup.exe (Inno Setup)
|
|
|
|
All stores — double-click consumer experience, no technical knowledge required
|
|
|
|
|
|
|
|
# 2. Installation Phase Overview
|
|
|
|
The installer proceeds through numbered phases. Phase state is saved to disk so installation can resume after a required reboot. Each phase is idempotent — safe to re-run if interrupted.
|
|
|
|
|
|
|
|
Phase
|
|
|
|
Name
|
|
|
|
Actions
|
|
|
|
1
|
|
|
|
collect-config
|
|
|
|
Prompt for license key, store name, admin email, admin password. Validate license against API. Save state.
|
|
|
|
2
|
|
|
|
enable-virt
|
|
|
|
Check Windows edition. Enable Hyper-V (Pro/Enterprise) or WSL2 (Home). Reboot if required — resume automatically after.
|
|
|
|
3
|
|
|
|
install-docker
|
|
|
|
Check if Docker already installed. Download and install Docker Desktop silently if not. Wait for daemon.
|
|
|
|
4
|
|
|
|
create-dirs
|
|
|
|
Create C:\Platform directory structure. Write .env, docker-compose.yml, nginx.conf, license.lic.
|
|
|
|
5
|
|
|
|
pull-images
|
|
|
|
Authenticate to private registry using license token. Pull platform Docker images.
|
|
|
|
6
|
|
|
|
start-services
|
|
|
|
docker compose up -d. Wait for API health check to pass.
|
|
|
|
7
|
|
|
|
register-service
|
|
|
|
Install NSSM. Register PlatformService Windows service for auto-start on boot.
|
|
|
|
8
|
|
|
|
complete
|
|
|
|
Clean up state file. Open browser to localhost. Show completion message.
|
|
|
|
|
|
|
|
# 3. Hyper-V Restart Handling
|
|
|
|
Enabling Hyper-V requires a kernel-level change that cannot take effect without a reboot. This is unavoidable on machines where Hyper-V is not already enabled. The installer handles this transparently — the store owner reboots once and installation continues automatically after login.
|
|
|
|
|
|
|
|
## 3.1 Detection Flow
|
|
|
|
Check Hyper-V state: Already enabled → no reboot needed, continue to Docker phase Disabled → enable, check if reboot required RestartNeeded = false → continue immediately (rare) RestartNeeded = true → save state, schedule resume, rebootWindows Home — check WSL2 instead: Already enabled → no reboot needed Disabled → enable WSL2 + VirtualMachinePlatform Both features require reboot → same resume pattern
|
|
|
|
|
|
|
|
## 3.2 Resume-After-Reboot Mechanism
|
|
|
|
Windows registry Run key schedules the script to execute automatically after the next login. The script detects the resume flag, skips completed phases, and continues from where it left off.
|
|
|
|
|
|
|
|
On reboot needed: 1. Save install state to C:\Platform\install-state.json { phase: 'install-docker', licenseKey: '...', storeName: '...' } 2. Write registry run key: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run PlatformInstallResume = powershell -ExecutionPolicy Bypass -File 'C:\Platform\install.ps1' -Resume 3. Prompt user to restart: 'A restart is required. Press Enter to restart now.'After reboot (auto-runs on login): 1. Script starts with -Resume flag 2. Reads install-state.json 3. Removes registry run key 4. Continues from saved phase 5. User sees: 'Resuming installation after restart...'
|
|
|
|
|
|
|
|
## 3.3 State File Format
|
|
|
|
C:\Platform\install-state.json{ "phase": "install-docker", "licenseKey": "LIC-2024-00142-XXXX", "registryToken": "eyJ...", "storeName": "Springfield Music Co.", "adminEmail": "admin@springfieldmusic.com", "modules": ["CORE", "MOD-RENTALS", "MOD-LESSONS"], "savedAt": "2024-09-01T10:30:00Z"}Note: admin password NOT saved to state file.If resuming after reboot, password is re-prompted.State file deleted on successful completion.
|
|
|
|
|
|
|
|
## 3.4 User Experience During Restart
|
|
|
|
============================================ Platform Music Store Installation============================================Enter your license key: LIC-2024-00142-XXXXValidating license... OKModules: Rentals, Lessons, Repairs, AccountingEnter your store name: Springfield Music Co.Enter admin email: admin@springfieldmusic.comEnter admin password: ********Checking virtualization...Enabling Hyper-V...============================================ A restart is required to enable Hyper-V. Installation will continue automatically after your computer restarts. You do not need to run this script again. Press Enter to restart now, or close this window to restart manually later.============================================[User presses Enter — machine reboots][Script runs automatically after login]============================================ Resuming installation after restart...============================================Checking Docker...Downloading Docker Desktop...Installing Docker Desktop......
|
|
|
|
|
|
|
|
# 4. Script Structure
|
|
|
|
## 4.1 Script Parameters and Entry Point
|
|
|
|
#Requires -RunAsAdministrator#Requires -Version 5.1$ErrorActionPreference = 'Stop'param( [switch]$Resume, [switch]$Uninstall)# ── Constants ───────────────────────────────────$INSTALL_DIR = 'C:\Platform'$STATE_FILE = '$INSTALL_DIR\install-state.json'$LICENSE_API = 'https://license.platform.com'$REGISTRY_URL = 'registry.platform.com'$RESUME_KEY = 'PlatformInstallResume'$SERVICE_NAME = 'PlatformService'$NSSM_URL = 'https://nssm.cc/release/nssm-2.24.zip'$DOCKER_URL = 'https://desktop.docker.com/win/main/amd64/...'# ── Entry point ─────────────────────────────────if ($Uninstall) { Invoke-Uninstall; exit }$currentPhase = 'collect-config'if ($Resume -or (Test-Path $STATE_FILE)) { $state = Get-Content $STATE_FILE | ConvertFrom-Json $currentPhase = $state.phase Remove-ResumeKey Write-Banner 'Resuming installation after restart...'}Invoke-InstallPhases -StartFrom $currentPhase
|
|
|
|
|
|
|
|
## 4.2 Phase Execution Pattern
|
|
|
|
function Invoke-InstallPhases($StartFrom) { $phases = @( 'collect-config', 'enable-virt', 'install-docker', 'create-dirs', 'pull-images', 'start-services', 'register-service', 'complete' ) $active = $false foreach ($phase in $phases) { if ($phase -eq $StartFrom) { $active = $true } if (-not $active) { continue } Write-Host '' Write-Host "--- Phase: $phase ---" Save-State $phase # save before running switch ($phase) { 'collect-config' { Invoke-CollectConfig } 'enable-virt' { Invoke-EnableVirtualization } 'install-docker' { Invoke-InstallDocker } 'create-dirs' { Invoke-CreateDirectories } 'pull-images' { Invoke-PullImages } 'start-services' { Invoke-StartServices } 'register-service' { Invoke-RegisterService } 'complete' { Invoke-Complete } } }}
|
|
|
|
|
|
|
|
## 4.3 Virtualization Detection
|
|
|
|
function Invoke-EnableVirtualization { $edition = (Get-WindowsEdition -Online).Edition Write-Host "Windows edition: $edition" if ($edition -match 'Pro|Enterprise|Education|Server') { Invoke-EnableHyperV } else { Invoke-EnableWSL2 }}function Invoke-EnableHyperV { $feature = Get-WindowsOptionalFeature ` -Online -FeatureName Microsoft-Hyper-V if ($feature.State -eq 'Enabled') { Write-Host 'Hyper-V already enabled — no restart needed.' return } Write-Host 'Enabling Hyper-V...' $result = Enable-WindowsOptionalFeature ` -Online -FeatureName Microsoft-Hyper-V ` -All -NoRestart if ($result.RestartNeeded) { Invoke-ScheduleResumeAndReboot }}function Invoke-EnableWSL2 { $wsl = Get-WindowsOptionalFeature ` -Online -FeatureName Microsoft-Windows-Subsystem-Linux $vmp = Get-WindowsOptionalFeature ` -Online -FeatureName VirtualMachinePlatform if ($wsl.State -eq 'Enabled' -and $vmp.State -eq 'Enabled') { Write-Host 'WSL2 already enabled — no restart needed.' return } Write-Host 'Enabling WSL2...' Enable-WindowsOptionalFeature ` -Online -FeatureName Microsoft-Windows-Subsystem-Linux ` -NoRestart | Out-Null Enable-WindowsOptionalFeature ` -Online -FeatureName VirtualMachinePlatform ` -NoRestart | Out-Null Invoke-ScheduleResumeAndReboot}
|
|
|
|
|
|
|
|
## 4.4 Resume Schedule and Reboot
|
|
|
|
function Invoke-ScheduleResumeAndReboot { $scriptPath = $MyInvocation.ScriptName # Schedule resume after reboot $cmd = "powershell -ExecutionPolicy Bypass" + " -WindowStyle Normal" + " -File '$scriptPath' -Resume" New-ItemProperty ` -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' ` -Name $RESUME_KEY ` -Value $cmd ` -Force | Out-Null Write-Host '' Write-Host '============================================' Write-Host ' A restart is required to enable' Write-Host ' virtualization for Docker.' Write-Host '' Write-Host ' Installation will continue automatically' Write-Host ' after your computer restarts.' Write-Host ' You do not need to run this script again.' Write-Host '============================================' Write-Host '' $confirm = Read-Host 'Press Enter to restart now' Restart-Computer -Force exit # Won't reach here but good practice}function Remove-ResumeKey { Remove-ItemProperty ` -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' ` -Name $RESUME_KEY ` -ErrorAction SilentlyContinue}
|
|
|
|
|
|
|
|
## 4.5 Docker Installation
|
|
|
|
function Invoke-InstallDocker { if (Get-Command docker -ErrorAction SilentlyContinue) { Write-Host 'Docker already installed.' Invoke-WaitForDockerDaemon return } Write-Host 'Downloading Docker Desktop...' $installer = '$env:TEMP\DockerDesktopInstaller.exe' Invoke-WebRequest -Uri $DOCKER_URL -OutFile $installer Write-Host 'Installing Docker Desktop (2-5 minutes)...' Start-Process $installer ` -ArgumentList 'install --quiet --accept-license' ` -Wait # Refresh PATH $env:Path = [Environment]::GetEnvironmentVariable('Path', 'Machine') Invoke-WaitForDockerDaemon}function Invoke-WaitForDockerDaemon { Write-Host 'Waiting for Docker daemon...' $timeout = 90 $elapsed = 0 while ($elapsed -lt $timeout) { try { docker info 2>&1 | Out-Null Write-Host 'Docker is running.' return } catch { } Start-Sleep -Seconds 3 $elapsed += 3 Write-Host " Still waiting... ($elapsed/$timeout sec)" } throw 'Docker did not start. Please restart and re-run.'}
|
|
|
|
|
|
|
|
## 4.6 Service Registration (NSSM)
|
|
|
|
function Invoke-RegisterService { Write-Host 'Installing service manager (NSSM)...' # Download NSSM if not present $nssm = '$INSTALL_DIR\nssm.exe' if (-not (Test-Path $nssm)) { $zip = '$env:TEMP\nssm.zip' Invoke-WebRequest -Uri $NSSM_URL -OutFile $zip Expand-Archive $zip '$env:TEMP\nssm' -Force Copy-Item '$env:TEMP\nssm\nssm-2.24\win64\nssm.exe' $nssm } # Remove existing service if present & $nssm stop $SERVICE_NAME 2>&1 | Out-Null & $nssm remove $SERVICE_NAME confirm 2>&1 | Out-Null # Docker executable path $docker = 'C:\Program Files\Docker\Docker\resources\bin\docker.exe' # Register service & $nssm install $SERVICE_NAME $docker ` 'compose --project-directory $INSTALL_DIR up' & $nssm set $SERVICE_NAME Start SERVICE_AUTO_START & $nssm set $SERVICE_NAME AppDirectory $INSTALL_DIR & $nssm set $SERVICE_NAME AppStdout '$INSTALL_DIR\logs\service.log' & $nssm set $SERVICE_NAME AppStderr '$INSTALL_DIR\logs\service-error.log' & $nssm set $SERVICE_NAME AppRotateFiles 1 & $nssm set $SERVICE_NAME AppRotateBytes 10485760 # 10MB Start-Service $SERVICE_NAME Write-Host 'Service registered and started.'}
|
|
|
|
|
|
|
|
# 5. Uninstall Script
|
|
|
|
The same script handles uninstallation via the -Uninstall flag. Clean removal for beta testing and troubleshooting.
|
|
|
|
|
|
|
|
function Invoke-Uninstall { Write-Host 'Uninstalling Platform...' # Stop and remove service $nssm = '$INSTALL_DIR\nssm.exe' if (Test-Path $nssm) { & $nssm stop $SERVICE_NAME 2>&1 | Out-Null & $nssm remove $SERVICE_NAME confirm 2>&1 | Out-Null } # Stop and remove containers if (Get-Command docker -ErrorAction SilentlyContinue) { Set-Location $INSTALL_DIR -ErrorAction SilentlyContinue docker compose down -v 2>&1 | Out-Null docker image rm registry.platform.com/platform:latest 2>&1 | Out-Null } # Remove resume registry key if present Remove-ResumeKey # Ask about data $keepData = Read-Host 'Keep your store data? (y/n)' if ($keepData -eq 'n') { Remove-Item -Recurse -Force $INSTALL_DIR -ErrorAction SilentlyContinue Write-Host 'All data removed.' } else { # Remove everything except data and backups Remove-Item '$INSTALL_DIR\*.yml' -ErrorAction SilentlyContinue Remove-Item '$INSTALL_DIR\*.conf' -ErrorAction SilentlyContinue Remove-Item '$INSTALL_DIR\*.env' -ErrorAction SilentlyContinue Remove-Item '$INSTALL_DIR\*.exe' -ErrorAction SilentlyContinue Write-Host 'Platform removed. Data preserved in $INSTALL_DIR\data' } Write-Host 'Uninstall complete.'}
|
|
|
|
|
|
|
|
# 6. Error Handling
|
|
|
|
All phases run inside a try-catch. Failures show a clear message with context and a support contact. State is preserved so the install can be retried from the failed phase without starting over.
|
|
|
|
|
|
|
|
try { Invoke-InstallPhases -StartFrom $currentPhase} catch { Write-Host '' Write-Host '============================================' Write-Host ' Installation failed.' Write-Host '' Write-Host " Error: $($_.Exception.Message)" Write-Host " Phase: $currentPhase" Write-Host '' Write-Host ' Your progress has been saved.' Write-Host ' Re-run this script to retry from' Write-Host ' where it failed.' Write-Host '' Write-Host ' Support: support@platform.com' Write-Host ' Include the error above in your email.' Write-Host '============================================' exit 1}
|
|
|
|
|
|
|
|
# 7. Phase 2 — Inno Setup .exe Wrapper
|
|
|
|
Once the platform is stable and beta is complete, the PowerShell script is wrapped in an Inno Setup installer. No logic changes to the PowerShell script are required — Inno Setup is purely a packaging and UI layer on top.
|
|
|
|
|
|
|
|
## 7.1 What Inno Setup Adds
|
|
|
|
- Professional installer UI — welcome screen, license agreement, progress bar
|
|
|
|
- Single signed .exe download — no script to right-click and run
|
|
|
|
- UAC elevation prompt handled automatically
|
|
|
|
- Code signed binary — no SmartScreen warning
|
|
|
|
- Windows Add/Remove Programs entry
|
|
|
|
- Desktop shortcut creation
|
|
|
|
- Built-in restart handling — Inno Setup has native resume-after-reboot support
|
|
|
|
- Uninstaller registered in Windows
|
|
|
|
|
|
|
|
## 7.2 Inno Setup Wraps the PowerShell Script
|
|
|
|
; installer.iss (Inno Setup script)[Setup]AppName=Platform Music StoreAppVersion=1.0DefaultDirName={autopf}\PlatformOutputBaseFilename=PlatformSetupSignTool=signtool[Files]; Bundle all dependenciesSource: install.ps1; DestDir: {tmp}Source: DockerDesktopInstaller.exe; DestDir: {tmp}Source: nssm.exe; DestDir: {app}Source: docker-compose.yml; DestDir: {app}Source: nginx.conf; DestDir: {app}[Run]; Execute PowerShell script silentlyFilename: powershell.exe; Parameters: -ExecutionPolicy Bypass -File '{tmp}\install.ps1' -DockerInstaller '{tmp}\DockerDesktopInstaller.exe' -InstallDir '{app}' StatusMsg: Installing Platform... Flags: runhidden waituntilterminated
|
|
|
|
|
|
|
|
## 7.3 Code Signing Requirements
|
|
|
|
Certificate Type
|
|
|
|
Cost/yr
|
|
|
|
Notes
|
|
|
|
Standard OV certificate
|
|
|
|
~$200-400
|
|
|
|
Removes unknown publisher warning. SmartScreen trust builds over time with installs.
|
|
|
|
EV (Extended Validation)
|
|
|
|
~$400-700
|
|
|
|
Instant SmartScreen trust — no reputation period. Requires hardware token (YubiKey). Recommended for commercial release.
|
|
|
|
|
|
|
|
# 8. Distribution Security
|
|
|
|
## 8.1 Source Code Protection
|
|
|
|
Python source code is compiled to a native binary using Nuitka before being packaged into the Docker image. The Docker image is distributed via a private registry — customers receive a container with a compiled binary, not readable Python source.
|
|
|
|
|
|
|
|
Build pipeline (GitHub Actions): 1. Run tests 2. Nuitka compile: python -m nuitka --standalone --onefile main.py Output: single compiled binary 3. Docker build: FROM ubuntu:24.04 COPY main /app/main # compiled binary only CMD ["/app/main"] # no Python, no .py files 4. Push to AWS ECR: registry.platform.com/platform:1.6.0 registry.platform.com/platform:latest 5. Update version registry API
|
|
|
|
|
|
|
|
## 8.2 Registry Access Control
|
|
|
|
The private Docker registry requires authentication. License validation returns a time-limited AWS ECR pull token scoped to read-only access on the platform image only.
|
|
|
|
|
|
|
|
License validation response:{ valid: true, modules: ['CORE', 'MOD-RENTALS', ...], registry_token: 'eyJ...', // ECR token, 12hr expiry registry_url: 'registry.platform.com', license_json: '{ signed license file... }'}ECR token properties: - Read-only (pull only, cannot push) - Scoped to platform image only - 12 hour expiry - Refreshed automatically by tray app - Even if extracted from config, limited blast radius
|
|
|
|
|
|
|
|
## 8.3 License Enforcement in Binary
|
|
|
|
The Ed25519 public key is embedded in the compiled binary at build time. License verification is compiled code — there is no license.py file to edit or bypass. The binary verifies the license file signature on every startup and checks module permissions on every licensed API route.
|
|
|
|
|
|
|
|
# 9. Post-Install Management
|
|
|
|
Phase 1 (PowerShell) customers manage the platform via command line. Phase 2 customers get the tray app. Both approaches documented below.
|
|
|
|
|
|
|
|
## 9.1 Phase 1 — Command Line Management
|
|
|
|
# All commands run from C:\Platformcd C:\Platform# Check statusdocker compose ps# View logsdocker compose logs --tail=50docker compose logs api --follow# Restartdocker compose restart# Stopdocker compose down# Startdocker compose up -d# Updatedocker compose pulldocker compose up -d# Backup nowdocker compose exec postgres pg_dump -U platform platform > backups\backup-$(Get-Date -Format 'yyyy-MM-dd').sql
|
|
|
|
|
|
|
|
## 9.2 Phase 2 — Tray App
|
|
|
|
The system tray app (built in Go for minimal footprint) provides a point-and-click interface for all common management tasks. Store owner never needs a terminal.
|
|
|
|
|
|
|
|
Right-click tray icon: ● Platform Manager v1.6.0 ────────────────────────── Open Platform → browser to localhost ────────────────────────── Status ✓ API Running ✓ Database Running ✓ Cache Running ────────────────────────── 🔵 Update Available (1.7.0) View Changelog Install Update ────────────────────────── Backup Now View Last Backup: Today 3:00am ✓ ────────────────────────── Restart Services View Logs ────────────────────────── Stop Platform |