init project

This commit is contained in:
2025-08-11 12:07:23 +02:00
commit 53041ccc1a
11 changed files with 789 additions and 0 deletions

3
.env.example Normal file
View File

@@ -0,0 +1,3 @@
WORKSHOP_WIFI_SSID=
WORKSHOP_WIFI_PASSWORD=
WORKSHOP_DOMAIN=

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.env
result/
.direnv/

68
Makefile Normal file
View File

@@ -0,0 +1,68 @@
# Load .env file if it exists
-include .env
export
.PHONY: help deploy-cloud build-usb flash-usb local-shell local-deploy local-ssh clean status
DOMAIN := $(or $(WORKSHOP_DOMAIN),codecrispi.es)
USB_DEVICE := /dev/sdX
help:
@echo "🍪 CODE CRISPIES Workshop"
@echo ""
@echo "Config: WiFi=$(or $(WORKSHOP_WIFI_SSID),CODE_CRISPIES_GUEST), Domain=$(DOMAIN)"
@echo ""
@echo "Cloud Infrastructure:"
@echo " make deploy-cloud - Deploy VMs to Hetzner (with health checks)"
@echo " make status-cloud - Check cloud server status"
@echo " make destroy-cloud - Destroy cloud infrastructure"
@echo ""
@echo "USB Boot Drive:"
@echo " make build-usb - Build NixOS ISO"
@echo " make flash-usb - Flash ISO to USB (set USB_DEVICE=/dev/sdX)"
@echo ""
@echo "Local Development:"
@echo " make local-shell - Enter dev shell"
build-usb:
@echo "🔨 Building NixOS workshop ISO..."
@echo "📝 Config: WiFi=$(or $(WORKSHOP_WIFI_SSID),CODE_CRISPIES_GUEST), Domain=$(DOMAIN)"
nix build .#live-iso
@echo "✅ ISO built: result/iso/nixos.iso"
flash-usb: build-usb
@echo "⚠️ Flashing to ${USB_DEVICE} - THIS WILL ERASE THE DEVICE!"
@read -p "Continue? [y/N]: " confirm && [ "$$confirm" = "y" ]
sudo dd if=result/iso/nixos.iso of=${USB_DEVICE} bs=4M status=progress oflag=sync
@echo "✅ USB drive ready!"
deploy-cloud:
@echo "🚀 Deploying to Hetzner Cloud..."
cd terraform && terraform init
cd terraform && terraform apply -auto-approve \
-var="hcloud_token=${HCLOUD_TOKEN}" \
-var="domain=${DOMAIN}" \
-var="ssh_public_key=$$(cat ~/.ssh/id_rsa.pub)"
@echo "🔍 Running health checks..."
./scripts/deploy.sh
@echo "✅ Cloud deployment complete and verified!"
status-cloud:
@echo "📊 Checking server status..."
@for name in hopper curie lovelace noether hamilton franklin johnson clarke goldberg liskov wing rosen shaw karp rich; do \
echo -n "$$name.${DOMAIN}: "; \
if curl -s -f https://traefik.$$name.${DOMAIN}/ping >/dev/null 2>&1; then \
echo "✅ Ready"; \
else \
echo "❌ Not ready"; \
fi; \
done
destroy-cloud:
cd terraform && terraform destroy -auto-approve
local-shell:
nix develop
clean:
rm -rf result .direnv

89
README.md Normal file
View File

@@ -0,0 +1,89 @@
# 🍪 CODE CRISPIES Workshop Infrastructure
Three deployment environments for Co-op Cloud workshop:
## 🚀 Quick Start
```bash
# 1. Build & flash USB drives
make build-usb
make flash-usb USB_DEVICE=/dev/sdX
# 2. Deploy cloud infrastructure
export HCLOUD_TOKEN=your_token
make deploy-cloud
# 3. Local development
make local-shell
make local-deploy
make local-ssh
```
## 📁 Project Structure
```
├── flake.nix # USB boot environment
├── local/flake.nix # Local NixOS 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)
- Hetzner VMs: `hopper.codecrispi.es`, `curie.codecrispi.es`, etc.
- Pre-configured with Docker Swarm + abra
- SSL certificates via Let's Encrypt
### 2. USB Boot (Workshop)
- NixOS live environment
- Auto-connects to workshop WiFi
- Helper functions: `connect hopper`, `recipes`, `help`
- SSH into cloud VMs
### 3. Local (Development)
- NixOS containers: `participant1.local` through `participant15.local`
- Test abra deployments locally
- Isolated Docker Swarm per container
## 🔧 Development Workflow
```bash
# Enter development environment
make local-shell
# Deploy local testing environment
make local-deploy
# SSH into local participant container
make local-ssh # Select participant 1-15
# Test app deployment inside container
abra app new wordpress -S --domain=test.participant1.local
abra app deploy test.participant1.local
```
## 📦 Workshop Flow
1. **Participant boots USB** → NixOS live environment
2. **Connects to WiFi**`CODE_CRISPIES_GUEST`
3. **SSH to cloud VM**`connect hopper`
4. **Deploy apps**`abra app new wordpress -S --domain=mysite.hopper.codecrispi.es`
5. **Access via browser**`https://mysite.hopper.codecrispi.es`
## 🎯 Available Apps
- **WordPress** - CMS/Blog
- **Nextcloud** - File sharing
- **HedgeDoc** - Collaborative markdown
- **Jitsi** - Video conferencing
- **PrestaShop** - E-commerce
## 🧹 Cleanup
```bash
make clean # Clean local artifacts
make destroy-cloud # Destroy Hetzner infrastructure
```

View File

@@ -0,0 +1,121 @@
# 🍪 CODE CRISPIES Workshop
## USB Boot Instructions
### 📋 Quick Reference Card
**Your assigned server:** `__________.codecrispies.es`
**Workshop WiFi:** `CODE_CRISPIES_GUEST` / Password: `workshop2024`
---
## 💻 How to Boot from USB Drive
### Step 1: Insert USB Drive
- Insert the CODE CRISPIES workshop USB drive
- Do NOT remove it until workshop ends
### Step 2: Boot from USB
#### 🖥️ **Desktop PC / Most Laptops**
1. **Restart** your computer
2. **Press and HOLD** one of these keys immediately as it starts:
- `F12` (most common)
- `F11`
- `F9`
- `ESC`
3. Select **USB Drive** or **UEFI: USB Drive** from boot menu
4. Press `Enter`
#### 🍎 **Mac (Intel)**
1. **Restart** your Mac
2. **Press and HOLD** `Option` (⌥) key immediately
3. Select the **USB drive** (may show as "EFI Boot")
4. Press `Enter`
#### 🍎 **Mac (Apple Silicon M1/M2)**
1. **Shut down** completely
2. **Press and HOLD** the power button until you see startup options
3. Select the **USB drive** option
4. Click **Continue**
#### 🔧 **If Boot Menu Doesn't Appear**
**Windows PC:**
1. Boot into Windows
2. Hold `Shift` + click **Restart**
3. Choose **Troubleshoot****Advanced****UEFI Firmware**
4. Find **Boot Order** settings
5. Move **USB Drive** to top of list
6. Save and exit
**Popular Manufacturer Keys:**
- **Dell:** `F12`
- **HP:** `F9` or `ESC` then `F9`
- **Lenovo:** `F12` or `Fn + F12`
- **ASUS:** `F8` or `ESC`
- **Acer:** `F12`
- **MSI:** `F11`
- **Samsung:** `F2` then navigate to Boot tab
- **Toshiba:** `F12`
---
## ✅ What You Should See
1. **NixOS Boot Screen** appears
2. System loads (takes 30-60 seconds)
3. **Desktop environment** starts automatically
4. **Terminal opens** with CODE CRISPIES welcome message
5. You see available servers and commands
---
## 🚀 Getting Started Commands
```bash
# Connect to your assigned server
connect hopper
# See available app recipes
recipes
# Get help
help
```
---
## 📱 Mobile Hotspot Instructions
**If WiFi isn't working:**
**iPhone:**
Settings → Personal Hotspot → Turn On
Name: iPhone, Password: (ask facilitator)
**Android:**
Settings → Network → Hotspot & Tethering → Mobile Hotspot
Name: Android, Password: (ask facilitator)
---
## 🆘 Troubleshooting
**"No bootable device"**
→ Try different F-key (F11, F9, ESC)
→ USB drive may not be properly inserted
**Mac won't boot USB**
→ USB drive might need to be reformatted for Mac
→ Ask facilitator for Mac-compatible USB
**Boots to Windows/Mac instead**
→ You didn't press the boot key fast enough
→ Restart and try again immediately
**Terminal doesn't open**
→ Click terminal icon in taskbar
→ Or press `Ctrl + Alt + T`
**Can't connect to internet**
→ Try different WiFi network
→ Use mobile hotspot as backup

108
flake.nix Normal file
View File

@@ -0,0 +1,108 @@
{
description = "CODE CRISPIES Workshop Live Environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixos-generators.url = "github:nix-community/nixos-generators";
};
outputs = { self, nixpkgs, nixos-generators }:
let
system = "x86_64-linux";
participantNames = [
"hopper" "curie" "lovelace" "noether" "hamilton"
"franklin" "johnson" "clarke" "goldberg" "liskov"
"wing" "rosen" "shaw" "karp" "rich"
];
in {
packages.${system}.live-iso = nixos-generators.nixosGenerate {
inherit system;
format = "iso";
modules = [
({ pkgs, ... }: {
# WiFi support
networking.wireless.enable = true;
networking.networkmanager.enable = true;
networking.wireless.networks = {
"CODE_CRISPIES_GUEST" = {
psk = "workshop2024";
};
};
# Auto-connect to workshop WiFi
systemd.services.workshop-wifi = {
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
script = ''
${pkgs.networkmanager}/bin/nmcli dev wifi connect "CODE_CRISPIES_GUEST" password "workshop2024" || true
'';
};
# Auto-login workshop user
services.getty.autologinUser = "workshop";
users.users.workshop = {
isNormalUser = true;
shell = pkgs.zsh;
};
# Workshop shell environment
programs.zsh = {
enable = true;
interactiveShellInit = ''
echo "🍪 CODE CRISPIES Workshop Environment"
echo "📶 WiFi: CODE_CRISPIES_GUEST (auto-connecting...)"
echo "📡 Available servers:"
${builtins.concatStringsSep "\n" (map (name:
"echo \" - ${name}.codecrispi.es\""
) participantNames)}
echo ""
echo "💡 Commands: connect <name> | recipes | help"
connect() {
[ -z "$1" ] && { echo "Usage: connect <name>"; return 1; }
echo "🔗 Connecting to $1.codecrispi.es..."
ssh -o StrictHostKeyChecking=no workshop@$1.codecrispi.es
}
recipes() {
echo "📦 Available recipes:"
echo " wordpress nextcloud hedgedoc jitsi prestashop"
echo "Deploy: abra app new wordpress -S --domain=myapp.<name>.codecrispi.es"
}
help() {
echo "1 connect <yourname>"
echo "2 abra app new wordpress -S --domain=mysite.<yourname>.codecrispi.es"
echo "3 abra app deploy mysite.<yourname>.codecrispi.es"
echo "4 Visit https://mysite.<yourname>.codecrispi.es"
}
'';
};
environment.systemPackages = with pkgs [ openssh curl git networkmanager ];
# Auto-start terminal
services.xserver = {
enable = true;
displayManager = {
autoLogin.enable = true;
autoLogin.user = "workshop";
sessionCommands = "${pkgs.xterm}/bin/xterm &";
};
};
})
];
};
# Dev shell for local testing
devShells.${system}.default = nixpkgs.legacyPackages.${system}.mkShell {
buildInputs = with nixpkgs.legacyPackages.${system}; [
terraform
nixos-rebuild
docker
openssh
];
};
};
}

84
local/flake.nix Normal file
View File

@@ -0,0 +1,84 @@
{
description = "Local Co-op Cloud Testing";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }: {
nixosConfigurations.workshop-local = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
{
containers = builtins.listToAttrs (map (i:
let participant = builtins.elemAt [
"hopper" "curie" "lovelace" "noether" "hamilton"
"franklin" "johnson" "clarke" "goldberg" "liskov"
"wing" "rosen" "shaw" "karp" "rich"
] (i - 1);
in {
name = "participant${toString i}";
value = {
autoStart = true;
privateNetwork = true;
hostAddress = "192.168.100.1";
localAddress = "192.168.100.${toString (10 + i)}";
config = { pkgs, ... }: {
virtualisation.docker = {
enable = true;
extraOptions = "--experimental";
};
# Install abra + setup
environment.systemPackages = with pkgs; [
docker
git
curl
(stdenv.mkDerivation {
pname = "abra";
version = "latest";
src = fetchurl {
url = "https://git.autonomic.zone/coop-cloud/abra/releases/latest/download/abra-x86_64-unknown-linux-gnu";
sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # Update with real hash
};
installPhase = "install -D $src $out/bin/abra";
})
];
systemd.services.workshop-setup = {
wantedBy = [ "multi-user.target" ];
after = [ "docker.service" ];
script = ''
# Docker swarm
${pkgs.docker}/bin/docker swarm init --advertise-addr 192.168.100.${toString (10 + i)} || true
${pkgs.docker}/bin/docker network create -d overlay proxy || true
# Abra server setup
mkdir -p /root/.abra/servers
echo "${participant}.local" > /root/.abra/servers/${participant}.local/server.conf
'';
};
services.openssh.enable = true;
networking = {
firewall.allowedTCPPorts = [ 22 80 443 ];
hostName = "${participant}-local";
};
};
};
}
) (nixpkgs.lib.range 1 15));
# DNS for *.local domains
services.dnsmasq = {
enable = true;
settings.address = builtins.concatMap (i: [
"/participant${toString i}.local/192.168.100.${toString (10 + i)}"
"/wp.participant${toString i}.local/192.168.100.${toString (10 + i)}"
"/nextcloud.participant${toString i}.local/192.168.100.${toString (10 + i)}"
]) (nixpkgs.lib.range 1 15);
};
}
];
};
};
}

52
scripts/deploy.sh Normal file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -e
echo "🚀 Deploying Co-op Cloud workshop..."
cd terraform
terraform apply \
-var="hcloud_token=$HCLOUD_TOKEN" \
-var="domain=codecrispi.es" \
-var="ssh_public_key=$(cat ~/.ssh/id_rsa.pub)" \
-auto-approve
echo "⏳ Waiting for servers to be ready..."
# Wait for SSH + Docker to be ready on each server
terraform output -json participant_ips | jq -r 'keys[]' | while read participant; do
echo "Checking $participant.codecrispi.es..."
# Wait for SSH to be available
while ! ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no workshop@$participant.codecrispi.es "exit" 2>/dev/null; do
echo " SSH not ready yet, retrying in 10s..."
sleep 10
done
# Wait for Docker + abra to be ready
while ! ssh -o StrictHostKeyChecking=no workshop@$participant.codecrispi.es "docker info && which abra" &>/dev/null; do
echo " Docker/abra not ready yet, retrying in 5s..."
sleep 5
done
echo "$participant ready!"
done
echo "🔧 Setting up each server..."
terraform output -json participant_ips | jq -r 'keys[]' | while read participant; do
echo "Configuring $participant..."
ssh -o StrictHostKeyChecking=no workshop@$participant.codecrispi.es << EOF
# Deploy Traefik
abra app deploy traefik.$participant.codecrispi.es
# Wait for Traefik to be ready
until curl -f https://traefik.$participant.codecrispi.es/ping 2>/dev/null; do
echo "Waiting for Traefik..."
sleep 5
done
echo "✅ $participant fully configured!"
EOF
done
echo "🎉 Workshop ready! Participants can access their servers."

34
terraform/cloud-init.yml Normal file
View File

@@ -0,0 +1,34 @@
#cloud-config
users:
- name: workshop
groups: [adm, docker]
sudo: ['ALL=(ALL) NOPASSWD:ALL']
shell: /bin/bash
ssh_authorized_keys:
- ${ssh_public_key}
packages:
- docker.io
- curl
- git
- jq
runcmd:
# Setup Docker Swarm
- systemctl enable docker
- systemctl start docker
- usermod -aG docker workshop
- docker swarm init
- docker network create -d overlay proxy
# Install abra
- curl -fsSL https://install.abra.coopcloud.tech | bash
- mv /root/.local/bin/abra /usr/local/bin/
- chmod +x /usr/local/bin/abra
# Pre-configure abra server for this participant
- sudo -u workshop mkdir -p /home/workshop/.abra/servers
- sudo -u workshop abra server add ${participant_name}.${domain}
# Pre-setup Traefik
- sudo -u workshop abra app new traefik --domain=traefik.${participant_name}.${domain} --server=${participant_name}.${domain}

44
terraform/main.tf Normal file
View File

@@ -0,0 +1,44 @@
locals {
domain = "codecrispi.es"
participants = [
"hopper", "curie", "lovelace", "noether", "hamilton",
"franklin", "johnson", "clarke", "goldberg", "liskov",
"wing", "rosen", "shaw", "karp", "rich"
]
}
resource "hcloud_server" "participant" {
for_each = toset(local.participants)
name = each.key
image = "ubuntu-22.04"
server_type = "cx21"
location = "nbg1"
ssh_keys = [hcloud_ssh_key.workshop.id]
user_data = templatefile("${path.module}/cloud-init.yml", {
participant_name = each.key
domain = local.domain
})
}
resource "hcloud_dns_record" "participant_main" {
for_each = toset(local.participants)
zone_id = var.dns_zone_id
name = each.key
type = "A"
value = hcloud_server.participant[each.key].ipv4_address
ttl = 60
}
resource "hcloud_dns_record" "participant_wildcard" {
for_each = toset(local.participants)
zone_id = var.dns_zone_id
name = "*.${each.key}"
type = "A"
value = hcloud_server.participant[each.key].ipv4_address
ttl = 60
}

183
usb/flake.nix Normal file
View File

@@ -0,0 +1,183 @@
{
description = "CODE CRISPIES Workshop Live Environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixos-generators.url = "github:nix-community/nixos-generators";
};
outputs = { self, nixpkgs, nixos-generators }:
let
system = "x86_64-linux";
participantNames = [
"hopper" "curie" "lovelace" "noether" "hamilton"
"franklin" "johnson" "clarke" "goldberg" "liskov"
"wing" "rosen" "shaw" "karp" "rich"
];
in {
packages.${system}.live-iso = nixos-generators.nixosGenerate {
inherit system;
format = "iso";
modules = [
({ pkgs, ... }: {
# WiFi + NetworkManager
networking = {
networkmanager.enable = true;
wireless.enable = false; # Disable wpa_supplicant, use NetworkManager
};
# Auto-connect to workshop WiFi
systemd.services.workshop-wifi = {
wantedBy = [ "multi-user.target" ];
after = [ "NetworkManager.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = "workshop";
};
script = ''
sleep 10 # Wait for NetworkManager to start
${pkgs.networkmanager}/bin/nmcli dev wifi connect "CODE_CRISPIES_GUEST" password "workshop2024" || true
'';
};
# Auto-login workshop user
services.getty.autologinUser = "workshop";
users.users.workshop = {
isNormalUser = true;
shell = pkgs.zsh;
extraGroups = [ "networkmanager" ];
};
# Workshop shell environment with CORRECT recipes
programs.zsh = {
enable = true;
interactiveShellInit = ''
echo "🍪 CODE CRISPIES Workshop Environment"
echo "📶 WiFi: CODE_CRISPIES_GUEST"
echo "📡 Available servers:"
${builtins.concatStringsSep "\n" (map (name:
"echo \" - ${name}.codecrispi.es\""
) participantNames)}
echo ""
echo "💡 Commands: connect <name> | recipes | help"
connect() {
[ -z "$1" ] && {
echo "Usage: connect <name>"
echo "Available: ${builtins.concatStringsSep " " participantNames}"
return 1
}
echo "🔗 Connecting to $1.codecrispi.es..."
ssh -o StrictHostKeyChecking=no workshop@$1.codecrispi.es
}
recipes() {
echo "📦 Popular Co-op Cloud recipes (Score 3+):"
echo ""
echo "🌐 Content Management:"
echo " wordpress - CMS/Blog platform"
echo " ghost - Headless Node.js CMS"
echo " dokuwiki - Simple text-based wiki"
echo ""
echo " Productivity & Collaboration:"
echo " nextcloud - File sharing & collaboration"
echo " hedgedoc - Collaborative markdown editor"
echo " collabora - Online office suite"
echo " onlyoffice - Office suite with editors"
echo " outline - Wiki and knowledge base"
echo ""
echo "🍽 Lifestyle & Organization:"
echo " mealie - Recipe manager & meal planner"
echo ""
echo "💬 Communication:"
echo " mattermost - Team chat platform"
echo " rocketchat - Team communications"
echo ""
echo "🎯 Event & Community Management:"
echo " engelsystem - Volunteer shift management"
echo " loomio - Group decision making"
echo " mrbs - Meeting room booking"
echo " rallly - Doodle poll alternative"
echo ""
echo "🛠 Development & Git:"
echo " gitea - Self-hosted Git service"
echo " custom-php - Custom PHP applications"
echo ""
echo "🎨 Creative & Media:"
echo " owncast - Live streaming platform"
echo ""
echo "Deploy example:"
echo " abra app new wordpress -S --domain=mysite.<yourname>.codecrispi.es"
}
help() {
echo "📚 CODE CRISPIES Workshop Guide"
echo ""
echo "1 Connect to your assigned server:"
echo " connect <yourname> # e.g., connect hopper"
echo ""
echo "2 Check your server status:"
echo " abra server ls"
echo " abra app ls"
echo ""
echo "3 Deploy your first app:"
echo " abra app new wordpress -S --domain=mysite.<yourname>.codecrispi.es"
echo " abra app deploy mysite.<yourname>.codecrispi.es"
echo ""
echo "4 Check deployment status:"
echo " abra app ps mysite.<yourname>.codecrispi.es"
echo " abra app logs mysite.<yourname>.codecrispi.es"
echo ""
echo "5 Access your site:"
echo " https://mysite.<yourname>.codecrispi.es"
echo ""
echo "🆘 Need help? Ask the workshop facilitator!"
echo "📦 See all recipes: recipes"
}
'';
};
environment.systemPackages = with pkgs; [
openssh
curl
git
networkmanager
];
# Auto-start terminal in graphical environment
services.xserver = {
enable = true;
displayManager = {
autoLogin.enable = true;
autoLogin.user = "workshop";
sessionCommands = ''
${pkgs.xterm}/bin/xterm -maximized -e ${pkgs.zsh}/bin/zsh &
'';
};
desktopManager.xfce.enable = true;
};
# Enable hardware support for most WiFi cards
hardware.enableRedistributableFirmware = true;
# ISO-specific settings
isoImage.makeEfiBootable = true;
isoImage.makeUsbBootable = true;
})
];
};
# Dev shell
devShells.${system}.default = nixpkgs.legacyPackages.${system}.mkShell {
buildInputs = with nixpkgs.legacyPackages.${system}; [
terraform
nixos-rebuild
docker
openssh
jq
];
};
};
}