Create a new directory under stacks/:
stacks/<stack-name>/
├── docker-compose.yml # Docker Compose service definitions
├── setup.sh # Post-boot automation script
├── deploy.md # Human-readable deployment guide
└── config.env.template # Environment variables with sane defaults
Create specs/<stack-name>.json:
{
"name": "<stack-name>",
"description": "What this stack does",
"vm": {
"name": "<stack-name>",
"cores": 2,
"ram_gb": 4,
"disk_gb": 40
},
"stack": "stacks/<stack-name>",
"services": {
"<service>": {
"port": 8080,
"version": "latest",
"default_user": "admin",
"default_password": "changeme"
}
}
}The vm section is read by create-vm.ps1 when --SpecFile is passed.
Every setup.sh must:
- Wait for services to be healthy. Poll HTTP endpoints, don't use fixed sleeps.
- Be idempotent. Running it twice should be safe (detect if setup already completed).
- Handle password gotchas. Use
printffor JSON with special characters. - Save credentials to
api-keys.txt. Consistent location,chmod 600. - Print a summary. URLs, credentials, and verification commands.
Template:
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[[ -f "${SCRIPT_DIR}/config.env" ]] && source "${SCRIPT_DIR}/config.env"
# Wait for service
info "Waiting for <service>..."
while ! curl -sf http://localhost:<port>/health >/dev/null 2>&1; do
sleep 3
done
# Setup logic here...
# Save credentials
cat > ./api-keys.txt <<EOF
# Generated: $(date)
User: admin
Password: ${PASSWORD}
API Key: ${API_KEY}
EOF
chmod 600 ./api-keys.txtThe full workflow for any stack:
1. Build cloud-init ISO: ./cloud-init/build-iso.sh <vm-name> <password>
2. SCP ISO to hyperv-host: scp /tmp/<vm-name>-cidata.iso hyperv-host:C:/Users/user/Downloads/
3. Create VM (on hyperv-host): .\create-vm.ps1 -VMName <vm-name> -CloudInitISO <path> [-SpecFile <path>]
4. Find VM IP: .\find-vm-ip.ps1 -VMName <vm-name>
5. SCP stack to VM: scp -r stacks/<stack-name>/ admin@<ip>:~/<stack-name>/
6. SSH in and deploy: ssh admin@<ip> "cd ~/<stack-name> && cp config.env.template config.env && docker compose up -d && ./setup.sh"
Before submitting:
- Deploy on a fresh VM (don't test on an already-configured one)
- Run setup.sh twice (idempotency check)
- Verify all credentials in api-keys.txt work
- Check
docker compose psshows all services healthy - Test from another machine (not just localhost)