{ pkgs, lib ? pkgs.lib, 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) "adapt_authoring" "agora" "alerta" "amusewiki" "authentik" "babybuddy" "backup-bot" "backup-bot-two" "base-row" "baserow" "bonfire" "botamusique" "caddy" "cal" "calibre-web" "capsul" "civicrm-backdrop" "civicrm-wordpress" "collabora" "compy" "container" "croc" "cryptpad" "custom-html" "custom-html-tiny" "custom-php" "dashy" "discourse" "distribution" "docker-hub-rss" "dokuwiki" "drone" "drone-docker-runner" "drutopia" "element-web" "engelsystem" "etherpad" "fab-manager" "farmos" "federatedwiki" "filerun" "filestash" "firefly-iii" "firefly-iii-importer" "fluffychat" "focalboard" "foodsoft" "forgejo-runner" "funkwhale" "gancio" "garage" "ghost" "gitlab" "go-neb" "go-ssb-room" "grafana" "grist" "h5ai" "hedgedoc" "hometown" "hugo" "icecast" "immich" "indentificator" "invidious" "invoiceninja" "jellyfin" "jellyseerr" "jitsi" "jupyter-lab" "kanboard" "karrot" "keycloak" "keycloak-collective-portal" "keyoxide" "kimai" "kutt" "laplace" "lasuite-docs" "lauti" "lemmy" "levelfly" "liberaforms" "limesurvey" "listmonk" "loomio" "mailman3" "mailu" "mastodon" "matomo" "mattermost" "mattermost-lts" "maubot" "mediawiki" "minecraft" "minetest" "miniflux" "minio" "mobilizon" "monica" "monitoring" "monitoring-lite" "monitoring-ng" "mrbs" "mumble" "mycorrhiza" "n8n" "navidrome" "netdata" "nitter" "nocodb" "notea" "ntfy" "oasis" "ohmyform" "onlyoffice" "open-dispatch" "open-inventory" "osticket" "outline" "owncast" "parasol-static-site" "peertube" "pelican" "penpot" "photoprism" "phpservermon" "pixelfed" "plausible" "portainer" "postfix-relay" "pretix" "privatebin" "projectsend" "prowlarr" "qbit" "radarr" "radicale" "rallly" "rauthy" "redmine" "renovate" "restic-rest-server" "rocketchat" "rsshub" "rstudio" "rustdesk-server" "screensy" "seafile" "selfoss" "sextant" "shlink" "singlelink" "snikket" "snowflake" "sonarr" "statping" "statuspal" "strapi" "stream-share" "swarm-cronjob" "swarmpit" "synapse-admin" "traefik" "traefik-cert-dumper" "traefik-forward-auth" "uptime-kuma" "vaultwarden" "vikunja" "voila" "vroom" "wallabag" "weblate" "wekan" "woodpecker" "wordpress-bedrock" "workadventure" "writefreely" "xwiki" "zammad" "znc" "zulip" ]; in isoConfig // { system.stateVersion = "25.05"; # Allow unfree packages in order to install firmware for proprietary hardware (mainly for wifi controller necessary) #nixpkgs.config.allowUnfree = true; # Hardware firmware for WiFi controllers #hardware.enableAllFirmware = true; hardware.enableRedistributableFirmware = true; # Latest kernel for better hardware support #boot.kernelPackages = pkgs.linuxPackages_latest; # Timezone Configuration time.timeZone = "Europe/Berlin"; # SSH Configuration services.openssh = { enable = true; settings = { PermitRootLogin = "no"; PasswordAuthentication = true; PubkeyAuthentication = true; }; ports = [ 22 ]; }; # Network Configuration networking = { networkmanager = { enable = true; wifi.backend = "wpa_supplicant"; # Standard backend for live ISOs dns = "none"; # We use dnsmasq ensureProfiles = { profiles = { "CODE_CRISPIES" = { connection = { id = "CODE_CRISPIES"; type = "wifi"; autoconnect = true; }; wifi = { mode = "infrastructure"; ssid = "CODE_CRISPIES"; }; wifi-security = { key-mgmt = "wpa-psk"; psk = "scienceinthecity2025"; }; ipv4 = { method = "auto"; }; ipv6 = { method = "auto"; }; }; "APSFreeWiFi" = { connection = { id = "APSFreeWiFi"; type = "wifi"; autoconnect = true; }; wifi = { mode = "infrastructure"; ssid = "APSFreeWiFi"; }; wifi-security = { key-mgmt = "none"; }; ipv4 = { method = "auto"; }; ipv6 = { method = "auto"; }; }; }; }; }; 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"; }; }; # SSH key generation for workshop user systemd.services.workshop-ssh-keygen = { description = "Generate SSH key for workshop user for passwordless localhost access"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; path = with pkgs; [ openssh coreutils gnugrep ]; script = '' USER_HOME=/home/workshop SSH_DIR=$USER_HOME/.ssh KEY_FILE=$SSH_DIR/id_ed25519 AUTH_KEYS_FILE=$SSH_DIR/authorized_keys mkdir -p $SSH_DIR chown workshop:workshop $SSH_DIR chmod 700 $SSH_DIR if [ ! -f "$KEY_FILE" ]; then echo "Generating SSH key for workshop user..." ssh-keygen -t ed25519 -f $KEY_FILE -N "" -C "workshop@workshop-vm" chown workshop:workshop $KEY_FILE $KEY_FILE.pub chmod 600 $KEY_FILE chmod 644 $KEY_FILE.pub fi PUB_KEY=$(cat $KEY_FILE.pub) if ! grep -qF -- "$PUB_KEY" "$AUTH_KEYS_FILE" 2>/dev/null; then echo "Adding public key to authorized_keys..." echo "$PUB_KEY" >> $AUTH_KEYS_FILE fi chown workshop:workshop $AUTH_KEYS_FILE chmod 600 $AUTH_KEYS_FILE ''; serviceConfig = { Type = "oneshot"; User = "root"; RemainAfterExit = true; }; }; # Build timestamp service systemd.services.workshop-build-info = { description = "Write build timestamp to /etc/workshop-build-info"; wantedBy = [ "multi-user.target" ]; script = '' echo "$(date '+%Y-%m-%d %H:%M:%S')" > /etc/workshop-build-info ''; serviceConfig = { Type = "oneshot"; User = "root"; RemainAfterExit = true; }; }; services.getty.autologinUser = "workshop"; security.sudo.wheelNeedsPassword = false; # System Packages environment.systemPackages = with pkgs; [ openssh curl git networkmanager networkmanagerapplet # Network Manager GUI for GNOME gnome-control-center # GNOME Settings (includes network panel) docker docker-compose gnome-terminal bash wget jq tree nano dnsutils dig gnutar openssl # Add this for certificate generation # Additional font packages for QEMU #chromium # Add Chromium browser firefox dejavu_fonts liberation_ttf fontconfig ]; # System Setup Service (Root Tasks) systemd.services.workshop-system-setup = { description = "System-level checks for network, DNS, and Docker"; 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 ]; 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 # 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 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 # Final DNS resolution test if nslookup test.workshop.local 127.0.0.1; then echo "๐ŸŽ‰ System services ready!" else echo "โš ๏ธ DNS may need manual restart: systemctl restart dnsmasq" fi ''; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; User = "root"; Environment = [ "TERM=xterm-256color" "HOME=/root" ]; }; }; # Abra Installation Service (System-wide) with Enhanced Verification systemd.services.workshop-abra-install = { description = "Install abra CLI system-wide with retry logic and proper verification"; wantedBy = [ "multi-user.target" ]; after = [ "workshop-system-setup.service" ]; path = with pkgs; [ bash wget curl coreutils gnutar ncurses gzip file gnugrep docker ]; script = '' # Enhanced installation with proper verification export TERM=xterm-256color export HOME=/root # Check if abra is already properly installed (CORRECTED VERIFICATION) if sudo abra --version >/dev/null 2>&1; then echo "โœ… abra already installed and functional" exit 0 fi # Attempt installation with retry logic for attempt in {1..60}; do echo "๐Ÿš€ Installing abra (attempt $attempt/60)..." # Quick internet check before attempting installation if ! curl -s --max-time 3 https://abra.coopcloud.tech >/dev/null 2>&1; then echo "๐ŸŒ No internet connectivity, skipping attempt $attempt" elif curl -fsSL https://install.abra.coopcloud.tech | bash; then # Verify installation success using CORRECT check method if sudo abra --version >/dev/null 2>&1; then echo "โœ… abra installed successfully" # Add to bashrc only once if ! grep -q "/root/.local/bin" /root/.bashrc 2>/dev/null; then echo 'export PATH="$PATH:/root/.local/bin"' >> /root/.bashrc echo "โœ… Added /root/.local/bin to PATH in /root/.bashrc" fi exit 0 else echo "โš ๏ธ Installation script completed but abra not functional" fi else echo "โŒ Installation attempt $attempt failed" fi if [ $attempt -lt 60 ]; then echo "โณ Retrying in 5 seconds..." sleep 5 fi done echo "โŒ abra installation failed after 60 attempts" exit 1 ''; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; User = "root"; Environment = [ "TERM=xterm-256color" "HOME=/root" ]; }; }; # Enhanced Bash Configuration with All Features programs.bash.interactiveShellInit = let recipeList = builtins.concatStringsSep " " allRecipes; in '' # Workshop Environment Welcome echo "๐Ÿš€ SCIENCE IN THE CITY Workshop Environment" echo "Mode: Local Development (Offline Co-op Cloud)" echo "" # Network Health Check echo "๐ŸŒ Network Status:" if systemctl is-active --quiet NetworkManager; then echo "โœ… NetworkManager running" nmcli -t -f STATE general | head -1 else echo "โŒ NetworkManager not running" echo " Start with: sudo systemctl start NetworkManager" fi # 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 /root/.local/bin is in PATH (safety net) if [[ ":$PATH:" != *":/root/.local/bin:"* ]]; then echo "โœ… adding abra to PATH" export PATH="$PATH:/root/.local/bin" fi # Check abra installation if sudo abra >/dev/null 2>&1; then echo "โœ… abra ready: $(sudo which abra)" source <(sudo abra autocomplete bash) 2>/dev/null || true echo "โœ… abra autocomplete enabled" else echo "โš ๏ธ abra not found! Install with: curl -fsSL https://install.abra.coopcloud.tech | bash" fi # Build info echo "โœ… Workshop ISO - NixOS $(nixos-version) - Built: $(cat /etc/workshop-build-info 2>/dev/null || echo 'unknown')" # 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 ;; esac } complete -F _workshop_completion deploy browser abra # Core Workshop Functions setup() { echo "๐Ÿ”ง Setting up LOCAL Co-op Cloud environment..." # Run permission checks first setup_permissions || return 1 # Run setup steps individually setup_dns || return 1 setup_docker || return 1 setup_abra_server || return 1 setup_certificates || return 1 setup_traefik || return 1 echo "๐ŸŽ‰ Setup complete!" } setup_permissions() { echo "๐Ÿ” Checking system permissions and prerequisites..." # Check if running as workshop user if [[ "$(whoami)" != "workshop" ]]; then echo "โš ๏ธ Not running as workshop user (current: $(whoami))" echo " This may cause permission issues. Consider running as workshop user." else echo "โœ… Running as workshop user" fi # Check sudo access if sudo -n true 2>/dev/null; then echo "โœ… Sudo access available (no password required)" else echo "โš ๏ธ Sudo may require password - this could interrupt automated setup" fi # Check Docker group membership if id -nG | grep -q "docker"; then echo "โœ… User is in docker group" else echo "โš ๏ธ User not in docker group - Docker commands may fail" echo " Current groups: $(id -nG)" fi # Check if abra is available via sudo if sudo abra --version >/dev/null 2>&1; then echo "โœ… abra available via sudo: $(sudo which abra)" else echo "โŒ abra not available via sudo" echo " Check: which abra && abra --version" return 1 fi # Check abra server configuration if sudo abra server ls 2>/dev/null | grep -q "default"; then echo "โœ… Abra default server configured" else echo "โš ๏ธ Abra default server not configured - will be set up" fi # Check /tmp permissions if [[ -w "/tmp" ]]; then echo "โœ… /tmp directory is writable" else echo "โŒ /tmp directory is not writable" ls -ld /tmp return 1 fi # Check openssl availability if command -v openssl >/dev/null 2>&1; then echo "โœ… OpenSSL available: $(openssl version | head -1)" else echo "โŒ OpenSSL not found - certificate generation will fail" return 1 fi # Check curl availability if command -v curl >/dev/null 2>&1; then echo "โœ… curl available for health checks" else echo "โš ๏ธ curl not found - health checks may not work properly" fi echo "๐ŸŽฏ Permission checks complete!" } setup_dns() { echo "๐ŸŒ Step 1: Verifying DNS configuration..." if ! nslookup traefik.workshop.local 127.0.0.1 >/dev/null 2>&1; then echo "๐Ÿ”„ DNS not working, restarting dnsmasq..." sudo systemctl restart dnsmasq sleep 3 # Test again if nslookup traefik.workshop.local 127.0.0.1 >/dev/null 2>&1; then echo "โœ… DNS restarted successfully" else echo "โŒ DNS restart failed" return 1 fi else echo "โœ… DNS working correctly" fi } setup_docker() { echo "๐Ÿณ Step 2: Setting up Docker Swarm and networks..." # Check Docker status if ! docker info 2>/dev/null | grep -q "Swarm: active"; then echo "๐Ÿ”ฅ Initializing Docker Swarm..." if docker swarm init --advertise-addr 127.0.0.1; then echo "โœ… Docker Swarm initialized" else echo "โŒ Docker Swarm initialization failed" return 1 fi else echo "โœ… Docker Swarm already active" fi # Check proxy network if ! docker network ls | grep -q "proxy"; then echo "๐ŸŒ Creating proxy network..." if docker network create -d overlay proxy; then echo "โœ… Proxy network created" else echo "โŒ Proxy network creation failed" return 1 fi else echo "โœ… Proxy network exists" fi } setup_abra_server() { echo "๐Ÿ—„๏ธ Step 3: Setting up Abra server..." if ! sudo abra server ls 2>/dev/null | grep -q "default"; then echo "๐Ÿ—„๏ธ Adding LOCAL server to abra..." if sudo abra server add --local; then echo "โœ… Local server registered" else echo "โŒ Failed to add local server" return 1 fi else echo "โœ… Abra server already configured" fi } setup_certificates() { echo "๐Ÿ” Step 4: Generating self-signed certificates..." setup_certificates_dir || return 1 setup_certificates_generate || return 1 setup_certificates_verify || return 1 # Export CERT_DIR for use in setup_traefik export CERT_DIR } setup_certificates_dir() { echo "๐Ÿ“ Creating certificate directory..." CERT_DIR="/tmp/workshop-certs" echo " Target directory: $CERT_DIR" # Check if directory already exists and clean it up if [[ -d "$CERT_DIR" ]]; then echo " ๐Ÿงน Cleaning up existing certificate directory..." rm -rf "$CERT_DIR" || { echo "โŒ Failed to remove existing directory" return 1 } fi # Create fresh directory if mkdir -p "$CERT_DIR"; then echo "โœ… Certificate directory created" else echo "โŒ Failed to create certificate directory" echo " Current user: $(whoami)" echo " User ID: $(id)" echo " /tmp permissions: $(ls -ld /tmp)" return 1 fi # Verify directory permissions echo "๐Ÿ” Verifying directory permissions..." ls -la /tmp/ | grep workshop-certs || { echo "โŒ Directory not found in /tmp listing" return 1 } local dir_perms=$(stat -c "%a" "$CERT_DIR" 2>/dev/null || echo "unknown") echo " Directory permissions: $dir_perms" echo " Directory owner: $(stat -c "%U:%G" "$CERT_DIR" 2>/dev/null || echo "unknown")" } setup_certificates_generate() { echo "๐Ÿ”‘ Generating self-signed certificate..." CERT_FILE="$CERT_DIR/workshop.crt" KEY_FILE="$CERT_DIR/workshop.key" echo " Certificate file: $CERT_FILE" echo " Key file: $KEY_FILE" # Check if openssl is available if ! command -v openssl >/dev/null 2>&1; then echo "โŒ OpenSSL not found in PATH" which openssl || echo " openssl command not found" return 1 fi echo " OpenSSL version: $(openssl version)" # Check if certificate already exists if [[ -f "$CERT_FILE" ]]; then echo " โš ๏ธ Certificate file already exists, removing..." rm -f "$CERT_FILE" "$KEY_FILE" || { echo "โŒ Failed to remove existing certificate files" return 1 } fi # Generate certificate following abra guidelines echo " Generating RSA key and certificate (abra-compatible)..." if openssl req -x509 -out "$CERT_FILE" -keyout "$KEY_FILE" \ -newkey rsa:2048 -nodes -sha256 -days 365 \ -subj "/CN=*.workshop.local" -extensions EXT -config <( \ printf "[dn]\nCN=*.workshop.local\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:*.workshop.local,DNS:workshop.local,DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth"); then echo "โœ… Certificate generation completed successfully (abra-compatible)" else echo "โŒ Certificate generation failed" echo " OpenSSL exit code: $?" return 1 fi } setup_certificates_verify() { echo "๐Ÿ” Verifying certificate files..." CERT_FILE="$CERT_DIR/workshop.crt" KEY_FILE="$CERT_DIR/workshop.key" # Check if files exist if [[ ! -f "$CERT_FILE" ]]; then echo "โŒ Certificate file not found: $CERT_FILE" ls -la "$CERT_DIR" || echo " Directory listing failed" return 1 fi if [[ ! -f "$KEY_FILE" ]]; then echo "โŒ Key file not found: $KEY_FILE" ls -la "$CERT_DIR" || echo " Directory listing failed" return 1 fi echo "โœ… Certificate files created successfully" # Show file details echo " Certificate file details:" ls -la "$CERT_FILE" echo " Key file details:" ls -la "$KEY_FILE" # Verify certificate content echo " Verifying certificate content..." if openssl x509 -in "$CERT_FILE" -text -noout >/dev/null 2>&1; then echo "โœ… Certificate is valid X.509 format" # Show certificate details echo " Certificate subject:" openssl x509 -in "$CERT_FILE" -subject -noout 2>/dev/null || echo " Could not read certificate subject" echo " Certificate issuer:" openssl x509 -in "$CERT_FILE" -issuer -noout 2>/dev/null || echo " Could not read certificate issuer" echo " Certificate validity:" openssl x509 -in "$CERT_FILE" -dates -noout 2>/dev/null || echo " Could not read certificate dates" echo " Certificate extensions:" openssl x509 -in "$CERT_FILE" -text -noout 2>/dev/null | grep -A 5 "Subject Alternative Name" || echo " No SAN extension found" else echo "โŒ Certificate file is not valid" return 1 fi # Verify key content if openssl rsa -in "$KEY_FILE" -check -noout >/dev/null 2>&1; then echo "โœ… Private key is valid" else echo "โŒ Private key is invalid" return 1 fi # Verify certificate matches key if openssl x509 -in "$CERT_FILE" -noout -modulus 2>/dev/null | openssl md5 >/dev/null 2>&1 && \ openssl rsa -in "$KEY_FILE" -noout -modulus 2>/dev/null | openssl md5 >/dev/null 2>&1; then echo "โœ… Certificate and key match" else echo "โš ๏ธ Could not verify certificate/key match (non-critical)" fi echo "๐ŸŽ‰ Certificate generation and verification complete!" } setup_traefik() { echo "๐Ÿš€ Step 5: Setting up Traefik..." setup_traefik_app || return 1 setup_traefik_config || return 1 setup_traefik_secrets || return 1 setup_traefik_deploy || return 1 setup_traefik_wait || return 1 } setup_traefik_app() { echo "๐Ÿ“ฆ Checking Traefik app..." if ! sudo abra app ls 2>/dev/null | grep -q "traefik"; then echo "๐Ÿš€ Creating Traefik app for OFFLINE use..." echo " Command: sudo abra app new traefik --domain=traefik.workshop.local --server=default" if sudo abra app new traefik --domain=traefik.workshop.local --server=default; then echo "โœ… Traefik app created successfully" else echo "โŒ Failed to create Traefik app" echo " abra exit code: $?" sudo abra app ls 2>&1 || echo " Could not list apps" return 1 fi else echo "โœ… Traefik app already exists" fi } setup_traefik_config() { echo "โš™๏ธ Configuring Traefik for offline mode..." TRAEFIK_ENV="/root/.abra/servers/default/traefik.workshop.local.env" echo " Config file: $TRAEFIK_ENV" # Check if config file exists if [[ -f "$TRAEFIK_ENV" ]]; then echo " โš ๏ธ Config file already exists, backing up..." cp "$TRAEFIK_ENV" "$TRAEFIK_ENV.backup" || echo " Backup failed, continuing..." fi # Create offline-friendly traefik configuration echo " Writing offline configuration..." if sudo tee -a "$TRAEFIK_ENV" >/dev/null </dev/null || echo " Parent directory not accessible" return 1 fi } setup_traefik_secrets() { echo "๐Ÿ“‹ Installing self-signed certificates as Docker secrets..." # Verify certificate files exist if [[ ! -f "$CERT_DIR/workshop.crt" ]]; then echo "โŒ Certificate file not found: $CERT_DIR/workshop.crt" ls -la "$CERT_DIR" 2>/dev/null || echo " Certificate directory not accessible" return 1 fi if [[ ! -f "$CERT_DIR/workshop.key" ]]; then echo "โŒ Key file not found: $CERT_DIR/workshop.key" ls -la "$CERT_DIR" 2>/dev/null || echo " Certificate directory not accessible" return 1 fi echo " Certificate files verified:" ls -la "$CERT_DIR/workshop.crt" "$CERT_DIR/workshop.key" # Insert SSL certificate secret echo " ๐Ÿ” Inserting SSL certificate secret..." echo " Command: sudo abra app secret insert traefik.workshop.local ssl_cert v1" if sudo abra app secret insert traefik.workshop.local ssl_cert v1 -f "$CERT_DIR/workshop.crt"; then echo "โœ… SSL certificate secret inserted successfully" else echo "โŒ Failed to insert SSL certificate secret" echo " abra exit code: $?" echo " Checking abra app status..." sudo abra app ls 2>&1 || echo " Could not list apps" echo " Checking certificate file..." file "$CERT_DIR/workshop.crt" 2>/dev/null || echo " Could not check certificate file type" return 1 fi # Insert SSL key secret echo " ๐Ÿ”‘ Inserting SSL key secret..." echo " Command: sudo abra app secret insert traefik.workshop.local ssl_key v1" if sudo abra app secret insert traefik.workshop.local ssl_key v1 -f "$CERT_DIR/workshop.key"; then echo "โœ… SSL key secret inserted successfully" else echo "โŒ Failed to insert SSL key secret" echo " abra exit code: $?" echo " Checking abra app status..." sudo abra app ls 2>&1 || echo " Could not list apps" echo " Checking key file..." file "$CERT_DIR/workshop.key" 2>/dev/null || echo " Could not check key file type" return 1 fi echo "๐ŸŽ‰ All secrets inserted successfully!" } setup_traefik_deploy() { echo "๐Ÿš€ Deploying Traefik..." echo " Command: sudo abra app deploy traefik.workshop.local" if sudo abra app deploy traefik.workshop.local; then echo "โœ… Traefik deployment initiated successfully" else echo "โŒ Traefik deployment failed" echo " abra exit code: $?" echo " Checking deployment status..." sudo abra app ps traefik.workshop.local 2>&1 || echo " Could not check app status" return 1 fi } setup_traefik_wait() { echo "โณ Waiting for Traefik to be ready..." for i in {1..30}; do echo " Checking Traefik status (attempt $i/30)..." # Try HTTPS first if curl -s -k --max-time 5 https://traefik.workshop.local/ping >/dev/null 2>&1; then echo "โœ… Traefik ready via HTTPS!" echo " Dashboard: https://traefik.workshop.local (accept self-signed cert)" echo " ๐Ÿ’ก For HTTP: http://traefik.workshop.local" break fi # Try HTTP as fallback if curl -s --max-time 5 http://traefik.workshop.local/ping >/dev/null 2>&1; then echo "โœ… Traefik ready via HTTP!" echo " Dashboard: http://traefik.workshop.local" echo " ๐Ÿ’ก For HTTPS: https://traefik.workshop.local (may require accepting cert)" break fi if [[ $i -eq 30 ]]; then echo "โŒ Traefik failed to respond after 30 attempts" echo " ๐Ÿ” Debug commands:" echo " sudo abra app logs traefik.workshop.local" echo " sudo abra app ps traefik.workshop.local" echo " docker service ls | grep traefik" return 1 fi sleep 2 done # Cleanup temporary certs echo "๐Ÿงน Cleaning up temporary certificate files..." if rm -rf "$CERT_DIR" 2>/dev/null; then echo "โœ… Certificate cleanup completed" else echo "โš ๏ธ Certificate cleanup failed (non-critical)" fi echo "๐ŸŽ‰ Traefik setup complete!" } 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 -k --max-time 3 https://traefik.workshop.local/ping >/dev/null 2>&1 && \ ! curl -s --max-time 3 http://traefik.workshop.local/ping >/dev/null 2>&1; then echo "โš ๏ธ Traefik not responding. Setting up..." setup || return 1 fi # Create and deploy app echo "๐Ÿ“ฆ Creating app: $recipe" sudo abra app new "$recipe" --domain="$domain" --server=default echo "๐Ÿš€ Deploying: $domain" sudo abra app deploy "$domain" echo "โณ Waiting for deployment..." for i in {1..60}; do if curl -s -k --max-time 3 https://$domain >/dev/null 2>&1 || \ curl -s --max-time 3 http://$domain >/dev/null 2>&1; then echo "โœ… Deployed! Access at: https://$domain (accept self-signed cert)" echo "๐Ÿ’ก Or HTTP: http://$domain" return 0 fi sleep 3 done echo "โš ๏ธ Deployment may still be starting..." echo "๐Ÿ” Debug: sudo abra app ps $domain" } connect() { echo "๐Ÿ”Œ SSH connection helper (workshop environment)" echo "Usage: ssh -o StrictHostKeyChecking=no user@host" echo "Note: This workshop focuses on local development only" } browser() { local target_url="about:blank" if [[ -n "$1" ]]; then target_url="http://$1.workshop.local" echo "๐ŸŒ Opening $1 at $target_url in Firefox" 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 } install() { echo "๐Ÿ”„ Checking and repairing abra installation..." # Use the CORRECT check method (matches service verification) if sudo abra --version >/dev/null 2>&1; then echo "โœ… abra is already installed and functional" return 0 fi echo "๐Ÿ”„ Restarting abra installation service..." sudo systemctl restart workshop-abra-install # Monitor installation progress echo "๐Ÿ“Š Monitoring installation..." for i in {1..30}; do if sudo abra --version >/dev/null 2>&1; then echo "โœ… abra installation completed successfully!" return 0 fi sleep 2 done echo "โŒ Installation still not complete. Check status:" echo " sudo systemctl status workshop-abra-install" } 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 [recipe] - 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 } network_help() { echo "๐ŸŒ Network Configuration Help:" echo "" echo "๐Ÿ” Check Network Status:" echo " nmcli device status - Show network devices" echo " nmcli connection show - Show available connections" echo " nmcli connection up - Connect to network" echo " systemctl status NetworkManager - Check service status" echo "" echo "๐Ÿ“ก Manual WiFi Connection:" echo " nmcli device wifi list - Scan for WiFi networks" echo " nmcli device wifi connect password " echo " nmcli connection up " echo "" echo "๐Ÿ”ง GUI Network Manager:" echo " Click network icon in top bar โ†’ Settings" echo " Or run: nm-connection-editor" echo "" echo "๐ŸŒ Test Internet Connection:" echo " ping 8.8.8.8 - Test basic connectivity" echo " curl https://abra.coopcloud.tech - Test HTTPS" echo "" echo "๐Ÿ“‹ Offline Setup (No Internet):" echo " 1. Configure network manually using above commands" echo " 2. Run 'setup' to initialize local environment" echo " 3. Deploy apps locally without external dependencies" echo " 4. Use 'browser ' to access deployed services" } help() { echo "๐Ÿš€ SCIENCE IN THE CITY Workshop Commands:" echo "" echo "๐Ÿ  Local Development:" echo " setup - Setup local proxy (REQUIRED FIRST!)" echo " recipes - Show all available apps" echo " deploy - Deploy app locally" echo " browser [recipe] - Launch browser [to app]" echo " desktop - Start GUI session" echo " sudo abra - Run abra CLI directly as root" echo "" echo "๐ŸŒ Network:" echo " network_help - Network configuration guide" echo "" echo "๐Ÿ”ง Workshop Tools:" echo " connect - SSH connection helper" echo " help - Show this help message" echo "" echo "๐Ÿ” Debug:" echo " docker service ls - List running services" echo " systemctl status dnsmasq - Check DNS" echo " systemctl status workshop-abra-install - Check abra installation" echo "" echo "๐Ÿ“š Learning Flow:" echo " 1. setup" echo " 2. deploy wordpress" echo " 3. browser wordpress" echo " 4. explore workshop environment" } ''; programs.firefox = { enable = true; preferences = { "browser.fixup.fallback-to-https" = false; "browser.urlbar.autoFill" = false; # Disable telemetry and data collection "datareporting.healthreport.uploadEnabled" = false; "datareporting.policy.dataSubmissionEnabled" = false; "toolkit.telemetry.enabled" = false; "toolkit.telemetry.unified" = false; "toolkit.telemetry.archive.enabled" = false; "toolkit.telemetry.newProfilePing.enabled" = false; "toolkit.telemetry.shutdownPingSender.enabled" = false; "toolkit.telemetry.updatePing.enabled" = false; "toolkit.telemetry.bhrPing.enabled" = false; "toolkit.telemetry.firstShutdownPing.enabled" = false; "toolkit.telemetry.coverage.opt-out" = true; "toolkit.coverage.opt-out" = true; "toolkit.coverage.endpoint.base" = ""; # Disable Mozilla experiments "experiments.supported" = false; "experiments.enabled" = false; "experiments.manifest.uri" = ""; # Disable crash reporting "breakpad.reportURL" = ""; "browser.tabs.crashReporting.sendReport" = false; "browser.crashReports.unsubmittedCheck.autoSubmit2" = false; # Disable default browser check "browser.shell.checkDefaultBrowser" = false; # Disable welcome tour and onboarding "browser.aboutwelcome.enabled" = false; "browser.onboarding.enabled" = false; "browser.onboarding.tour-type" = "new"; "browser.onboarding.seen-tourset-version" = "999"; "browser.onboarding.hidden" = true; # Disable pocket "extensions.pocket.enabled" = false; # Disable Firefox accounts "identity.fxaccounts.enabled" = false; # Disable sponsored content "browser.newtabpage.activity-stream.showSponsored" = false; "browser.newtabpage.activity-stream.showSponsoredTopSites" = false; # Disable search suggestions "browser.search.suggest.enabled" = false; "browser.urlbar.suggest.searches" = false; # Disable geolocation "geo.enabled" = false; # Disable webRTC "media.peerconnection.enabled" = false; }; }; # Font packages for GUI rendering (QEMU GTK display) fonts.packages = with pkgs; [ dejavu_fonts # DejaVu fonts including Sans Mono liberation_ttf noto-fonts cantarell-fonts # GNOME default font ubuntu-classic # Additional font for compatibility freefont_ttf # Additional fonts fontconfig # Enhanced font configuration for QEMU ]; # GUI Configuration services.xserver = { enable = true; desktopManager.gnome.enable = true; displayManager.gdm.enable = true; }; # Essential GNOME services for network integration services.gnome = { glib-networking.enable = true; # Critical for NetworkManager integration gnome-settings-daemon.enable = true; # Handles GNOME settings gnome-keyring.enable = true; # Optional but good practice }; # Exclude unnecessary GNOME packages environment.gnome.excludePackages = with pkgs; [ gnome-photos gnome-tour gnome-music gnome-maps cheese epiphany geary evince totem simple-scan yelp gnome-contacts gnome-weather gnome-clocks gnome-terminal ]; # Auto-start GNOME settings commented out to prevent any potential boot hangs # environment.etc."xdg/autostart/gnome-settings.desktop".text = '' # [Desktop Entry] # Type=Application # Name=Workshop GNOME Settings # Exec=sh -c "gsettings set org.gnome.shell favorite-apps \"['org.gnome.TextEditor.desktop', 'firefox.desktop']\" && gsettings set org.gnome.shell welcome-dialog-last-shown-version \"999999\"" # NoDisplay=false # ''; # Auto-login configuration (renamed in newer NixOS) services.displayManager.autoLogin = { enable = true; user = "workshop"; }; # Configure GNOME favorite apps and disable welcome dialog services.xserver.desktopManager.gnome = { extraGSettingsOverrides = '' [org.gnome.shell] favorite-apps=['org.gnome.TextEditor.desktop', 'org.gnome.Console.desktop', 'firefox.desktop'] welcome-dialog-last-shown-version='999999' ''; }; }