Skip to content

Commit f53a39f

Browse files
CoderCococlaude
andauthored
feat: add Windows PowerShell setup script (#31)
## Summary Add a PowerShell equivalent of the existing `setup.sh` script to enable Windows developers to set up the Game Server Manager project with a single command. ## Key Changes - **New `setup.ps1` script** that provides Windows-native setup automation with the following functionality: - Prerequisite validation and installation (Node.js 20+, Terraform, AWS CLI v2) - Automatic tool installation via `winget` where available, with fallback to manual download for AWS CLI - npm workspace dependency installation and Lambda bundle building - S3 backend bucket creation with versioning, encryption, and public access blocking - DynamoDB lock table creation for Terraform state locking - Terraform initialization with automatic state migration from local to S3 backend - User-friendly setup guidance with next steps and Discord bot configuration instructions ## Implementation Details - Uses PowerShell 5.1+ with strict error handling (`$ErrorActionPreference = 'Stop'`) - Includes helper functions for command detection and tool installation - Parses `terraform.tfvars` to extract project name and AWS region for backend configuration - Handles region-specific S3 bucket creation (special handling for `us-east-1`) - Gracefully skips resource creation if S3 bucket or DynamoDB table already exist - Provides clear console output with progress indicators and actionable error messages - Mirrors the functionality and user experience of the existing bash setup script https://claude.ai/code/session_01LHDg9ZNoZmERqW6zAN61bj --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 16761cd commit f53a39f

3 files changed

Lines changed: 266 additions & 6 deletions

File tree

docs/docs/intro.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ game-server-deploy/
8585
├── docs/ # this site
8686
├── Dockerfile # containerised management app
8787
├── docker-compose.yml
88-
├── setup.sh # first-time bootstrap (node/terraform/aws)
88+
├── setup.sh # first-time bootstrap — Linux / macOS
89+
├── setup.ps1 # first-time bootstrap — Windows (PowerShell)
8990
└── README.md
9091
```

docs/docs/setup.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ On the machine that will run `terraform apply` and the management app:
2121

2222
| Tool | Version | Notes |
2323
|------|---------|-------|
24-
| Node.js | 20+ | Enforced by `setup.sh` and the Nest server boot. |
24+
| Node.js | 20+ | Enforced by both setup scripts and the Nest server boot. |
2525
| npm | 10+ | Ships with Node 20. |
26-
| Terraform | 1.5+ | Installed automatically by `setup.sh` on Debian/Ubuntu. |
27-
| AWS CLI | v2 | Installed automatically by `setup.sh` on Linux. |
26+
| Terraform | 1.5+ | Installed automatically by `setup.sh` (Debian/Ubuntu) or `setup.ps1` (Windows via winget). |
27+
| AWS CLI | v2 | Installed automatically by `setup.sh` (Linux) or `setup.ps1` (Windows via MSI). |
2828
| Docker | 24+ | Only if you plan to run the app via `docker compose`. |
2929

3030
On the AWS side you need:
@@ -124,17 +124,31 @@ instead — the management app will pick them up too.
124124

125125
## 3. Clone and bootstrap
126126

127+
**Linux / macOS:**
128+
127129
```bash
128130
git clone https://github.com/codercoco/game-server-deploy.git
129131
cd game-server-deploy
130132
chmod +x setup.sh
131133
./setup.sh
132134
```
133135

134-
`setup.sh` is idempotent — safe to re-run at any time. It:
136+
**Windows (PowerShell 5.1+):**
137+
138+
```powershell
139+
git clone https://github.com/codercoco/game-server-deploy.git
140+
cd game-server-deploy
141+
.\setup.ps1
142+
```
143+
144+
> If PowerShell blocks the script with an execution-policy error, run
145+
> `Set-ExecutionPolicy RemoteSigned -Scope CurrentUser` once, then retry.
146+
147+
Both scripts are idempotent — safe to re-run at any time. They:
135148

136149
1. Checks for Node 20+, and installs Terraform and the AWS CLI if missing
137-
(Debian/Ubuntu only; macOS users should install those manually first).
150+
(`setup.sh` uses apt on Debian/Ubuntu; `setup.ps1` uses winget + the AWS MSI
151+
installer on Windows; macOS users should install those tools manually first).
138152
2. Runs `npm ci` from `app/` so all workspaces are installed.
139153
3. Runs `npm run build:lambdas` to produce `app/packages/lambda/*/dist/handler.cjs`
140154
— Terraform's `archive_file` data sources zip these at apply time, so

setup.ps1

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
#Requires -Version 5.1
2+
<#
3+
.SYNOPSIS
4+
Game Server Manager — Windows setup script (PowerShell equivalent of setup.sh).
5+
6+
.DESCRIPTION
7+
Checks prerequisites (Node.js 20+, Terraform, AWS CLI), installs missing tools
8+
where possible, installs npm workspaces, builds Lambda bundles, bootstraps the
9+
S3 + DynamoDB Terraform backend, and runs `terraform init`.
10+
#>
11+
12+
$ErrorActionPreference = 'Stop'
13+
Set-StrictMode -Version Latest
14+
15+
$ScriptDir = $PSScriptRoot
16+
17+
Write-Host ""
18+
Write-Host " Game Server Manager - Setup"
19+
Write-Host " --------------------------------------"
20+
Write-Host ""
21+
22+
# ---------------------------------------------------------------------------
23+
# Helpers
24+
# ---------------------------------------------------------------------------
25+
26+
function Test-Command {
27+
param([string]$Name)
28+
return [bool](Get-Command $Name -ErrorAction SilentlyContinue)
29+
}
30+
31+
# $ErrorActionPreference = 'Stop' only traps cmdlet errors, not native-exe exit codes.
32+
# Wrap every native call with this so a non-zero exit code throws immediately.
33+
function Invoke-Native {
34+
param([scriptblock]$Block)
35+
& $Block
36+
if ($LASTEXITCODE -ne 0) {
37+
throw "Command failed with exit code $LASTEXITCODE."
38+
}
39+
}
40+
41+
function Install-WithWinget {
42+
param([string]$PackageId, [string]$DisplayName)
43+
if (-not (Test-Command 'winget')) {
44+
Write-Host " winget is not available. Please install $DisplayName manually."
45+
exit 1
46+
}
47+
Write-Host " Installing $DisplayName via winget..."
48+
Invoke-Native { winget install --id $PackageId --silent --accept-package-agreements --accept-source-agreements }
49+
# Refresh PATH for the current session so subsequent Test-Command calls work.
50+
$env:PATH = [System.Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' +
51+
[System.Environment]::GetEnvironmentVariable('PATH', 'User')
52+
}
53+
54+
# ---------------------------------------------------------------------------
55+
# 1. Prerequisites
56+
# ---------------------------------------------------------------------------
57+
58+
# Node.js 20+
59+
if (-not (Test-Command 'node')) {
60+
Write-Host " Node.js not found."
61+
Install-WithWinget 'OpenJS.NodeJS.LTS' 'Node.js LTS'
62+
if (-not (Test-Command 'node')) {
63+
Write-Host " Node.js still not found after install. Open a new terminal and re-run."
64+
exit 1
65+
}
66+
}
67+
68+
$nodeMajor = [int](node -p "process.versions.node.split('.')[0]")
69+
if ($nodeMajor -lt 20) {
70+
Write-Host " Node.js 20+ required (detected $nodeMajor). Please upgrade and re-run."
71+
exit 1
72+
}
73+
74+
# Terraform
75+
if (-not (Test-Command 'terraform')) {
76+
Write-Host " terraform not found — installing via winget..."
77+
Install-WithWinget 'HashiCorp.Terraform' 'Terraform'
78+
if (-not (Test-Command 'terraform')) {
79+
Write-Host " Terraform still not found after install. Open a new terminal and re-run."
80+
exit 1
81+
}
82+
}
83+
84+
# AWS CLI
85+
if (-not (Test-Command 'aws')) {
86+
Write-Host " AWS CLI not found — downloading and installing v2..."
87+
$msiPath = Join-Path $env:TEMP 'AWSCLIV2.msi'
88+
Invoke-WebRequest -Uri 'https://awscli.amazonaws.com/AWSCLIV2.msi' -OutFile $msiPath
89+
Start-Process msiexec.exe -ArgumentList "/i `"$msiPath`" /qn /norestart" -Wait -Verb RunAs
90+
Remove-Item $msiPath -ErrorAction SilentlyContinue
91+
$env:PATH = [System.Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' +
92+
[System.Environment]::GetEnvironmentVariable('PATH', 'User')
93+
if (-not (Test-Command 'aws')) {
94+
Write-Host " AWS CLI still not found after install. Open a new terminal and re-run."
95+
exit 1
96+
}
97+
}
98+
99+
Write-Host " Prerequisites found (node, terraform, aws cli)"
100+
101+
# ---------------------------------------------------------------------------
102+
# 2. Install JS dependencies and build Lambda bundles
103+
# ---------------------------------------------------------------------------
104+
105+
Write-Host ""
106+
Write-Host " Installing Node dependencies..."
107+
Set-Location (Join-Path $ScriptDir 'app')
108+
Invoke-Native { npm ci }
109+
110+
Write-Host ""
111+
Write-Host " Building Lambda bundles..."
112+
Invoke-Native { npm run build:lambdas }
113+
114+
# ---------------------------------------------------------------------------
115+
# 3. Bootstrap S3 backend + Terraform init
116+
# ---------------------------------------------------------------------------
117+
118+
Write-Host ""
119+
Write-Host " Initializing Terraform..."
120+
Set-Location (Join-Path $ScriptDir 'terraform')
121+
122+
$tfvarsPath = 'terraform.tfvars'
123+
if (-not (Test-Path $tfvarsPath)) {
124+
Copy-Item 'terraform.tfvars.example' $tfvarsPath
125+
Write-Host " Created terraform.tfvars from example - edit it with your settings."
126+
}
127+
128+
# Parse project_name and aws_region from terraform.tfvars (fall back to defaults).
129+
$tfvarsContent = Get-Content $tfvarsPath -Raw
130+
131+
$projectMatch = [regex]::Match($tfvarsContent, '(?m)^\s*project_name\s*=\s*"([^"]+)"')
132+
$regionMatch = [regex]::Match($tfvarsContent, '(?m)^\s*aws_region\s*=\s*"([^"]+)"')
133+
134+
$TfProject = if ($projectMatch.Success) { $projectMatch.Groups[1].Value } else { 'game-servers' }
135+
$TfRegion = if ($regionMatch.Success) { $regionMatch.Groups[1].Value } else { 'us-east-1' }
136+
137+
$TfStateBucket = "$TfProject-tf-state"
138+
$TfLockTable = "$TfProject-tf-locks"
139+
140+
Write-Host ""
141+
Write-Host " Bootstrapping S3 backend (bucket: $TfStateBucket, region: $TfRegion)..."
142+
143+
# S3 bucket
144+
$bucketExists = $false
145+
try {
146+
aws s3api head-bucket --bucket $TfStateBucket --region $TfRegion 2>$null
147+
$bucketExists = ($LASTEXITCODE -eq 0)
148+
} catch { $bucketExists = $false }
149+
150+
if ($bucketExists) {
151+
Write-Host " S3 bucket $TfStateBucket already exists - skipping."
152+
} else {
153+
Write-Host " Creating S3 bucket $TfStateBucket..."
154+
if ($TfRegion -eq 'us-east-1') {
155+
Invoke-Native { aws s3api create-bucket --bucket $TfStateBucket --region $TfRegion }
156+
} else {
157+
Invoke-Native {
158+
aws s3api create-bucket `
159+
--bucket $TfStateBucket `
160+
--region $TfRegion `
161+
--create-bucket-configuration "LocationConstraint=$TfRegion"
162+
}
163+
}
164+
Invoke-Native {
165+
aws s3api put-bucket-versioning `
166+
--bucket $TfStateBucket `
167+
--versioning-configuration Status=Enabled `
168+
--region $TfRegion
169+
}
170+
Invoke-Native {
171+
aws s3api put-public-access-block `
172+
--bucket $TfStateBucket `
173+
--public-access-block-configuration 'BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true' `
174+
--region $TfRegion
175+
}
176+
Invoke-Native {
177+
aws s3api put-bucket-encryption `
178+
--bucket $TfStateBucket `
179+
--server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"},"BucketKeyEnabled":true}]}' `
180+
--region $TfRegion
181+
}
182+
}
183+
184+
# DynamoDB lock table
185+
$tableExists = $false
186+
try {
187+
aws dynamodb describe-table --table-name $TfLockTable --region $TfRegion 2>$null
188+
$tableExists = ($LASTEXITCODE -eq 0)
189+
} catch { $tableExists = $false }
190+
191+
if ($tableExists) {
192+
Write-Host " DynamoDB table $TfLockTable already exists - skipping."
193+
} else {
194+
Write-Host " Creating DynamoDB lock table $TfLockTable..."
195+
Invoke-Native {
196+
aws dynamodb create-table `
197+
--table-name $TfLockTable `
198+
--attribute-definitions AttributeName=LockID,AttributeType=S `
199+
--key-schema AttributeName=LockID,KeyType=HASH `
200+
--billing-mode PAY_PER_REQUEST `
201+
--region $TfRegion
202+
}
203+
Write-Host " Waiting for DynamoDB table to become ACTIVE..."
204+
Invoke-Native { aws dynamodb wait table-exists --table-name $TfLockTable --region $TfRegion }
205+
}
206+
207+
# terraform init (with optional state migration)
208+
$tfInitArgs = @(
209+
"-backend-config=bucket=$TfStateBucket"
210+
"-backend-config=key=$TfProject/terraform.tfstate"
211+
"-backend-config=region=$TfRegion"
212+
"-backend-config=dynamodb_table=$TfLockTable"
213+
"-backend-config=encrypt=true"
214+
)
215+
216+
if (Test-Path 'terraform.tfstate') {
217+
Write-Host " Local terraform.tfstate detected - migrating state to S3..."
218+
Invoke-Native { 'yes' | terraform init -migrate-state @tfInitArgs }
219+
} else {
220+
Invoke-Native { terraform init @tfInitArgs }
221+
}
222+
223+
# ---------------------------------------------------------------------------
224+
# Done
225+
# ---------------------------------------------------------------------------
226+
227+
Write-Host ""
228+
Write-Host " Setup complete!"
229+
Write-Host ""
230+
Write-Host " Next steps:"
231+
Write-Host " 1. Edit terraform/terraform.tfvars with your game servers and domain"
232+
Write-Host " 2. Run: cd terraform; terraform plan"
233+
Write-Host " 3. Run: cd terraform; terraform apply"
234+
Write-Host " 4. Run the management app:"
235+
Write-Host " Dev: cd app; npm run dev"
236+
Write-Host " Docker: docker compose up --build"
237+
Write-Host " 5. Open http://localhost:5173 (dev) or http://localhost:5000 (docker)"
238+
Write-Host ""
239+
Write-Host " Discord bot setup (serverless):"
240+
Write-Host " - Open Credentials tab in the web UI and save the Application ID,"
241+
Write-Host " Bot Token, and Application Public Key from the Discord Developer Portal."
242+
Write-Host " - Copy the 'Interactions Endpoint URL' from the same tab and paste it"
243+
Write-Host " into the Discord Developer Portal under General Information."
244+
Write-Host " - Add a guild ID under the Guilds tab and click 'Register commands'."
245+
Write-Host ""

0 commit comments

Comments
 (0)