From cbbebbd35db0e4bcf364a4a93bc2bcc13f5d1c07 Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Fri, 15 Aug 2025 04:33:23 +0200 Subject: [PATCH] refactor: Enhance local development environment and documentation - .gitignore: Ignore result* files. - Makefile: Refactored to improve clarity, remove unused targets, add new testing targets (test-usb, test-vm), and streamline cloud deployment commands. - README.md: Significantly updated to reflect the new single-participant environment focus, refine the learning flow, update commands, and improve overall readability. - flake.nix: Modified to enable Docker for local development, adjust desktop environment behavior (manual start), and implement improved systemd services for container setup and health monitoring, including retry logic. --- .gitignore | 2 +- Makefile | 93 ++++++---------- README.md | 274 +++++++++++---------------------------------- flake.nix | 318 ++++++++++++++++++++++++++++++++++++++++------------- 4 files changed, 342 insertions(+), 345 deletions(-) diff --git a/.gitignore b/.gitignore index bd0efa4..733ba53 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .env -result/ +result* .direnv/ *.qcow2 diff --git a/Makefile b/Makefile index 5321d28..73e0edc 100644 --- a/Makefile +++ b/Makefile @@ -1,38 +1,35 @@ -include .env export -.PHONY: help deploy-cloud build-usb flash-usb local-vm-run clean status destroy-cloud opencode lint +.PHONY: help deploy-cloud build-usb flash-usb local-vm test-vm clean status destroy-cloud opencode lint DOMAIN := $(or $(WORKSHOP_DOMAIN),codecrispi.es) -PARTICIPANTS := $(or $(PARTICIPANTS),3) USB_DEVICE := $(or $(USB_DEVICE),/dev/sdX) help: @echo "CODE CRISPIES Workshop Infrastructure" @echo "" - @echo "๐ŸŒ Cloud Infrastructure (Hetzner):" + @echo "๐ŸŒ Cloud Infrastructure (Hetzner):" @echo " make deploy-cloud - Deploy 15 VMs to Hetzner Cloud" @echo " make status-cloud - Check server health" @echo " make destroy-cloud - Destroy cloud infrastructure" @echo "" - @echo "๐Ÿ’พ USB Boot Drive:" + @echo "๐Ÿ’พ USB Boot Drive (Single Participant Environment):" @echo " make build-usb - Build NixOS workshop ISO" @echo " make flash-usb - Flash ISO to USB drive" + @echo " make test-usb - Test USB environment in QEMU" @echo "" - @echo "๐Ÿ–ฅ๏ธ Local Development:" - @echo " make local-vm-run - Start local VM with containers" - @echo " make local-vm-test - Test with 2 containers only" - @echo " make local-vm-full - Test with all 15 containers" + @echo "๐Ÿ–ฅ๏ธ Local Development:" + @echo " make local-vm - Start single participant VM" + @echo " make test-vm - Test VM without GUI" @echo " make clean - Clean build artifacts" @echo "" - @echo "โš™๏ธ Development:" + @echo "โš™๏ธ Development:" @echo " make opencode - Start opencode in dev shell" @echo " make lint - Run linting checks" - @echo " make check-vm - Verify VM builds correctly" @echo "" @echo "Current Config:" @echo " Domain: $(DOMAIN)" - @echo " Participants: $(PARTICIPANTS)" @echo " USB Device: $(USB_DEVICE)" @echo "" @echo "Required: HCLOUD_TOKEN, SSH key at ~/.ssh/id_ed25519.pub" @@ -53,89 +50,67 @@ flash-usb: build-usb echo "โŒ Set USB_DEVICE=/dev/sdX (find with 'lsblk')"; \ exit 1; \ fi - @echo "โš ๏ธ About to flash $(USB_DEVICE) - THIS WILL ERASE ALL DATA!" + @echo "โš ๏ธ About to flash $(USB_DEVICE) - THIS WILL ERASE ALL DATA!" @echo "Device info: $$(lsblk $(USB_DEVICE) 2>/dev/null || echo 'DEVICE NOT FOUND')" @read -p "Continue? [y/N]: " confirm && [ "$$confirm" = "y" ] sudo dd if=result/iso/nixos.iso of=$(USB_DEVICE) bs=4M status=progress oflag=sync sync - @echo "โœ… USB drive ready for workshop!" + @echo "โœ… USB drive ready!" + +test-usb: build-usb + @echo "๐Ÿงช Testing USB environment in QEMU..." + qemu-system-x86_64 \ + -cdrom result/iso/nixos.iso \ + -m 2048 \ + -enable-kvm \ + -netdev user,id=net0 \ + -device virtio-net,netdev=net0 \ + -display gtk + +local-vm: + @echo "๐Ÿ–ฅ๏ธ Starting workshop VM..." + nix run .#local-vm + +test-vm: + @echo "๐Ÿงช Testing VM build..." + nix build .#local-vm + @echo "โœ… VM builds successfully" deploy-cloud: @if [ -z "$(HCLOUD_TOKEN)" ]; then \ echo "โŒ HCLOUD_TOKEN not set"; \ - echo "Get token from: https://console.hetzner.cloud/"; \ exit 1; \ fi - @if [ ! -f ~/.ssh/id_ed25519.pub ]; then \ - echo "โŒ SSH key not found at ~/.ssh/id_ed25519.pub"; \ - echo "Generate with: ssh-keygen -t ed25519"; \ - exit 1; \ - fi - @echo "๐Ÿš€ Deploying 15 workshop servers to Hetzner Cloud..." - @echo "Domain: $(DOMAIN)" + @echo "๐Ÿš€ Deploying 15 workshop servers..." cd terraform && terraform init cd terraform && terraform apply -auto-approve \ -var="hcloud_token=$(HCLOUD_TOKEN)" \ - -var="hetzner_dns_token=$(HETZNER_DNS_TOKEN)" \ - -var="dns_zone_id=$(DNS_ZONE_ID)" \ - -var="domain=$(DOMAIN)" \ - -var="ssh_public_key=$$(cat ~/.ssh/id_ed25519.pub)" - @echo "โณ Running health checks..." - @sleep 60 - $(MAKE) status-cloud - @echo "โœ… Cloud deployment complete!" + -var="domain=$(DOMAIN)" status-cloud: - @echo "๐Ÿ” Checking server health..." + @echo "๐Ÿ“Š Checking server health..." @for name in hopper curie lovelace noether hamilton franklin johnson clarke goldberg liskov wing rosen shaw karp rich; do \ printf "%-10s " "$$name:"; \ - if timeout 10 curl -s -f https://traefik.$$name.$(DOMAIN)/ping >/dev/null 2>&1; then \ + if timeout 5 ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no workshop@$$name.$(DOMAIN) "echo ok" >/dev/null 2>&1; then \ echo "โœ… Ready"; \ - elif timeout 5 ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no workshop@$$name.$(DOMAIN) "echo ok" >/dev/null 2>&1; then \ - echo "โณ SSH OK, Traefik starting..."; \ else \ echo "โŒ Not ready"; \ fi; \ done destroy-cloud: - @echo "โš ๏ธ This will destroy ALL workshop servers!" + @echo "โš ๏ธ This will destroy ALL workshop servers!" @read -p "Continue? [y/N]: " confirm && [ "$$confirm" = "y" ] cd terraform && terraform destroy -auto-approve - @echo "โœ… Cloud infrastructure destroyed" - -local-vm-run: - @echo "๐Ÿ–ฅ๏ธ Starting local workshop VM with $(PARTICIPANTS) containers..." - @echo "VM will open with desktop showing all participant containers" - PARTICIPANTS=$(PARTICIPANTS) nix run --impure .#local-vm - -local-vm-test: - @echo "๐Ÿงช Testing with 2 containers only..." - PARTICIPANTS=2 nix run --impure .#local-vm - -local-vm-full: - @echo "๐Ÿš€ Testing with all 15 containers (heavy resource usage!)..." - PARTICIPANTS=15 nix run --impure .#local-vm - -check-vm: - @echo "โœ… Verifying VM builds correctly..." - PARTICIPANTS=2 nix build --impure .#local-vm - @echo "โœ… VM build successful" clean: rm -rf result .direnv terraform/.terraform terraform/terraform.tfstate* @echo "๐Ÿงน Cleaned up build artifacts" opencode: - @echo "๐Ÿ’ป Starting opencode in Nix dev shell..." nix develop --command opencode lint: @echo "๐Ÿ” Linting project files..." - @echo "Markdown files..." @markdownlint-cli . || true - @echo "JSON files..." - @find . -type f -name "*.json" -print0 | xargs -0 -I {} bash -c 'jq . "{}" >/dev/null || (echo "JSON lint error in {}" && exit 1)' - @echo "Nix files..." @nixpkgs-fmt --check . || true - @echo "โœ… Linting complete" diff --git a/README.md b/README.md index 994f2eb..0676208 100644 --- a/README.md +++ b/README.md @@ -1,249 +1,101 @@ -# ๐Ÿช CODE CRISPIES Workshop Infrastructure +# ๐Ÿช CODE CRISPIES Workshop Infrastructure -This repository contains the infrastructure for the Co-op Cloud workshop, providing three distinct deployment environments with dynamic scaling support. +Single-participant learning environments with local practice and cloud deployment capabilities. ---- ## ๐Ÿš€ Quick Start ```bash -# 1. Start the local development virtual machine (default: 3 containers) -make local-vm-run +# 1. Start local VM for development/testing +make local-vm -# 2. Test with different container counts -PARTICIPANTS=2 make local-vm-test # Lightweight testing -PARTICIPANTS=15 make local-vm-full # Full workshop simulation - -# 3. Build & flash USB drives for participants +# 2. Build USB drives for participants make build-usb make flash-usb USB_DEVICE=/dev/sdX -# 4. Deploy the production cloud infrastructure -export HCLOUD_TOKEN="your_token_here" +# 3. Deploy cloud infrastructure +export HCLOUD_TOKEN="your_token" make deploy-cloud ``` ---- - -## ๐Ÿ“ Project Structure - -``` -โ”œโ”€โ”€ flake.nix # All Nix configurations (USB, VM, containers) -โ”œโ”€โ”€ terraform/ # Hetzner Cloud infrastructure -โ”œโ”€โ”€ scripts/deploy.sh # Cloud setup automation -โ”œโ”€โ”€ docs/USB_BOOT_INSTRUCTIONS.md -โ””โ”€โ”€ Makefile # Build & deploy commands -``` - ---- - -## ๐ŸŒ Three Environments - -### 1. Cloud (Production) - -- **What:** 15 Hetzner VMs named `hopper.codecrispi.es`, `curie.codecrispi.es`, etc. -- **Purpose:** The live environment for workshop participants. -- **Participants:** hopper, curie, lovelace, noether, hamilton, franklin, johnson, clarke, goldberg, liskov, wing, rosen, shaw, karp, rich - -### 2. USB Boot (Workshop) - -- **What:** A bootable NixOS live environment with SSH client tools. -- **Purpose:** Used by participants to connect to their cloud servers. -- **Features:** Pre-configured with helper functions like `connect hopper`, `recipes` command, and workshop-specific tooling. - -### 3. Local (Development) - -- **What:** A self-contained Virtual Machine (VM) that runs on your local computer with configurable container count. -- **Purpose:** Complete local testing environment that mirrors production setup without needing cloud servers. -- **Scalability:** Supports 1-15 containers via `PARTICIPANTS` environment variable. - ---- - -## ๐Ÿ”ง Local Development Workflow - -1. **Choose Your Scale** - ```bash - # Lightweight development (2 containers) - PARTICIPANTS=2 make local-vm-run - - # Production simulation (15 containers) - requires 8GB+ RAM - PARTICIPANTS=15 make local-vm-run - - # Use default (3 containers) - good balance - make local-vm-run - ``` - -2. **Work Inside the VM** - All testing is now done inside the VM's graphical desktop: - * Open the **Terminal** to run commands. - * Open **Firefox** to view the deployed web applications. - -3. **Example: Deploying WordPress** - - **In the VM's Terminal**, get a root shell and SSH into a participant's container: - ```bash - # Become root (no password needed) - sudo -i - - # Connect to participant 1 (hopper) - connect hopper - - # Or direct SSH (password: root) - ssh root@192.168.100.11 - ``` - - **Inside the container**, deploy a WordPress site with `abra`: - ```bash - abra app new wordpress -S --domain=blog.hopper.local - abra app deploy blog.hopper.local - ``` - - **In the VM's Firefox**, navigate to `http://blog.hopper.local`. You will see the WordPress installation screen. - -4. **Available Helper Commands** - ```bash - sudo containers # List all containers with IPs - sudo logs # Show setup logs for all containers - sudo recipes # Display available Co-op Cloud recipes - sudo help # Show all available commands - ``` - ---- - -## ๐ŸŒ Cloud Deployment - -The cloud environment creates 15 production servers: +## ๐ŸŽฏ Learning Flow +### Local Practice (USB/VM) ```bash -# Set required environment variables -export HCLOUD_TOKEN="your_hetzner_token" -export HETZNER_DNS_TOKEN="your_dns_token" -export DNS_ZONE_ID="your_zone_id" - -# Deploy all 15 servers -make deploy-cloud - -# Check server status -make status-cloud +recipes # Show available apps +deploy wordpress # Deploy locally +browser # View at wordpress.workshop.local ``` -Each server is accessible at: -- `hopper.codecrispi.es` -- `curie.codecrispi.es` -- `lovelace.codecrispi.es` -- ... (15 total) - ---- - -## ๐Ÿ’พ USB Workshop Environment - -Build bootable USB drives for participants: - +### Cloud Deployment +```bash +connect hopper # SSH to cloud server +# Same abra commands work here +abra app new wordpress -S --domain=blog.hopper.codecrispi.es +abra app deploy blog.hopper.codecrispi.es +``` + +## ๐Ÿ—๏ธ Architecture + +**Single Participant Model**: Each environment (USB/VM) is complete and self-contained. + +- **USB Boot**: Bootable NixOS with Docker + abra for hands-on learning +- **Local VM**: Identical environment for development/testing +- **Cloud Servers**: 15 production servers (hopper, curie, lovelace, etc.) + +## ๐Ÿ’พ USB Environment + +Pre-configured with: +- Docker Swarm + abra installation +- SSH client for cloud access +- Terminal-first interface (`desktop` command for GUI) +- Helper commands: `recipes`, `deploy`, `connect`, `help` + +Build and flash: ```bash -# Build the ISO make build-usb - -# Flash to USB drive (replace /dev/sdX with your device) make flash-usb USB_DEVICE=/dev/sdb ``` -The USB environment includes: -- Pre-configured SSH client -- `connect ` command to SSH into assigned servers -- `recipes` command showing available Co-op Cloud applications -- Workshop-specific networking and WiFi helpers +## ๐ŸŒ Cloud Deployment ---- - -## โš™๏ธ Environment Variables - -Control workshop behavior with environment variables: +Creates 15 Hetzner VMs at `{name}.codecrispi.es`: ```bash -# Number of containers (1-15, default: 3) -export PARTICIPANTS=5 -make local-vm-run - -# Workshop domain for cloud deployment -export WORKSHOP_DOMAIN=myworkshop.com - -# USB device for flashing -export USB_DEVICE=/dev/sdb +export HCLOUD_TOKEN="your_token" +make deploy-cloud +make status-cloud # Check health ``` ---- - -## ๐Ÿงน Cleanup & Management +## ๐Ÿ–ฅ๏ธ Local Development ```bash -# Clean local build artifacts -make clean - -# Destroy Hetzner cloud infrastructure -make destroy-cloud - -# Verify VM builds correctly -make check-vm - -# Run development tools -make opencode # Start development environment -make lint # Code quality checks +make local-vm # Start VM +make test-vm # Verify build ``` ---- +The VM simulates the USB experience with identical configuration and commands. -## ๐Ÿ”‘ Prerequisites +## ๐Ÿ“š Available Commands -- **SSH Key:** Ed25519 key at `~/.ssh/id_ed25519.pub` - ```bash - ssh-keygen -t ed25519 - ``` -- **Nix:** NixOS or Nix package manager with flakes enabled -- **Cloud Tokens:** Hetzner Cloud API token for deployment -- **Resources:** - - 2-3 containers: 4GB+ RAM - - 5-10 containers: 8GB+ RAM - - 15 containers: 16GB+ RAM +**In USB/VM environments**: +- `recipes` - Show Co-op Cloud catalog +- `deploy ` - Deploy locally (e.g., `deploy wordpress`) +- `apps` - List deployed applications +- `connect ` - SSH to cloud server +- `cloud-deploy ` - Direct cloud deployment +- `desktop` - Start GUI session +- `browser` - Launch Firefox ---- +## ๐Ÿ”ง Prerequisites -## ๐ŸŽฏ Workshop Flow +- Nix with flakes enabled +- SSH key at `~/.ssh/id_ed25519.pub` +- HCLOUD_TOKEN for cloud deployment +- 2GB+ RAM for VM testing -1. **Preparation:** Deploy cloud infrastructure with `make deploy-cloud` -2. **Distribution:** Flash USB drives for participants with `make build-usb && make flash-usb` -3. **Workshop:** Participants boot from USB and connect to their assigned cloud servers -4. **Development:** Use local VM with `make local-vm-run` for testing and development +## ๐Ÿงน Cleanup -The architecture ensures participants get identical environments whether connecting from USB boot drives to cloud servers, or testing locally in the development VM. - ---- - -## ๐Ÿ› Troubleshooting - -### VM Won't Start ```bash -# Check if build works -make check-vm - -# Try with fewer containers -PARTICIPANTS=2 make local-vm-run -``` - -### Containers Not Accessible -```bash -# Check container status inside VM -sudo containers - -# View setup logs -sudo logs - -# Manual SSH test -ssh root@192.168.100.11 # Password: root -``` - -### Abra Not Working in Container -```bash -# Inside container, check installation -ls -la /root/.local/bin/abra -export PATH="/root/.local/bin:$PATH" -abra --version +make clean # Local artifacts +make destroy-cloud # Cloud infrastructure ``` diff --git a/flake.nix b/flake.nix index 0fda668..bc1a196 100644 --- a/flake.nix +++ b/flake.nix @@ -54,11 +54,14 @@ networking.networkmanager.enable = true; networking.hostName = "workshop-live"; + # Enable Docker for local development + virtualisation.docker.enable = true; + services.getty.autologinUser = "workshop"; users.users.workshop = { isNormalUser = true; shell = pkgs.zsh; - extraGroups = [ "networkmanager" "wheel" ]; + extraGroups = [ "networkmanager" "wheel" "docker" ]; password = ""; }; @@ -66,18 +69,98 @@ environment.systemPackages = with pkgs; [ openssh curl git networkmanager firefox xterm + docker docker-compose + # For local abra installation + bash wget jq tree nano ]; + # Auto-install abra on boot + systemd.services.workshop-abra-setup = { + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" "docker.service" ]; + wants = [ "network-online.target" ]; + script = '' + export HOME=/home/workshop + + # Wait for network + for i in {1..10}; do + if ${pkgs.curl}/bin/curl -s --max-time 5 google.com >/dev/null 2>&1; then + break + fi + sleep 3 + done + + # Install abra for workshop user (DO NOT change installation method) + if [ ! -f /home/workshop/.local/bin/abra ]; then + sudo -u workshop mkdir -p /home/workshop/.local/bin + cd /home/workshop + sudo -u workshop ${pkgs.curl}/bin/curl -fsSL https://install.abra.coopcloud.tech | sudo -u workshop ${pkgs.bash}/bin/bash + fi + + # Initialize local Docker Swarm + ${pkgs.docker}/bin/docker swarm init --advertise-addr 127.0.0.1 2>/dev/null || true + + # Add workshop user to docker group + usermod -aG docker workshop + ''; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = "root"; + }; + }; + programs.zsh = { enable = true; interactiveShellInit = '' echo "CODE CRISPIES Workshop Environment" - echo "Available servers:" - ${builtins.concatStringsSep "\n" (map (name: - "echo \" - ${name}.codecrispi.es\"" - ) allParticipantNames)} + echo "Mode: Local Development + Cloud Access" echo "" - echo "Commands: connect | recipes | help" + echo "๐Ÿ  Local Development:" + echo " recipes - Show available app recipes" + echo " deploy - Deploy app locally (e.g., deploy wordpress)" + echo " browser - Launch Firefox" + echo " desktop - Start GUI session" + echo "" + echo "โ˜๏ธ Cloud Access:" + echo " Available servers:" + ${builtins.concatStringsSep "\n" (map (name: + "echo \" - ${name}.codecrispi.es\"" + ) allParticipantNames)} + echo " connect - SSH to cloud server" + echo "" + echo "๐Ÿ“š Commands: recipes | deploy | connect | browser | desktop | help" + + # Ensure abra is in PATH + export PATH="$HOME/.local/bin:$PATH" + + deploy() { + if [ -z "$1" ]; then + echo "Usage: deploy " + echo "Example: deploy wordpress" + echo "Run 'recipes' to see available options" + return 1 + fi + + local recipe="$1" + local domain="$recipe.workshop.local" + + echo "๐Ÿš€ Deploying $recipe locally..." + echo "Domain: $domain" + + # Check if abra is available + if ! command -v abra &> /dev/null; then + echo "โŒ Abra not found. Run 'sudo systemctl restart workshop-abra-setup'" + return 1 + fi + + # Deploy with abra + abra app new "$recipe" -S --domain="$domain" + abra app deploy "$domain" + + echo "โœ… Deployed! Access at: http://$domain" + echo "๐ŸŒ Open browser with: browser" + } connect() { [ -z "$1" ] && { echo "Usage: connect "; return 1; } @@ -88,44 +171,77 @@ recipes() { echo "Available Co-op Cloud Recipes:" echo "" - echo "Content Management:" + echo "๐Ÿ“ Content Management:" echo " wordpress ghost hedgedoc dokuwiki mediawiki" echo "" - echo "File & Collaboration:" + echo "๐Ÿ“ File & Collaboration:" echo " nextcloud seafile collabora onlyoffice" echo "" - echo "Communication:" + echo "๐Ÿ’ฌ Communication:" echo " jitsi-meet matrix-synapse rocketchat mattermost" echo "" - echo "E-commerce & Business:" + echo "๐Ÿ›’ E-commerce & Business:" echo " prestashop invoiceninja kimai pretix" echo "" - echo "Development & Tools:" + echo "โš™๏ธ Development & Tools:" echo " gitea drone n8n gitlab jupyter-lab" echo "" - echo "Analytics & Monitoring:" + echo "๐Ÿ“Š Analytics & Monitoring:" echo " plausible matomo uptime-kuma grafana" echo "" - echo "Media & Social:" + echo "๐ŸŽต Media & Social:" echo " peertube funkwhale mastodon pixelfed jellyfin" echo "" - echo "Deploy: abra app new -S --domain=myapp..codecrispi.es" - echo "Browse all: https://recipes.coopcloud.tech" + echo "๐Ÿš€ Local Deploy: deploy " + echo "โ˜๏ธ Cloud Deploy: connect then use abra commands" + echo "๐Ÿ“– Browse all: https://recipes.coopcloud.tech" + } + + browser() { + echo "๐ŸŒ Starting Firefox..." + if [ -n "$DISPLAY" ]; then + firefox & + else + echo "โŒ No GUI session. Run 'desktop' first" + fi + } + + desktop() { + echo "๐Ÿ–ฅ๏ธ Starting GUI session..." + if [ -z "$DISPLAY" ]; then + startx & + export DISPLAY=:0 + sleep 3 + echo "โœ… GUI started. Run 'browser' to open Firefox" + else + echo "โ„น๏ธ GUI already running" + fi } help() { echo "CODE CRISPIES Workshop Commands:" echo "" - echo "connect - SSH to your assigned server" - echo "recipes - Show available app recipes" - echo "sudo nmcli dev wifi connect SSID password PASSWORD" + echo "๐Ÿ  Local Development:" + echo " recipes - Show all available app recipes" + echo " deploy - Deploy app locally (e.g., deploy wordpress)" + echo " browser - Launch Firefox browser" + echo " desktop - Start GUI desktop session" echo "" - echo "Examples:" - echo " connect hopper" - echo " sudo nmcli dev wifi connect CODE_CRISPIES_GUEST password workshop2024" + echo "โ˜๏ธ Cloud Access:" + echo " connect - SSH to cloud server (e.g., connect hopper)" + echo "" + echo "๐Ÿ”ง System:" + echo " sudo nmcli dev wifi connect SSID password PASSWORD" + echo " sudo systemctl restart workshop-abra-setup # Reinstall abra" + echo "" + echo "๐Ÿ“š Learning Flow:" + echo " 1. Try local: recipes โ†’ deploy wordpress โ†’ browser" + echo " 2. Try cloud: connect hopper โ†’ same abra commands" + echo "" + echo "Available servers: ${builtins.concatStringsSep " " allParticipantNames}" } - export -f connect recipes help + export -f deploy connect recipes browser desktop help ''; }; @@ -134,16 +250,17 @@ desktopManager.xfce.enable = true; displayManager = { lightdm.enable = true; - autoLogin.enable = true; - autoLogin.user = "workshop"; + autoLogin.enable = false; # Manual desktop start }; }; + # Don't auto-start GUI, let user choose systemd.user.services.workshop-welcome = { - wantedBy = [ "graphical-session.target" ]; - after = [ "graphical-session.target" ]; - script = "${pkgs.xterm}/bin/xterm -title 'CODE CRISPIES Workshop' -e 'zsh' &"; - serviceConfig.Type = "forking"; + wantedBy = [ "default.target" ]; + script = '' + echo "Welcome! Run 'desktop' to start GUI session" + ''; + serviceConfig.Type = "oneshot"; }; }) ]; @@ -317,7 +434,7 @@ ) (builtins.length participantNames)); }; - # Dynamic container generation + # Dynamic container generation with improved stability containers = builtins.listToAttrs (builtins.genList (i: let @@ -331,6 +448,9 @@ privateNetwork = true; hostAddress = "192.168.100.1"; localAddress = ip; + + # Add restart policy for container itself + restartIfChanged = true; config = { system.stateVersion = "25.05"; @@ -363,61 +483,111 @@ docker curl git wget jq bash nano tree ]; + # Improved workshop setup with retry logic systemd.services.workshop-setup = { wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" "docker.service" ]; wants = [ "network-online.target" ]; - script = '' - echo "Setting up ${name} container..." - - # Wait for network connectivity - for i in {1..15}; do - if ${pkgs.curl}/bin/curl -s --max-time 5 google.com >/dev/null 2>&1; then - echo "Network ready" - break - fi - echo "Waiting for network... ($i/15)" - sleep 3 - done - - # Initialize Docker Swarm - ${pkgs.docker}/bin/docker swarm init --advertise-addr ${ip} || echo "Swarm already initialized or failed" - - # Install abra - export HOME=/root - if [ ! -f /root/.local/bin/abra ]; then - echo "Installing abra..." - ${pkgs.curl}/bin/curl -fsSL https://install.abra.coopcloud.tech | ${pkgs.bash}/bin/bash - echo "Abra installed to /root/.local/bin/abra" - fi - - # Setup PATH in .bashrc - if ! grep -q "/.local/bin" /root/.bashrc 2>/dev/null; then - echo 'export PATH="$HOME/.local/bin:$PATH"' >> /root/.bashrc - fi - - # Create system symlink for abra - if [ -f /root/.local/bin/abra ]; then - ln -sf /root/.local/bin/abra /usr/local/bin/abra 2>/dev/null || true - fi - - # Add abra server config - if [ -f /root/.local/bin/abra ]; then - export PATH="/root/.local/bin:$PATH" - /root/.local/bin/abra server add ${name}.local 2>/dev/null || echo "Server already added or command failed" - fi - - echo "${name} container ready!" - echo "SSH: ssh root@${ip} (password: root)" - echo "Workshop user: ssh workshop@${ip} (password: workshop)" - echo "Abra: Available via 'abra' command" - ''; + + # Add restart capability for failed setups serviceConfig = { Type = "oneshot"; RemainAfterExit = true; StandardOutput = "journal"; StandardError = "journal"; TimeoutStartSec = "300"; + Restart = "on-failure"; + RestartSec = "30s"; + StartLimitBurst = 3; + StartLimitIntervalSec = "10min"; + }; + + script = '' + set -e + echo "Setting up ${name} container (attempt started)..." + + # Wait for network connectivity with timeout + network_ready=false + for i in {1..20}; do + if ${pkgs.curl}/bin/curl -s --max-time 5 google.com >/dev/null 2>&1; then + echo "Network ready after $i attempts" + network_ready=true + break + fi + echo "Waiting for network... ($i/20)" + sleep 5 + done + + if [ "$network_ready" != "true" ]; then + echo "โŒ Network failed after 20 attempts" + exit 1 + fi + + # Initialize Docker Swarm (idempotent) + if ! ${pkgs.docker}/bin/docker info | grep -q "Swarm: active"; then + echo "Initializing Docker Swarm..." + ${pkgs.docker}/bin/docker swarm init --advertise-addr ${ip} || { + echo "Swarm init failed, but continuing..." + } + else + echo "Docker Swarm already active" + fi + + # Install abra (DO NOT change installation method - keep curl approach) + export HOME=/root + if [ ! -f /root/.local/bin/abra ]; then + echo "Installing abra..." + ${pkgs.curl}/bin/curl -fsSL https://install.abra.coopcloud.tech | ${pkgs.bash}/bin/bash + echo "Abra installed to /root/.local/bin/abra" + else + echo "Abra already installed" + fi + + # Setup PATH in .bashrc (idempotent) + if ! grep -q "/.local/bin" /root/.bashrc 2>/dev/null; then + echo 'export PATH="$HOME/.local/bin:$PATH"' >> /root/.bashrc + fi + + # Create system symlink for abra (idempotent) + if [ -f /root/.local/bin/abra ]; then + ln -sf /root/.local/bin/abra /usr/local/bin/abra 2>/dev/null || true + fi + + # Add abra server config (idempotent) + if [ -f /root/.local/bin/abra ]; then + export PATH="/root/.local/bin:$PATH" + /root/.local/bin/abra server add ${name}.local 2>/dev/null || echo "Server config handled" + fi + + echo "โœ… ${name} container setup completed successfully!" + echo "SSH: ssh root@${ip} (password: root)" + echo "Workshop user: ssh workshop@${ip} (password: workshop)" + echo "Abra: Available via 'abra' command" + ''; + }; + + # Add container health monitoring + systemd.services.workshop-health = { + wantedBy = [ "multi-user.target" ]; + after = [ "workshop-setup.service" ]; + serviceConfig = { + Type = "simple"; + Restart = "always"; + RestartSec = "60s"; + ExecStart = "${pkgs.writeScript "health-check" '' + #!/bin/bash + while true; do + # Check if abra is accessible + if [ -f /root/.local/bin/abra ]; then + export PATH="/root/.local/bin:$PATH" + /root/.local/bin/abra --version >/dev/null 2>&1 || { + echo "โš ๏ธ Abra health check failed, triggering restart" + systemctl restart workshop-setup.service + } + fi + sleep 300 # Check every 5 minutes + done + ''}"; }; };