From cc8e8dca932ef4ec01a4b3e74422f26764e25056 Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Sun, 17 Aug 2025 01:45:34 +0200 Subject: [PATCH] fix: simplify systemd service script for workshop setup --- common.nix | 417 +++++++++++++++++++++++------------------------------ 1 file changed, 180 insertions(+), 237 deletions(-) diff --git a/common.nix b/common.nix index e20e735..d702ec2 100644 --- a/common.nix +++ b/common.nix @@ -9,14 +9,14 @@ let }; }; - # Complete Co-op Cloud recipe list (based on your ABRA_RECIPES.md and more) + # Complete Co-op Cloud recipe list allRecipes = [ # Tier 1 - Production Ready (Score 5) "gitea" "mealie" "nextcloud" - # Tier 2 - Stable (Score 4) + # Tier 2 - Stable (Score 4) "gotosocial" "wordpress" @@ -40,7 +40,7 @@ let "owncast" "rallly" - # Additional recipes from Co-op Cloud catalog + # Extended catalog "hedgedoc" "mediawiki" "seafile" @@ -65,11 +65,12 @@ let "pixelfed" "jellyfin" ]; -in +in isoConfig // { system.stateVersion = "25.05"; + # SSH Configuration services.openssh = { enable = true; settings = { @@ -80,69 +81,55 @@ isoConfig // { ports = [ 22 ]; }; + # Network Configuration networking = { wireless.enable = false; networkmanager = { enable = true; - dns = "none"; + dns = "none"; # We use dnsmasq }; hostName = if isLiveIso then "workshop-live" else "workshop-vm"; - hosts = { - "127.0.0.1" = [ "workshop.local" "localhost" ]; - }; + hosts."127.0.0.1" = [ "workshop.local" "localhost" ]; + nameservers = lib.mkForce [ "127.0.0.1" ]; + firewall.enable = false; # Workshop environment }; - # Configure dnsmasq properly for wildcard DNS + # DNS Configuration - Wildcard *.workshop.local -> 127.0.0.1 services.dnsmasq = { enable = true; settings = { - # Wildcard: *.workshop.local -> 127.0.0.1 address = "/.workshop.local/127.0.0.1"; - - # Use upstream DNS for everything else server = [ "8.8.8.8" "1.1.1.1" ]; - - # Listen on all interfaces (important for VM/container access) listen-address = [ "127.0.0.1" ]; - - # Bind to interfaces bind-interfaces = true; - - # Don't read /etc/hosts for our custom domains - no-hosts = false; - - # Cache settings cache-size = 1000; - log-queries = true; - log-dhcp = true; - - # Local domain handling local = "/workshop.local/"; domain-needed = true; bogus-priv = true; }; }; - # Force system to use our dnsmasq - networking.nameservers = lib.mkForce [ "127.0.0.1" ]; - - # Disable systemd-resolved to avoid conflicts + # Disable systemd-resolved (conflicts with dnsmasq) services.resolved.enable = false; - # Enable Docker for local development + # Container Runtime virtualisation.docker.enable = true; - services.getty.autologinUser = "workshop"; - users.users.root.password = "root"; - users.users.workshop = { - isNormalUser = true; - shell = pkgs.bash; - extraGroups = [ "networkmanager" "wheel" "docker" ]; - password = "workshop"; + # 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 @@ -157,30 +144,27 @@ isoConfig // { tree nano dnsutils - dig # For DNS debugging + dig ]; - # Auto-install abra and setup Docker Swarm + # 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" ]; script = '' - export HOME=/home/workshop - export PATH="/run/current-system/sw/bin:/usr/bin:/bin" - # Wait for network and services with better testing + # Wait for network and services echo "Waiting for services to start..." for i in {1..30}; do - # Test external connectivity - if /run/current-system/sw/bin/curl -s --max-time 3 google.com >/dev/null 2>&1; then + 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 specifically + # Test DNS resolution for i in {1..20}; do - if /run/current-system/sw/bin/nslookup test.workshop.local 127.0.0.1 >/dev/null 2>&1; then + if nslookup test.workshop.local 127.0.0.1 >/dev/null 2>&1; then echo "โœ… Wildcard DNS ready" break fi @@ -189,7 +173,7 @@ isoConfig // { done # Test Docker for i in {1..10}; do - if /run/current-system/sw/bin/docker info >/dev/null 2>&1; then + if docker info >/dev/null 2>&1; then echo "โœ… Docker ready" break fi @@ -198,24 +182,22 @@ isoConfig // { # Install abra for workshop user if [ ! -f /home/workshop/.local/bin/abra ]; then echo "๐Ÿš€ Installing abra for user workshop..." - /usr/bin/su - workshop -c "mkdir -p /home/workshop/.local/bin" - # Run installer and log output - install_log="/tmp/abra-install.log" - /usr/bin/su - workshop -c "bash -c \"cd /home/workshop && /run/current-system/sw/bin/curl -fsSL https://install.abra.coopcloud.tech | bash\"" &> "$install_log" + sudo -u workshop bash -c "mkdir -p /home/workshop/.local/bin" + sudo -u workshop bash -c "curl -fsSL https://install.abra.coopcloud.tech | bash" if [ -f /home/workshop/.local/bin/abra ]; then echo "โœ… abra installed successfully." else - echo "โŒ abra installation failed. See logs: cat $install_log" + echo "โŒ abra installation failed." fi else echo "โœ… abra already installed." fi # Initialize Docker Swarm echo "๐Ÿ”„ Checking Docker Swarm status..." - if ! /run/current-system/sw/bin/docker info | grep -q "Swarm: active"; then + if ! docker info | grep -q "Swarm: active"; then echo "๐Ÿ”ฅ Initializing Docker Swarm..." - /run/current-system/sw/bin/docker swarm init --advertise-addr 127.0.0.1 2>/dev/null || true - if /run/current-system/sw/bin/docker info | grep -q "Swarm: active"; then + 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." @@ -225,22 +207,18 @@ isoConfig // { fi # Ensure workshop user is in docker group echo "๐Ÿ”„ Ensuring workshop user is in docker group..." - /usr/bin/usermod -aG docker workshop + 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 - # Create proper abra server configuration - if [ ! -f /home/workshop/.abra/servers/workshop.local.env ]; then - /usr/bin/su - workshop -c "mkdir -p /home/workshop/.abra/servers/" - fi # Set up autocomplete if command -v abra &> /dev/null; then - /run/current-system/sw/bin/su - workshop -c "source <(/home/workshop/.local/bin/abra autocomplete bash)" + sudo -u workshop bash -c "source <(/home/workshop/.local/bin/abra autocomplete bash)" fi # Test final DNS resolution - if /run/current-system/sw/bin/nslookup test.workshop.local 127.0.0.1; then + if nslookup test.workshop.local 127.0.0.1; then echo "๐ŸŽ‰ All services ready!" else echo "โš ๏ธ DNS may need manual restart: sudo systemctl restart dnsmasq" @@ -250,139 +228,139 @@ isoConfig // { Type = "oneshot"; RemainAfterExit = true; User = "root"; - Environment = [ - "PATH=/run/current-system/sw/bin:/usr/bin:/bin" - ]; }; }; - # Enhanced bash configuration with complete recipe support - programs.bash = { - interactiveShellInit = '' - # Workshop welcome and command definitions + # 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 "" - - # Test DNS immediately on login - if command -v nslookup &> /dev/null; then + + # 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" - echo "๐Ÿ”ง Debug: nslookup test.workshop.local 127.0.0.1" + echo "โš ๏ธ DNS not working! Run: sudo systemctl restart dnsmasq" fi fi - + # 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 + + # Bash Completion Configuration _workshop_completion() { - local cur prev opts + local cur prev COMPREPLY=() cur="''${COMP_WORDS[COMP_CWORD]}" prev="''${COMP_WORDS[COMP_CWORD-1]}" - - case "''${prev}" in + + case "$prev" in deploy|browser) - opts="$ALL_RECIPES" - COMPREPLY=( $(compgen -W "''${opts}" -- ''${cur}) ) + COMPREPLY=($(compgen -W "${recipeList}" -- "$cur")) return 0 ;; connect) - opts="${builtins.concatStringsSep " " cloudServerNames}" - COMPREPLY=( $(compgen -W "''${opts}" -- ''${cur}) ) + COMPREPLY=($(compgen -W "${serverList}" -- "$cur")) return 0 ;; esac } complete -F _workshop_completion deploy browser connect - - setup-traefik() { - echo "๐Ÿ”ง Setting up local Traefik proxy..." - - # Ensure we can SSH to workshop.local first (tutorial requirement) - if ! ssh -o ConnectTimeout=3 -o BatchMode=yes workshop@workshop.local echo "SSH OK" 2>/dev/null; then - echo "โš ๏ธ SSH to workshop.local not working, but continuing with local setup..." - fi - - # DNS check - if ! nslookup traefik.workshop.local 127.0.0.1 >/dev/null 2>&1; then - echo "โŒ DNS not resolving *.workshop.local" - sudo systemctl restart dnsmasq - sleep 3 - fi - - # 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 overlay network..." - docker network create -d overlay proxy - fi - - # Add server (tutorial step) - if ! abra server ls 2>/dev/null | grep -q "workshop.local"; then - echo "๐Ÿ— Adding workshop.local server..." - # Try to add as proper domain first, fallback to --local - abra server add workshop.local 2>/dev/null || abra server add --local - fi - - # Create Traefik app (tutorial step 1) - if ! abra app ls 2>/dev/null | grep -q "traefik"; then - echo "๐Ÿš€ Creating Traefik app..." - abra app new traefik --domain=traefik.workshop.local - fi - - # Configure Traefik (tutorial step 2) - echo "โš™๏ธ Configuring Traefik..." - abra app config traefik.workshop.local - - # Deploy Traefik (tutorial step 3) - echo "๐Ÿ“ฆ Deploying Traefik..." - abra app deploy traefik.workshop.local - - # Wait and verify - 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" - } - + + # 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 + if [[ -z "$1" ]]; then echo "Usage: deploy " - echo "Available recipes: $ALL_RECIPES" + 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 first + + # 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" - # Use correct server name - abra app new "$recipe" --domain="$domain" --server=default -S 2>/dev/null || \ - abra app new "$recipe" --domain="$domain" --server=default - echo "๐Ÿš€ Deploying app: $domain" + 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 @@ -391,144 +369,109 @@ isoConfig // { fi sleep 3 done - + echo "โš ๏ธ Deployment may still be starting..." echo "๐Ÿ” Debug: abra app ps $domain" } - + connect() { - [ -z "$1" ] && { echo "Usage: connect "; echo "Available: ${builtins.concatStringsSep " " cloudServerNames}"; return 1; } + 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 - # Specific app requested + + if [[ -n "$1" ]]; then target_url="http://$1.workshop.local" echo "๐ŸŒ Opening $1 at $target_url" else - echo "๐ŸŒ Opening Firefox browser" + echo "๐ŸŒ Opening Firefox browser" fi - - if [ -n "$DISPLAY" ]; then + + 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 (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 "โญ 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 app in browser" + echo " browser - Open in browser" echo " ๐Ÿ“– Full catalog: https://recipes.coopcloud.tech" echo "" - echo "๐Ÿ’ก Use tab completion: type 'deploy ' or 'browser '" + echo "๐Ÿ’ก Tab completion: deploy or browser " } - + desktop() { echo "๐Ÿ–ฅ๏ธ Starting GUI session..." - if command -v startx &> /dev/null; then - if [ -z "$DISPLAY" ]; then + if command -v startx >/dev/null 2>&1; then + if [[ -z "$DISPLAY" ]]; then startx & export DISPLAY=:0 sleep 3 - echo "โœ… GUI started. Check QEMU window or run 'browser'" + echo "โœ… GUI started" else - echo "โ„น๏ธ GUI already running" + 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" + echo "๐Ÿ’ก GUI available in QEMU window" fi } - + abra-status() { - echo "๐Ÿ” Checking workshop-abra-setup service status..." - systemctl status workshop-abra-setup + echo "๐Ÿ” Workshop setup service status:" + systemctl status workshop-abra-setup --no-pager echo "" - if [ -f /tmp/abra-install.log ]; then - echo "๐Ÿ“š Last abra installation log (/tmp/abra-install.log):" - cat /tmp/abra-install.log - else - echo "โ„น๏ธ No abra installation log found at /tmp/abra-install.log" - fi - echo "" - echo "๐Ÿ’ก To check if abra is in your PATH: which abra" - echo "๐Ÿ’ก To check abra version: abra --version" + echo "๐Ÿ’ก Commands: which abra | abra --version" } - + help() { 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 [recipe] - Launch Firefox [to specific app]" - echo " desktop - Start GUI desktop session" - echo " abra-status - Check the status of the abra setup service" + 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 (e.g., connect hopper)" + echo " connect - SSH to cloud server" + echo " Available: ${serverList}" echo "" - echo "Available servers: ${builtins.concatStringsSep " " cloudServerNames}" + 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. First time: setup-traefik" - 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" + 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; }; - - # Don't auto-start GUI, let user choose - systemd.user.services.workshop-welcome = { - wantedBy = [ "default.target" ]; - script = '' - echo "Welcome! Run 'desktop' to start GUI session" - ''; - serviceConfig.Type = "oneshot"; - }; }