diff --git a/Makefile b/Makefile index 093c60d..8a045ff 100644 --- a/Makefile +++ b/Makefile @@ -115,9 +115,9 @@ opencode: lint: @echo "๐Ÿ” Linting project files..." - @markdownlint-cli . || true - @nixpkgs-fmt --check . || true + @nix develop -c markdownlint-cli . || true + @nix develop -c nixpkgs-fmt --check . || true lint-fix: @echo "๐ŸŽจ Formatting Nix files..." - @nixpkgs-fmt . + @nix develop -c nixpkgs-fmt . diff --git a/README.md b/README.md index a564487..5f1605a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ๐Ÿช CODE CRISPIES Workshop Infrastructure +# ๐Ÿš€ CODE CRISPIES Workshop Infrastructure Single-participant learning environments with local practice and cloud deployment capabilities. @@ -6,7 +6,7 @@ Single-participant learning environments with local practice and cloud deploymen ```bash # 1. Start local VM for development/testing -make local-vm +make vm-run # 2. Build USB drives for participants make build-usb @@ -21,9 +21,10 @@ make deploy-cloud ### Local Practice (USB/VM) ```bash +setup-traefik # REQUIRED: Setup local proxy first! recipes # Show available apps deploy wordpress # Deploy locally -browser # View at wordpress.workshop.local +browser wordpress # Open directly in Firefox ``` ### Cloud Deployment @@ -34,21 +35,24 @@ abra app new wordpress -S --domain=blog.hopper.codecrispi.es abra app deploy blog.hopper.codecrispi.es ``` -## ๐Ÿ—๏ธ Architecture +## ๐Ÿ—ƒ๏ธ 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.) +- **Wildcard DNS**: `*.workshop.local` resolves to `127.0.0.1` via dnsmasq ## ๐Ÿ’พ USB Environment Pre-configured with: - Docker Swarm + abra installation - SSH client for cloud access +- Wildcard DNS resolution (dnsmasq) - Terminal-first interface (`desktop` command for GUI) -- Helper commands: `recipes`, `deploy`, `connect`, `help` +- Helper commands: `recipes`, `deploy`, `connect`, `browser`, `help` +- Tab completion for all commands Build and flash: ```bash @@ -69,20 +73,76 @@ make status-cloud # Check health ## ๐Ÿ–ฅ๏ธ Local Development ```bash -make local-vm # Start VM -make test-vm # Verify build +make vm-run # Start VM +make vm-build # Verify build ``` The VM simulates the USB experience with identical configuration and commands. -## ๐Ÿ“š Available Commands +## ๐Ÿ“š Complete Recipe Catalog + +Based on Co-op Cloud with quality scoring: + +### โญ Tier 1 - Production Ready (Score 5) +- **gitea** - Self-hosted Git service +- **nextcloud** - Personal cloud storage & collaboration +- **mealie** - Recipe manager and meal planner + +### ๐Ÿ”ง Tier 2 - Stable (Score 4) +- **gotosocial** - Lightweight Fediverse server +- **wordpress** - Website & blog platform + +### ๐Ÿงช Tier 3 - Community (Score 3) +- **collabora** - Online office suite +- **croc** - File transfer tool +- **custom-php** - Custom PHP applications +- **dokuwiki** - Simple wiki software +- **engelsystem** - Event coordination +- **fab-manager** - FabLab management +- **ghost** - Professional publishing platform +- **karrot** - Grassroots initiatives platform +- **lauti** - Calendar software for events +- **loomio** - Collaborative decision-making +- **mattermost** / **mattermost-lts** - Team collaboration +- **mrbs** - Meeting room booking system +- **onlyoffice** - Document editing suite +- **open-inventory** - Inventory management +- **outline** - Team knowledge base +- **owncast** - Self-hosted live streaming +- **rallly** - Group meeting scheduler + +### ๐ŸŒ Extended Catalog +- **Content**: hedgedoc, mediawiki, seafile +- **Communication**: jitsi-meet, matrix-synapse, rocketchat +- **Business**: prestashop, invoiceninja, kimai, pretix +- **Development**: drone, n8n, gitlab, jupyter-lab +- **Analytics**: plausible, matomo, uptime-kuma, grafana +- **Media & Social**: peertube, funkwhale, mastodon, pixelfed, jellyfin + +## ๐Ÿ“š Enhanced Commands **In USB/VM environments**: -- `recipes` - Show Co-op Cloud catalog -- `deploy ` - Deploy locally (e.g., `deploy wordpress`) -- `connect ` - SSH to cloud server +- `setup-traefik` - **REQUIRED FIRST**: Setup local DNS proxy +- `recipes` - Show complete Co-op Cloud catalog +- `deploy ` - Deploy locally with tab completion +- `browser [app]` - Launch Firefox [to specific app] +- `connect ` - SSH to cloud server with tab completion - `desktop` - Start GUI session -- `browser` - Launch Firefox +- `help` - Show all commands and debug info + +**Examples**: +```bash +# Deploy and open WordPress +deploy wordpress +browser wordpress # Opens http://wordpress.workshop.local + +# Just open browser +browser # Opens blank page + +# Use tab completion +deploy # Shows all available recipes +connect # Shows all available servers +``` ## ๐Ÿ”ง Prerequisites @@ -97,3 +157,19 @@ The VM simulates the USB experience with identical configuration and commands. make clean # Local artifacts make destroy-cloud # Cloud infrastructure ``` + +## ๐Ÿ” Troubleshooting + +```bash +# Check DNS resolution +dig @127.0.0.1 test.workshop.local + +# Check running services +docker service ls + +# Check DNS service +systemctl status dnsmasq + +# Restart if needed +sudo systemctl restart dnsmasq +``` diff --git a/common.nix b/common.nix index 3a2aa27..b7594bd 100644 --- a/common.nix +++ b/common.nix @@ -8,6 +8,27 @@ let makeUsbBootable = true; }; }; + + # Complete Co-op Cloud recipe list (based on your ABRA_RECIPES.md and more) + allRecipes = [ + # Tier 1 - Production Ready (Score 5) + "gitea" "mealie" "nextcloud" + + # Tier 2 - Stable (Score 4) + "gotosocial" "wordpress" + + # Tier 3 - Community (Score 3) + "collabora" "croc" "custom-php" "dokuwiki" "engelsystem" "fab-manager" + "ghost" "karrot" "lauti" "loomio" "mattermost" "mattermost-lts" "mrbs" + "onlyoffice" "open-inventory" "outline" "owncast" "rallly" + + # Additional recipes from Co-op Cloud catalog + "hedgedoc" "mediawiki" "seafile" "jitsi-meet" "matrix-synapse" + "rocketchat" "prestashop" "invoiceninja" "kimai" "pretix" + "drone" "n8n" "gitlab" "jupyter-lab" "plausible" "matomo" + "uptime-kuma" "grafana" "peertube" "funkwhale" "mastodon" + "pixelfed" "jellyfin" + ]; in isoConfig // { @@ -19,13 +40,36 @@ isoConfig // { hostName = if isLiveIso then "workshop-live" else "workshop-vm"; }; + # Enable dnsmasq for wildcard DNS resolution + services.dnsmasq = { + enable = true; + settings = { + # Wildcard: *.workshop.local -> 127.0.0.1 + address = [ + "/.workshop.local/127.0.0.1" + ]; + # Don't forward queries for .local domains upstream + local = [ + "/workshop.local/" + ]; + # Listen on all interfaces + listen-address = "127.0.0.1"; + # Don't read /etc/hosts (we want full control) + no-hosts = true; + }; + }; + + # Configure NetworkManager to use our dnsmasq + networking.networkmanager.dns = "dnsmasq"; + networking.nameservers = [ "127.0.0.1" ]; + # Enable Docker for local development virtualisation.docker.enable = true; services.getty.autologinUser = "workshop"; users.users.workshop = { isNormalUser = true; - shell = pkgs.bash; # Simple bash instead of zsh + shell = pkgs.bash; extraGroups = [ "networkmanager" "wheel" "docker" ]; password = ""; }; @@ -46,22 +90,26 @@ isoConfig // { jq tree nano + dnsutils + dig # For DNS debugging ]; - # Auto-install abra on boot + # Auto-install abra and setup Docker Swarm systemd.services.workshop-abra-setup = { wantedBy = [ "multi-user.target" ]; - after = [ "network-online.target" "docker.service" ]; + after = [ "network-online.target" "docker.service" "dnsmasq.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 + # Wait for network, Docker, and DNS + for i in {1..20}; do + if ${pkgs.curl}/bin/curl -s --max-time 5 google.com >/dev/null 2>&1 && \ + ${pkgs.docker}/bin/docker info >/dev/null 2>&1 && \ + ${pkgs.dnsutils}/bin/dig @127.0.0.1 test.workshop.local +short | grep -q "127.0.0.1"; then break fi - sleep 3 + sleep 2 done # Install abra for workshop user @@ -71,11 +119,21 @@ isoConfig // { 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 + # Initialize Docker Swarm with retry logic + for i in {1..5}; do + if ${pkgs.docker}/bin/docker swarm init --advertise-addr 127.0.0.1 2>/dev/null; then + break + elif ${pkgs.docker}/bin/docker info | grep -q "Swarm: active"; then + break + fi + sleep 2 + done - # Add workshop user to docker group + # Ensure workshop user is in docker group usermod -aG docker workshop + + # Create Docker network for local development + ${pkgs.docker}/bin/docker network create --driver bridge workshop-net 2>/dev/null || true ''; serviceConfig = { Type = "oneshot"; @@ -84,52 +142,116 @@ isoConfig // { }; }; - # Simple bash configuration with custom functions + # Enhanced bash configuration with complete recipe support programs.bash = { interactiveShellInit = '' # Workshop welcome and command definitions - echo "CODE CRISPIES Workshop Environment" + echo "๐Ÿš€ CODE CRISPIES Workshop Environment" echo "Mode: Local Development + Cloud Access" echo "" echo "๐Ÿ  Local Development:" - echo " recipes - Show available app recipes" - echo " deploy - Deploy app locally (e.g., deploy wordpress)" - echo " setup-traefik - Setup local Traefik (required first!)" - echo " browser - Launch Firefox" - echo " desktop - Start GUI session" + echo " setup-traefik - Setup local Traefik (REQUIRED FIRST!)" + echo " recipes - Show available app recipes" + echo " deploy - Deploy app locally (e.g., deploy wordpress)" + echo " browser [recipe] - Launch Firefox [to specific app]" + echo " desktop - Start GUI session" echo "" echo "โ˜๏ธ Cloud Access:" echo " Available servers:" ${builtins.concatStringsSep "\n" (map (name: "echo \" - ${name}.codecrispi.es\"" ) cloudServerNames)} - echo " connect - SSH to cloud server" + echo " connect - SSH to cloud server" echo "" - echo "๐Ÿ“š Commands: setup-traefik | recipes | deploy | connect | browser | desktop | help" + echo "๐Ÿ“š Commands: setup-traefik | recipes | deploy | browser | connect | desktop | help" # Ensure abra is in PATH export PATH="$HOME/.local/bin:$PATH" + # Complete recipe list for bash completion + ALL_RECIPES="${builtins.concatStringsSep " " allRecipes}" + + # Enable tab completion for deploy and browser commands + _workshop_completion() { + local cur prev opts + COMPREPLY=() + cur="''${COMP_WORDS[COMP_CWORD]}" + prev="''${COMP_WORDS[COMP_CWORD-1]}" + + case "''${prev}" in + deploy|browser) + opts="$ALL_RECIPES" + COMPREPLY=( $(compgen -W "''${opts}" -- ''${cur}) ) + return 0 + ;; + connect) + opts="${builtins.concatStringsSep " " cloudServerNames}" + COMPREPLY=( $(compgen -W "''${opts}" -- ''${cur}) ) + return 0 + ;; + esac + } + complete -F _workshop_completion deploy browser connect + setup-traefik() { echo "๐Ÿ”ง Setting up local Traefik proxy..." if ! command -v abra &> /dev/null; then - echo "โŒ Abra not found. Run 'sudo systemctl restart workshop-abra-setup'" - return 1 + echo "โŒ Abra not found. Installing..." + sudo systemctl restart workshop-abra-setup + sleep 5 + export PATH="$HOME/.local/bin:$PATH" fi - abra app new traefik -S --domain=traefik.workshop.local + # Test DNS resolution + if ! dig @127.0.0.1 test.workshop.local +short | grep -q "127.0.0.1"; then + echo "โš ๏ธ DNS not ready, restarting dnsmasq..." + sudo systemctl restart dnsmasq + sleep 2 + fi + + # Ensure Docker Swarm is ready + if ! docker info 2>/dev/null | grep -q "Swarm: active"; then + echo "๐Ÿ”„ Initializing Docker Swarm..." + docker swarm init --advertise-addr 127.0.0.1 || true + fi + + # Create abra context if not exists + if ! abra server ls 2>/dev/null | grep -q "workshop-local"; then + echo "๐Ÿ“ Creating local abra context..." + abra server add workshop-local docker://localhost --local + fi + + echo "๐Ÿš€ Deploying Traefik..." + abra app new traefik -S --domain=traefik.workshop.local --server=workshop-local abra app deploy traefik.workshop.local - echo "โœ… Traefik deployed! Dashboard: http://traefik.workshop.local" - echo "๐Ÿš€ Now you can deploy apps with 'deploy '" + # Wait for Traefik to be ready + echo "โณ Waiting for Traefik to start..." + for i in {1..30}; do + if curl -s http://traefik.workshop.local >/dev/null 2>&1; then + break + fi + sleep 2 + done + + if curl -s http://traefik.workshop.local >/dev/null 2>&1; then + echo "โœ… Traefik deployed! Dashboard: http://traefik.workshop.local" + echo "๐Ÿš€ Now you can deploy apps with 'deploy '" + echo "๐ŸŒ DNS test: $(dig @127.0.0.1 traefik.workshop.local +short)" + else + echo "โš ๏ธ Traefik deployed but may still be starting..." + echo "๐Ÿ” Debug: docker service ls | systemctl status dnsmasq" + fi } deploy() { if [ -z "$1" ]; then echo "Usage: deploy " echo "Example: deploy wordpress" - echo "Run 'recipes' to see available options" + echo "Available recipes: $ALL_RECIPES" + echo "" + echo "๐Ÿ” Use tab completion or run 'recipes' for categorized list" return 1 fi @@ -143,95 +265,143 @@ isoConfig // { echo "โŒ Abra not found. Run 'sudo systemctl restart workshop-abra-setup'" return 1 fi + + # Check if Traefik is running + if ! curl -s http://traefik.workshop.local >/dev/null 2>&1; then + echo "โš ๏ธ Traefik not detected. Running setup first..." + setup-traefik + fi - abra app new "$recipe" -S --domain="$domain" + echo "๐Ÿ“ฆ Creating app: $recipe" + abra app new "$recipe" -S --domain="$domain" --server=workshop-local + + echo "๐Ÿš€ Deploying app: $domain" abra app deploy "$domain" - echo "โœ… Deployed! Access at: http://$domain" - echo "๐ŸŒ Open browser with: browser" + echo "โณ Waiting for deployment..." + for i in {1..60}; do + if curl -s http://$domain >/dev/null 2>&1; then + echo "โœ… Deployed! Access at: http://$domain" + echo "๐ŸŒ Quick launch: browser $recipe" + return 0 + fi + sleep 3 + done + + echo "โš ๏ธ Deployment completed but app may still be starting..." + echo "๐Ÿ” Debug: docker service ls | dig @127.0.0.1 $domain +short" + echo "๐ŸŒ Try: browser $recipe (in a few moments)" } connect() { - [ -z "$1" ] && { echo "Usage: connect "; return 1; } - echo "Connecting to $1.codecrispi.es..." + [ -z "$1" ] && { echo "Usage: connect "; echo "Available: ${builtins.concatStringsSep " " cloudServerNames}"; return 1; } + echo "๐Ÿ”Œ Connecting to $1.codecrispi.es..." ssh -o StrictHostKeyChecking=no workshop@$1.codecrispi.es } - recipes() { - echo "Available Co-op Cloud Recipes:" - echo "" - echo "๐Ÿ“ Content Management:" - echo " wordpress ghost hedgedoc dokuwiki mediawiki" - echo "" - echo "๐Ÿ“ File & Collaboration:" - echo " nextcloud seafile collabora onlyoffice" - echo "" - echo "๐Ÿ’ฌ Communication:" - echo " jitsi-meet matrix-synapse rocketchat mattermost" - echo "" - echo "๐Ÿ›’ E-commerce & Business:" - echo " prestashop invoiceninja kimai pretix" - echo "" - echo "โš™๏ธ Development & Tools:" - echo " gitea drone n8n gitlab jupyter-lab" - echo "" - echo "๐Ÿ“Š Analytics & Monitoring:" - echo " plausible matomo uptime-kuma grafana" - echo "" - echo "๐ŸŽต Media & Social:" - echo " peertube funkwhale mastodon pixelfed jellyfin" - echo "" - echo "๐Ÿš€ Local Deploy: deploy " - echo "โ˜๏ธ Cloud Deploy: connect then use abra commands" - echo "๐Ÿ“– Browse all: https://recipes.coopcloud.tech" - } - browser() { - echo "๐ŸŒ Starting Firefox..." + local target_url="about:blank" + + if [ -n "$1" ]; then + # Specific app requested + target_url="http://$1.workshop.local" + echo "๐ŸŒ Opening $1 at $target_url" + else + echo "๐ŸŒ Opening Firefox browser" + fi + if [ -n "$DISPLAY" ]; then - firefox & + firefox "$target_url" & else echo "โŒ No GUI session. Run 'desktop' first" + echo "๐ŸŒ Target was: $target_url" fi } - desktop() { - echo "๐Ÿ–ฅ๏ธ Starting GUI session..." - if command -v startx &> /dev/null; then - if [ -z "$DISPLAY" ]; then - startx & - export DISPLAY=:0 - sleep 3 - echo "โœ… GUI started. Check QEMU window or run 'browser'" - else - echo "โ„น๏ธ GUI already running" - fi - else - echo "๐Ÿ’ก GUI available in QEMU window (Alt+Tab to switch)" - echo "๐Ÿ–ฑ๏ธ Click on QEMU graphics window to use desktop" - fi - } + recipes() { + echo "๐Ÿ“š Complete Co-op Cloud Recipe Catalog:" + echo "" + echo "โญ Tier 1 - Production Ready (Score 5):" + echo " gitea mealie nextcloud" + echo "" + echo "๐Ÿ”ง Tier 2 - Stable (Score 4):" + echo " gotosocial wordpress" + echo "" + echo "๐Ÿงช Tier 3 - Community (Score 3):" + echo " collabora croc custom-php dokuwiki engelsystem" + echo " fab-manager ghost karrot lauti loomio mattermost" + echo " mattermost-lts mrbs onlyoffice open-inventory outline" + echo " owncast rallly" + echo "" + echo "๐ŸŒ Extended Catalog:" + echo " Content: hedgedoc mediawiki seafile" + echo " Chat: jitsi-meet matrix-synapse rocketchat" + echo " Business: prestashop invoiceninja kimai pretix" + echo " Dev Tools: drone n8n gitlab jupyter-lab" + echo " Analytics: plausible matomo uptime-kuma grafana" + echo " Media: peertube funkwhale mastodon pixelfed jellyfin" + echo "" + echo "๐Ÿš€ Usage:" + echo " deploy - Deploy locally" + echo " browser - Open app in browser" + echo " ๐Ÿ“– Full catalog: https://recipes.coopcloud.tech" + echo "" + echo "๐Ÿ’ก Use tab completion: type 'deploy ' or 'browser '" + } + + desktop() { + echo "๐Ÿ–ฅ๏ธ Starting GUI session..." + if command -v startx &> /dev/null; then + if [ -z "$DISPLAY" ]; then + startx & + export DISPLAY=:0 + sleep 3 + echo "โœ… GUI started. Check QEMU window or run 'browser'" + else + echo "โ„น๏ธ GUI already running" + fi + else + echo "๐Ÿ’ก GUI available in QEMU window (Alt+Tab to switch)" + echo "๐Ÿ–ฑ๏ธ Click on QEMU graphics window to use desktop" + fi + } help() { - echo "CODE CRISPIES Workshop Commands:" + echo "๐Ÿš€ CODE CRISPIES Workshop Commands:" echo "" echo "๐Ÿ  Local Development:" - echo " setup-traefik - Setup local Traefik proxy (required first!)" - 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 " setup-traefik - Setup local Traefik proxy (REQUIRED FIRST!)" + echo " recipes - Show all available app recipes" + echo " deploy - Deploy app locally (e.g., deploy wordpress)" + echo " browser [recipe] - Launch Firefox [to specific app]" + echo " desktop - Start GUI desktop session" echo "" echo "โ˜๏ธ Cloud Access:" - echo " connect - SSH to cloud server (e.g., connect hopper)" + echo " connect - SSH to cloud server (e.g., connect hopper)" echo "" echo "Available servers: ${builtins.concatStringsSep " " cloudServerNames}" echo "" echo "๐Ÿ“š Learning Flow:" echo " 1. First time: setup-traefik" - echo " 2. Try local: recipes โ†’ deploy wordpress โ†’ browser" + echo " 2. Try local: recipes โ†’ deploy wordpress โ†’ browser wordpress" echo " 3. Try cloud: connect hopper โ†’ same abra commands" + echo "" + echo "๐Ÿ” Debug Commands:" + echo " docker service ls - Check running services" + echo " dig @127.0.0.1 app.workshop.local - Test DNS resolution" + echo " systemctl status dnsmasq - Check DNS service" + echo "" + echo "๐Ÿ’ก Tab completion available for deploy, browser, connect commands" } + + # Welcome DNS test + if command -v dig &> /dev/null; then + if dig @127.0.0.1 test.workshop.local +short 2>/dev/null | grep -q "127.0.0.1"; then + echo "โœ… DNS wildcard ready: *.workshop.local โ†’ 127.0.0.1" + else + echo "โš ๏ธ DNS not ready yet, services may be starting..." + fi + fi ''; };