{ pkgs, lib ? pkgs.lib, cloudServerNames, isLiveIso ? false, ... }: let # Only include isoImage config when building ISO isoConfig = lib.optionalAttrs isLiveIso { isoImage = { makeEfiBootable = true; makeUsbBootable = true; }; }; # Complete Co-op Cloud recipe list 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" # Extended 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 // { system.stateVersion = "25.05"; # SSH Configuration services.openssh = { enable = true; settings = { PermitRootLogin = "no"; PasswordAuthentication = true; PubkeyAuthentication = true; }; ports = [ 22 ]; }; # Network Configuration networking = { wireless.enable = false; networkmanager = { enable = true; dns = "none"; # We use dnsmasq }; hostName = if isLiveIso then "workshop-live" else "workshop-vm"; hosts."127.0.0.1" = [ "workshop.local" "localhost" ]; nameservers = lib.mkForce [ "127.0.0.1" ]; firewall.enable = false; # Workshop environment }; # DNS Configuration - Wildcard *.workshop.local -> 127.0.0.1 services.dnsmasq = { enable = true; settings = { address = "/.workshop.local/127.0.0.1"; server = [ "8.8.8.8" "1.1.1.1" ]; listen-address = [ "127.0.0.1" ]; bind-interfaces = true; cache-size = 1000; local = "/workshop.local/"; domain-needed = true; bogus-priv = true; }; }; # Disable systemd-resolved (conflicts with dnsmasq) services.resolved.enable = false; # Container Runtime virtualisation.docker.enable = true; # User Configuration users = { users.root.password = "root"; users.workshop = { isNormalUser = true; shell = pkgs.bash; extraGroups = [ "networkmanager" "wheel" "docker" ]; password = "workshop"; }; }; services.getty.autologinUser = "workshop"; security.sudo.wheelNeedsPassword = false; # System Packages environment.systemPackages = with pkgs; [ openssh curl git networkmanager firefox docker docker-compose bash wget jq tree nano dnsutils dig ]; # Workshop Setup Service - REFACTORED systemd.services.workshop-abra-setup = { wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" "docker.service" "dnsmasq.service" ]; wants = [ "network-online.target" ]; path = with pkgs; [ bash curl dnsutils docker gnugrep shadow coreutils wget ]; script = '' # Wait for network and services echo "Waiting for services to start..." for i in {1..30}; do if curl -s --max-time 3 google.com >/dev/null 2>&1; then echo "โœ… External network ready" break fi sleep 2 done # Test DNS resolution for i in {1..20}; do if nslookup test.workshop.local 127.0.0.1 >/dev/null 2>&1; then echo "โœ… Wildcard DNS ready" break fi echo "๐Ÿ”„ Waiting for DNS... (attempt $i)" sleep 2 done # Test Docker for i in {1..10}; do if docker info >/dev/null 2>&1; then echo "โœ… Docker ready" break fi sleep 2 done # Install abra for workshop user - as root, to /usr/local/bin if [ ! -f /usr/local/bin/abra ]; then echo "๐Ÿš€ Installing abra for root user..." # Download and install abra directly to /usr/local/bin curl -fsSL https://install.abra.coopcloud.tech | bash if [ -f /usr/local/bin/abra ] && [ -x /usr/local/bin/abra ]; then echo "โœ… abra installed successfully to /usr/local/bin/abra" else echo "โŒ abra installation failed." echo "๐Ÿ” Debug: Contents of /usr/local/bin:" ls -la /usr/local/bin/abra 2>/dev/null || echo "File not found" fi else echo "โœ… abra already installed at /usr/local/bin/abra" fi # Initialize Docker Swarm echo "๐Ÿ”„ Checking Docker Swarm status..." if ! docker info | grep -q "Swarm: active"; then echo "๐Ÿ”ฅ Initializing Docker Swarm..." docker swarm init --advertise-addr 127.0.0.1 2>/dev/null || true if docker info | grep -q "Swarm: active"; then echo "โœ… Docker Swarm initialized." else echo "โŒ Docker Swarm initialization failed." fi else echo "โœ… Docker Swarm already active." fi # Ensure workshop user is in docker group (we are root, can use usermod directly) echo "๐Ÿ”„ Ensuring workshop user is in docker group..." usermod -aG docker workshop if id -nG workshop | grep -q "docker"; then echo "โœ… workshop user is in docker group." else echo "โŒ Failed to add workshop user to docker group." fi # Set up autocomplete (skip this for now since we can't run as user easily) # The bash init script will handle abra autocomplete on login # Test final DNS resolution if nslookup test.workshop.local 127.0.0.1; then echo "๐ŸŽ‰ All services ready!" else echo "โš ๏ธ DNS may need manual restart: systemctl restart dnsmasq" fi ''; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; User = "root"; }; }; # Enhanced Bash Configuration with All Features programs.bash.interactiveShellInit = let recipeList = builtins.concatStringsSep " " allRecipes; serverList = builtins.concatStringsSep " " cloudServerNames; in '' # Workshop Environment Welcome echo "๐Ÿš€ CODE CRISPIES Workshop Environment" echo "Mode: Local Development + Cloud Access" echo "" # DNS Health Check if command -v nslookup >/dev/null 2>&1; then if nslookup test.workshop.local 127.0.0.1 >/dev/null 2>&1; then echo "โœ… DNS wildcard ready: *.workshop.local โ†’ 127.0.0.1" else echo "โš ๏ธ DNS not working! Run: sudo systemctl restart dnsmasq" fi fi # Ensure abra is in PATH export PATH="$HOME/.local/bin:$PATH" # Bash Completion Configuration _workshop_completion() { local cur prev COMPREPLY=() cur="''${COMP_WORDS[COMP_CWORD]}" prev="''${COMP_WORDS[COMP_CWORD-1]}" case "$prev" in deploy|browser) COMPREPLY=($(compgen -W "${recipeList}" -- "$cur")) return 0 ;; connect) COMPREPLY=($(compgen -W "${serverList}" -- "$cur")) return 0 ;; esac } complete -F _workshop_completion deploy browser connect # Core Workshop Functions setup-traefik() { echo "๐Ÿ”ง Setting up local Traefik proxy..." # Test SSH capability (tutorial requirement) if ! timeout 3 ssh -o BatchMode=yes workshop@workshop.local echo "SSH OK" 2>/dev/null; then echo "โš ๏ธ SSH to workshop.local not working, continuing with local setup..." fi # Verify DNS if ! nslookup traefik.workshop.local 127.0.0.1 >/dev/null 2>&1; then echo "๐Ÿ”„ Restarting DNS..." sudo systemctl restart dnsmasq sleep 3 fi # Ensure Docker Swarm + proxy network if ! docker info 2>/dev/null | grep -q "Swarm: active"; then echo "๐Ÿ”ฅ Initializing Docker Swarm..." docker swarm init --advertise-addr 127.0.0.1 fi if ! docker network ls | grep -q "proxy"; then echo "๐ŸŒ Creating proxy network..." docker network create -d overlay proxy fi # Add server if ! abra server ls 2>/dev/null | grep -q "workshop.local"; then echo "๐Ÿ—๏ธ Adding workshop.local server..." abra server add workshop.local 2>/dev/null || abra server add --local fi # Create, configure, and deploy Traefik if ! abra app ls 2>/dev/null | grep -q "traefik"; then echo "๐Ÿš€ Creating Traefik app..." abra app new traefik --domain=traefik.workshop.local echo "โš™๏ธ Configuring Traefik..." abra app config traefik.workshop.local echo "๐Ÿ“ฆ Deploying Traefik..." abra app deploy traefik.workshop.local echo "โณ Waiting for Traefik..." for i in {1..30}; do if curl -s http://traefik.workshop.local >/dev/null 2>&1; then echo "โœ… Traefik ready! Dashboard: http://traefik.workshop.local" return 0 fi sleep 2 done echo "โš ๏ธ Traefik may still be starting. Check: abra app logs traefik.workshop.local" else echo "โœ… Traefik already exists" fi } deploy() { if [[ -z "$1" ]]; then echo "Usage: deploy " echo "Available: ${recipeList}" return 1 fi local recipe="$1" local domain="$recipe.workshop.local" echo "๐Ÿš€ Deploying $recipe locally..." echo "Domain: $domain" # Ensure Traefik is running if ! curl -s --max-time 3 http://traefik.workshop.local/ping >/dev/null 2>&1; then echo "โš ๏ธ Traefik not responding. Setting up..." setup-traefik || return 1 fi # Create and deploy app echo "๐Ÿ“ฆ Creating app: $recipe" abra app new "$recipe" --domain="$domain" --server=default 2>/dev/null || \ abra app new "$recipe" --domain="$domain" echo "๐Ÿš€ Deploying: $domain" abra app deploy "$domain" echo "โณ Waiting for deployment..." for i in {1..60}; do if curl -s --max-time 3 http://$domain >/dev/null 2>&1; then echo "โœ… Deployed! Access at: http://$domain" return 0 fi sleep 3 done echo "โš ๏ธ Deployment may still be starting..." echo "๐Ÿ” Debug: abra app ps $domain" } connect() { if [[ -z "$1" ]]; then echo "Usage: connect " echo "Available: ${serverList}" return 1 fi echo "๐Ÿ”Œ Connecting to $1.codecrispi.es..." ssh -o StrictHostKeyChecking=no workshop@$1.codecrispi.es } browser() { local target_url="about:blank" if [[ -n "$1" ]]; then target_url="http://$1.workshop.local" echo "๐ŸŒ Opening $1 at $target_url" else echo "๐ŸŒ Opening Firefox browser" fi if [[ -n "$DISPLAY" ]]; then firefox "$target_url" & else echo "โŒ No GUI session. Run 'desktop' first" echo "๐ŸŒ Target was: $target_url" fi } recipes() { echo "๐Ÿ“š Complete Co-op Cloud Recipe Catalog:" echo "" echo "โญ Tier 1 - Production Ready: gitea mealie nextcloud" echo "๐Ÿ”ง Tier 2 - Stable: gotosocial wordpress" echo "๐Ÿงช Tier 3 - Community: collabora croc dokuwiki ghost loomio..." echo "๐ŸŒ Extended: matrix-synapse rocketchat gitlab n8n mastodon..." echo "" echo "๐Ÿš€ Usage:" echo " deploy - Deploy locally" echo " browser - Open in browser" echo " ๐Ÿ“– Full catalog: https://recipes.coopcloud.tech" echo "" echo "๐Ÿ’ก Tab completion: deploy or browser " } desktop() { echo "๐Ÿ–ฅ๏ธ Starting GUI session..." if command -v startx >/dev/null 2>&1; then if [[ -z "$DISPLAY" ]]; then startx & export DISPLAY=:0 sleep 3 echo "โœ… GUI started" else echo "โ„น๏ธ GUI already running" fi else echo "๐Ÿ’ก GUI available in QEMU window" fi } abra-status() { systemctl status workshop-abra-setup } abra-logs() { journalctl -u workshop-abra-setup -f } help() { echo "๐Ÿš€ CODE CRISPIES Workshop Commands:" echo "" echo "๐Ÿ  Local Development:" echo " setup-traefik - Setup local proxy (REQUIRED FIRST!)" echo " recipes - Show all available apps" echo " deploy - Deploy app locally" echo " browser [recipe] - Launch Firefox [to app]" echo " desktop - Start GUI session" echo "" echo "โ˜๏ธ Cloud Access:" echo " connect - SSH to cloud server" echo " Available: ${serverList}" echo "" echo "๐Ÿ” Debug:" echo " abra-status - Check setup service" echo " docker service ls - List running services" echo " systemctl status dnsmasq - Check DNS" echo "" echo "๐Ÿ“š Learning Flow:" echo " 1. setup-traefik" echo " 2. deploy wordpress" echo " 3. browser wordpress" echo " 4. connect hopper" } ''; # GUI Configuration services.xserver = { enable = true; desktopManager.xfce.enable = true; displayManager.lightdm.enable = true; }; }