diff --git a/content/library/software/proxmox-schritt-fuer-schritt.md b/content/library/software/proxmox-schritt-fuer-schritt.md new file mode 100644 index 0000000..18574a5 --- /dev/null +++ b/content/library/software/proxmox-schritt-fuer-schritt.md @@ -0,0 +1,118 @@ +--- +title: "Proxmox, Schritt für Schritt" +date: 2026-06-02 +tags: ["software", "proxmox", "self-hosting", "anleitung", "lxc"] +summary: "Wie aus einer gebrauchten Kiste die Infrastruktur eines Architekturbüros wird — mit den Skripten, die einen Dienst in Minuten aufstellen." +color: kusa +layout: text +--- + +Die Kiste aus dem [ersten Teil](/library/software/server-im-eigenen-haus/) muss man nicht streicheln können, um sie zu verstehen. Es genügt ein Bild: Proxmox macht aus einem Rechner ein Mehrfamilienhaus. Das Haus ist die Maschine, die Wohnungen sind die Container, und in jeder Wohnung lebt genau ein Dienst — die Website, die Zeiterfassung, der Dateispeicher. Niemand stört den anderen, jeder hat seine eigene Tür, und zieht eine Partei aus, bleiben die übrigen, wo sie sind. + +Dieser Text zeigt, wie man das Haus baut und die erste Wohnung bezieht. Er setzt keine Erfahrung mit Servern voraus, nur die Bereitschaft, einen Befehl abzutippen und zu lesen, was er antwortet. + +## Das Fundament + +Proxmox VE ist im Kern ein Debian-Linux mit einer Weboberfläche und der Fähigkeit, zweierlei Sorten Wohnungen zu vermieten: vollwertige virtuelle Maschinen und — das ist unser Fall — Linux-Container, sogenannte LXC. Ein Container teilt sich den Kern des Wirts und ist deshalb sparsam: Vier Gigabyte Arbeitsspeicher reichen für einen ausgewachsenen Dienst, ein Dutzend davon laufen auf gewöhnlicher Bürohardware. + +Installiert wird Proxmox einmalig vom USB-Stick, so wie man ein Betriebssystem installiert. Das ist gut dokumentiert und hier nicht das Thema. Ab dem Moment, in dem die Weboberfläche unter `https://:8006` erscheint, beginnt der interessante Teil. + +## Das Muster: ein Container, ein Dienst, ein Befehl + +Wir richten keinen Dienst von Hand ein. Jeder Handgriff, den man zweimal macht, gehört in ein Skript — schon weil man ihn sonst beim Wiederaufsetzen vergisst. Unser Muster, von Dienst zu Dienst gleich, lautet: + +1. einen **unprivilegierten** LXC anlegen (er darf weniger, also kann weniger schiefgehen), +2. ihn so einstellen, dass **Docker** darin läuft (`nesting` und `keyctl`), +3. den Dienst als **Docker-Compose-Stack** hineinstellen, +4. alle **Geheimnisse automatisch erzeugen** lassen, nichts von Hand eintippen, +5. ein **Backup** einrichten, bevor überhaupt Daten da sind. + +Das ist die ganze Liturgie. Wer sie einmal in ein Skript gegossen hat, stellt den nächsten Dienst hin, indem er das Skript ruft. + +## Die erste Wohnung: unser CMS + +Diese Website ist das Musterbeispiel. Ein einziger Befehl, abgesetzt auf dem Proxmox-Wirt als `root`, baut den ganzen Container — Docker, das Repository, sämtliche Schlüssel, der laufende Stack: + +```bash +bash <(curl -fsSL https://git.kgva.ch/karim/OPENBUREAU/raw/branch/main/cms/proxmox/create-openbureau-lxc.sh) +``` + +Das Skript fragt nur nach Speicherort, Netzwerkbrücke und IP — Enter übernimmt je den Vorschlag — und ist nach wenigen Minuten fertig. Am Ende nennt es die Adressen: den Editor unter `…:8080/admin/`, die Website unter `…:8080/`. + +Spannend ist nicht der Einzeiler, sondern was er tut. Das [vollständige Skript](https://git.kgva.ch/karim/OPENBUREAU/src/branch/main/cms/proxmox/create-openbureau-lxc.sh) liest sich von oben nach unten wie ein Protokoll. Den Container anlegen, mit den zwei Schaltern, die Docker erlauben: + +```bash +pct create "$CTID" "$TEMPLATE_REF" \ + --hostname openbureau \ + --cores 2 --memory 4096 --swap 1024 \ + --rootfs "local-lvm:20" \ + --net0 "name=eth0,bridge=vmbr0,ip=dhcp" \ + --unprivileged 1 \ + --features "nesting=1,keyctl=1" \ + --onboot 1 +``` + +Dann, im Container, Docker installieren, das Repository ziehen und — der Teil, der einem die durchwachte Nacht erspart — die Geheimnisse erzeugen, statt sie von Hand zu setzen: + +```bash +curl -fsSL https://get.docker.com | sh +systemctl enable --now docker +git clone https://git.kgva.ch/karim/OPENBUREAU.git /opt/openbureau + +cd /opt/openbureau/cms +cp .env.example .env +sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 32)|" .env +sed -i "s|^JWT_SECRET=.*|JWT_SECRET=$(openssl rand -hex 32)|" .env +docker compose up -d --build +``` + +Und schliesslich, noch bevor der erste Beitrag geschrieben ist, das tägliche Backup — denn das Forum lebt allein in der Datenbank, nicht im Git: + +```bash +printf '15 3 * * * root cd /opt/openbureau/cms && bash scripts/backup-db.sh\n' \ + > /etc/cron.d/openbureau-backup +``` + +Kein Schritt davon ist klug; jeder ist nur aufgeschrieben. Das ist der ganze Trick. + +## Ein Menü statt Handarbeit + +Weil das Muster sich von Dienst zu Dienst wiederholt, haben wir es ein einziges Mal in ein Installationsskript gegossen. Es ruft sich genauso wie das CMS-Skript — ein Einzeiler auf dem Proxmox-Wirt, als `root` —, nur legt es kein bestimmtes Programm fest, sondern fragt, was man haben will: + +```bash +bash <(curl -fsSL https://git.kgva.ch/karim/OPENBUREAU/raw/branch/main/proxmox/install.sh) +``` + +Zuerst fragt es nicht nach Technik, sondern nach dem Vorhaben: ein ganzes Büro einrichten, bloss Office 365 und die Synology ersetzen, nur die öffentliche Website — oder, für jene, die genau wissen, was sie wollen, einzeln auswählen. Aus der Antwort leitet das Skript ab, welche Container es braucht, und baut sie der Reihe nach. Jeder bekommt seine eigene Wohnung; für jede erledigt das Skript dasselbe, was oben Schritt für Schritt stand: Template holen, unprivilegierten Container anlegen, Docker hineinlegen, den Dienst starten. + +Wer das Menü überspringen will, hängt den gewünschten Dienst direkt an: + +```bash +… install.sh nextcloud # nur Nextcloud +… install.sh empty dateien 200 8192 # leerer Docker-LXC, 200 GB / 8 GB RAM +… install.sh git git.kgva.ch/karim/RAPPORT-SERVER.git rapport +``` + +Hinter dem Menü steckt keine grosse Maschine, sondern ein Bündel kleiner, eigenständiger Skripte — eines pro Dienst. Die Suite ist nur der Dialog, der sie der Reihe nach aufruft. Wer das Menü gar nicht braucht, lädt das einzelne Skript direkt: + +```bash +bash <(curl -fsSL …/proxmox/nextcloud-lxc.sh) # 500 GB / 8 GB RAM +bash <(curl -fsSL …/proxmox/empty-lxc.sh) dateien 200 8192 +bash <(curl -fsSL …/proxmox/git-compose-lxc.sh) git.kgva.ch/karim/RAPPORT-SERVER.git rapport +``` + +Jedes dieser Skripte ist in sich geschlossen und tut, was oben Schritt für Schritt stand: Template holen, unprivilegierten Container anlegen, Docker hineinlegen, den Dienst starten. Genau diese Wiederholbarkeit ist der Sinn der Übung — ein Dienst, den man nicht mit einem Befehl neu aufsetzen kann, ist ein Dienst, vor dem man sich beim nächsten Mal fürchtet. + +## Office 365 und die Synology ersetzen: Nextcloud + +Der grösste Brocken verdient einen eigenen Blick, weil er am meisten ersetzt. [Nextcloud](https://nextcloud.com) übernimmt in einem Aufwasch, wofür sonst zwei Abos und eine NAS herhalten: die Dateiablage mit Synchronisation auf alle Geräte — das OneDrive- und Synology-Drive-Erbe —, gemeinsame Kalender und Kontakte, dazu über das eingebaute Office das Bearbeiten von Dokumenten und Tabellen im Browser, zu zweit am selben Text. + +Im Menü ist es ein Haken, von Hand der Befehl oben. Was dann läuft, ist die offizielle All-in-One-Variante: ein verwalteter Container, der die übrigen selbst aufsetzt. Den Rest erledigt die Weboberfläche unter Port 8080 — sie vergibt das Admin-Passwort, fragt die Domain ab und startet die eigentlichen Dienste. Ohne eigene Domain erreicht man das Ganze vorerst im lokalen Netz; für den Zugriff von aussen kommt später ein Reverse-Proxy davor, dasselbe Prinzip, das auch unser CMS hinter TLS bringt. + +Damit ist die Rechnung geschlossen: Mail, Kalender, Kontakte, Dateien, gemeinsame Dokumente — alles, wofür das Büro bisher Monat für Monat pro Kopf bezahlt hat, läuft im Schrank. Und unsere eigenen Werkzeuge, RAPPORT und DOSSIER, ziehen über denselben Git-Eintrag im Menü nach, weil sie demselben Muster folgen wie alles andere. + +## Das Backup ist kein Anhang + +Ein Satz zum Schluss, der eigentlich an den Anfang gehört. Selbst zu hosten heisst, selbst für die Sicherung geradezustehen. Zweierlei greift bei uns ineinander. Innerhalb jedes Dienstes sichert ein nächtlicher cron-Lauf die Datenbank weg — bei dieser Website das Forum, bei Nextcloud die Metadaten. Und für die Container als Ganzes nimmt der **Proxmox Backup Server** allabendlich einen Schnappschuss, aus dem sich eine ganze Wohnung in Minuten wiederherstellen lässt, sollte sie einmal abbrennen. + +Ein Backup, das man nie zurückgespielt hat, ist eine Hoffnung, kein Backup. Darum gehört der erste Wiederherstellungs-Versuch an den Tag, an dem der Dienst aufgesetzt wird — nicht an den Tag, an dem man ihn braucht. diff --git a/content/library/software/server-im-eigenen-haus.md b/content/library/software/server-im-eigenen-haus.md new file mode 100644 index 0000000..51d7d77 --- /dev/null +++ b/content/library/software/server-im-eigenen-haus.md @@ -0,0 +1,36 @@ +--- +title: "Server im eigenen Haus" +date: 2026-06-02 +tags: ["software", "proxmox", "self-hosting", "infrastruktur"] +summary: "Warum unsere Dienste auf einer eigenen Kiste laufen statt bei Microsoft, Google oder Synology — und was das mit Architektur zu tun hat." +color: yuyake +layout: text +--- + +Im Schrank neben dem Plotter steht jetzt eine Kiste. Kein schönes Gerät, ein ausgemusterter Bürorechner mit zu vielen Lüftern, der leise vor sich hin rauscht. Auf ihm liegt, was sonst über ein halbes Dutzend Abonnements verteilt wäre: die Korrespondenz, die Pläne, die Zeiterfassung, diese Website. Das Büro hat seine Daten nach Hause geholt. + +Lange war das anders, und lange fiel es nicht auf. Ein Architekturbüro produziert Daten, bevor es das erste Gebäude produziert — Wettbewerbsbeiträge, Pläne in dreissig Revisionen, Honorarabrechnungen, die Korrespondenz mit Bauherrschaft und Amt. Dieser Bestand wächst still, und ebenso still ist er in die Cloud gewandert. Microsoft 365 für Mail und Dokumente, OneDrive oder die Synology im Keller für die Dateien, ein gemietetes CRM für die Adressen. Jedes Stück für sich vernünftig, zusammen ein Büro, dessen Substanz auf fremden Servern liegt, zu Bedingungen, die ein anderer schreibt. + +Das funktioniert tadellos. Es ist bequem. Und es heisst, dass das Gedächtnis des Büros zur Miete wohnt. + +## Die Praxis besitzt ihre Werkzeuge + +Dass wir das umdrehen, ist keine Prinzipienreiterei, sondern eine Konsequenz aus dem [Manifest](/manifest/): Ein Büro offen zu führen heisst auch, die eigenen Werkzeuge zu besitzen — so wie ein Schreiner seine Hobel besitzt und nicht pro Span bezahlt. Wer die Werkzeuge mietet, mietet am Ende die eigene Arbeitsweise. + +Der Hobel ist in diesem Fall die Kiste im Schrank. Darauf läuft Proxmox, eine quelloffene Software, die aus einem gewöhnlichen Rechner viele kleine, sauber getrennte Maschinen macht. In jeder steckt ein Dienst: diese Website samt dem Editor, mit dem dieser Text geschrieben wurde; RAPPORT, unsere Zeiterfassung; DOSSIER, die Projektablage; der Dateispeicher, der die Synology ablöst; Kalender, Kontakte, Mail. Alles offen, alles auf den eigenen Platten. Was vorher Monat für Monat pro Kopf abgebucht wurde, deckt die gebrauchte Hardware in unter einem Jahr. + +## Was das mit Architektur zu tun hat + +Mehr, als es zunächst scheint. Wo die Daten einer Bauherrschaft liegen, ist keine Geschmacksfrage, sondern eine des Anstands und des Datenschutzes. Eine Maschine im eigenen Haus beantwortet die Frage, wo die anvertrauten Unterlagen sind, mit einem Fingerzeig auf den Schrank — nicht mit einem Verweis auf Rechenzentren in einer anderen Rechtsordnung. + +Dazu kommt die schlichte Unkündbarkeit. Verdoppelt ein Anbieter den Preis, streicht eine Funktion oder stellt das Produkt ein, ist das sein gutes Recht; man steht daneben und zahlt. Bei uns gibt es nichts, das gekündigt werden kann. Die Formate sind offen — die Texte dieser Bibliothek etwa sind schlichte Textdateien, lesbar auch dann, wenn unser ganzer Apparat einmal verschwindet. + +Und schliesslich behandelt ein offenes Büro seine Infrastruktur wie einen Entwurf: Man versteht sie, ändert sie, dokumentiert sie. Dieser Aufbau ist deshalb kein Betriebsgeheimnis, sondern steht [unter freier Lizenz](/lizenz/) offen. Wer sein Büro ähnlich einrichten will, kopiert unsere Skripte und macht weiter. + +## Der Preis der Selbstverständlichkeit + +Bleibt die unbequeme Seite, und sie gehört in jeden ehrlichen Text dieser Art: Man wird sein eigener Hauswart. Backups laufen nicht mehr von allein, Aktualisierungen muss jemand einspielen, und fällt der Strom, klingelt kein Support. + +Tragbar finden wir das aus zwei Gründen. Der Aufwand ist kleiner, als er klingt, sobald die Handgriffe automatisiert sind — einen neuen Dienst aufzusetzen ist bei uns ein einziger Befehl und kein verlorener Nachmittag. Und die Kontrolle ist den Rest wert: Lieber für ein Backup geradestehen, das man versteht, als sich auf eines verlassen, das man nie gesehen hat. + +Wie die Kiste im Schrank konkret eingerichtet ist — die Maschine, die Container, die Befehle, mit denen ein Dienst in Minuten steht — steht im zweiten Teil: [Proxmox, Schritt für Schritt](/library/software/proxmox-schritt-fuer-schritt/). diff --git a/proxmox/README.md b/proxmox/README.md new file mode 100644 index 0000000..f4a78bf --- /dev/null +++ b/proxmox/README.md @@ -0,0 +1,54 @@ +# OPENBUREAU — Proxmox-Selbsthosting-Set + +Skripte, um die Dienste eines Architekturbüros auf einem Proxmox-VE-Host +aufzusetzen — jeder Dienst in seinem eigenen, unprivilegierten, Docker-tauglichen +LXC. Alle Skripte werden **auf dem Proxmox-Host als `root`** ausgeführt. + +## Zwei Wege + +**1. Suite mit Dialog** — fragt, was man will, und installiert einen LXC nach dem +anderen: + +```bash +bash <(curl -fsSL https://git.kgva.ch/karim/OPENBUREAU/raw/branch/main/proxmox/install.sh) +``` + +**2. Einzelskripte** (für Fortgeschrittene) — direkt, ohne Menü. Jedes ist in sich +geschlossen: + +```bash +# Website + CMS (eigenes Skript, erzeugt alle Supabase-Secrets) +bash <(curl -fsSL …/cms/proxmox/create-openbureau-lxc.sh) + +# Nextcloud (Dateien/Kalender/Kontakte/Office — ersetzt 365 + Synology) +bash <(curl -fsSL …/proxmox/nextcloud-lxc.sh) [disk_gb] [ram_mb] + +# Leerer Docker-LXC als Gerüst +bash <(curl -fsSL …/proxmox/empty-lxc.sh) [name] [disk_gb] [ram_mb] + +# Beliebiger Dienst aus einem Git-Repo mit docker-compose (RAPPORT, DOSSIER …) +bash <(curl -fsSL …/proxmox/git-compose-lxc.sh) [name] [disk_gb] [ram_mb] +``` + +`install.sh` akzeptiert dieselben Dienste auch direkt als Argument +(`install.sh nextcloud`, `install.sh git …`). + +## Gemeinsames Muster + +Jedes Skript macht dasselbe: aktuelles Debian-12-Template sicherstellen, +unprivilegierten LXC mit `nesting=1,keyctl=1` anlegen (damit Docker darin läuft), +Docker installieren, den Dienst als Container/Compose-Stack starten. Storage, Netz +und SSH-Key lassen sich per Umgebungsvariable überschreiben: + +| Variable | Default | +|--------------------|--------------------------| +| `ROOTFS_STORAGE` | `local-lvm` | +| `TEMPLATE_STORAGE` | `local` | +| `BRIDGE` | `vmbr0` | +| `SSH_PUBKEY_FILE` | `~/.ssh/id_ed25519.pub` | + +## Hintergrund + +Warum und wie — die zwei Artikel in der Bibliothek: +[Server im eigenen Haus](https://openbureau.ch/library/software/server-im-eigenen-haus/) +und [Proxmox, Schritt für Schritt](https://openbureau.ch/library/software/proxmox-schritt-fuer-schritt/). diff --git a/proxmox/empty-lxc.sh b/proxmox/empty-lxc.sh new file mode 100755 index 0000000..ce40f59 --- /dev/null +++ b/proxmox/empty-lxc.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# +# Leerer, Docker-tauglicher LXC für Proxmox VE — das Gerüst für eigene Dienste. +# +# AUF DEM PROXMOX-HOST, als root: +# bash <(curl -fsSL https://git.kgva.ch/karim/OPENBUREAU/raw/branch/main/proxmox/empty-lxc.sh) [name] [disk_gb] [ram_mb] +# +# Beispiel: … empty-lxc.sh dateien 200 8192 +# +set -euo pipefail + +############################# gemeinsamer Kopf ############################# +SSH_PUBKEY_FILE="${SSH_PUBKEY_FILE:-$HOME/.ssh/id_ed25519.pub}" +ROOTFS_STORAGE="${ROOTFS_STORAGE:-local-lvm}" +TEMPLATE_STORAGE="${TEMPLATE_STORAGE:-local}" +BRIDGE="${BRIDGE:-vmbr0}" + +say() { echo -e "\n\033[1;36m▸ $*\033[0m"; } +ok() { echo -e "\033[1;32m✓ $*\033[0m"; } +warn() { echo -e "\033[1;33m! $*\033[0m" >&2; } +die() { echo -e "\033[1;31m✗ $*\033[0m" >&2; exit 1; } + +[ "$(id -u)" -eq 0 ] || die "Bitte als root auf dem Proxmox-Host ausführen." +command -v pct >/dev/null || die "pct nicht gefunden — läuft das wirklich auf Proxmox VE?" + +ensure_template() { + [ -n "${TEMPLATE_REF:-}" ] && return 0 + pveam update >/dev/null 2>&1 || true + local tpl + tpl="$(pveam available --section system | awk '/debian-12-standard/{print $2}' | sort -V | tail -1)" + [ -n "$tpl" ] || die "Kein debian-12-Template verfügbar." + pveam list "$TEMPLATE_STORAGE" | grep -q "$tpl" || { say "Lade Template $tpl…"; pveam download "$TEMPLATE_STORAGE" "$tpl" >/dev/null; } + TEMPLATE_REF="${TEMPLATE_STORAGE}:vztmpl/${tpl}" +} + +# create_lxc [cores] — setzt $CTID +create_lxc() { + local name="$1" disk="$2" ram="$3" cores="${4:-2}" ctid + ensure_template + ctid="$(pvesh get /cluster/nextid)" + local args=( + "$ctid" "$TEMPLATE_REF" --hostname "$name" + --cores "$cores" --memory "$ram" --swap 1024 + --rootfs "${ROOTFS_STORAGE}:${disk}" + --net0 "name=eth0,bridge=${BRIDGE},ip=dhcp" + --unprivileged 1 --features "nesting=1,keyctl=1" --onboot 1 + ) + [ -f "$SSH_PUBKEY_FILE" ] && args+=(--ssh-public-keys "$SSH_PUBKEY_FILE") + say "Erstelle LXC $ctid ($name) — ${cores} Kerne, ${ram} MB RAM, ${disk} GB…" + pct create "${args[@]}" >/dev/null + pct start "$ctid"; sleep 5 + CTID="$ctid" +} + +install_docker() { + say "Installiere Docker in $1…" + pct exec "$1" -- bash -euo pipefail -c ' + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq ca-certificates curl git openssl >/dev/null + curl -fsSL https://get.docker.com | sh >/dev/null + systemctl enable --now docker + ' +} + +ip_of() { pct exec "$1" -- hostname -I 2>/dev/null | awk '{print $1}'; } + +################################# Dienst ################################# +NAME="${1:-docker}"; DISK="${2:-20}"; RAM="${3:-4096}" + +create_lxc "$NAME" "$DISK" "$RAM" +install_docker "$CTID" +ok "Leerer Docker-LXC $CTID ($NAME) läuft unter $(ip_of "$CTID")." +echo " Hinein: pct enter $CTID" diff --git a/proxmox/git-compose-lxc.sh b/proxmox/git-compose-lxc.sh new file mode 100755 index 0000000..e2ad7dc --- /dev/null +++ b/proxmox/git-compose-lxc.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# +# Beliebiger self-hosted Dienst aus einem Git-Repo mit docker-compose, als +# eigener LXC für Proxmox VE. Folgt demselben Muster wie OPENBUREAU — passend +# für eigene Werkzeuge wie RAPPORT oder DOSSIER. +# +# AUF DEM PROXMOX-HOST, als root: +# bash <(curl -fsSL https://git.kgva.ch/karim/OPENBUREAU/raw/branch/main/proxmox/git-compose-lxc.sh) [name] [disk_gb] [ram_mb] +# +# Beispiel: … git-compose-lxc.sh git.kgva.ch/karim/RAPPORT-SERVER.git rapport +# +set -euo pipefail + +############################# gemeinsamer Kopf ############################# +SSH_PUBKEY_FILE="${SSH_PUBKEY_FILE:-$HOME/.ssh/id_ed25519.pub}" +ROOTFS_STORAGE="${ROOTFS_STORAGE:-local-lvm}" +TEMPLATE_STORAGE="${TEMPLATE_STORAGE:-local}" +BRIDGE="${BRIDGE:-vmbr0}" + +say() { echo -e "\n\033[1;36m▸ $*\033[0m"; } +ok() { echo -e "\033[1;32m✓ $*\033[0m"; } +warn() { echo -e "\033[1;33m! $*\033[0m" >&2; } +die() { echo -e "\033[1;31m✗ $*\033[0m" >&2; exit 1; } + +[ "$(id -u)" -eq 0 ] || die "Bitte als root auf dem Proxmox-Host ausführen." +command -v pct >/dev/null || die "pct nicht gefunden — läuft das wirklich auf Proxmox VE?" + +ensure_template() { + [ -n "${TEMPLATE_REF:-}" ] && return 0 + pveam update >/dev/null 2>&1 || true + local tpl + tpl="$(pveam available --section system | awk '/debian-12-standard/{print $2}' | sort -V | tail -1)" + [ -n "$tpl" ] || die "Kein debian-12-Template verfügbar." + pveam list "$TEMPLATE_STORAGE" | grep -q "$tpl" || { say "Lade Template $tpl…"; pveam download "$TEMPLATE_STORAGE" "$tpl" >/dev/null; } + TEMPLATE_REF="${TEMPLATE_STORAGE}:vztmpl/${tpl}" +} + +create_lxc() { + local name="$1" disk="$2" ram="$3" cores="${4:-2}" ctid + ensure_template + ctid="$(pvesh get /cluster/nextid)" + local args=( + "$ctid" "$TEMPLATE_REF" --hostname "$name" + --cores "$cores" --memory "$ram" --swap 1024 + --rootfs "${ROOTFS_STORAGE}:${disk}" + --net0 "name=eth0,bridge=${BRIDGE},ip=dhcp" + --unprivileged 1 --features "nesting=1,keyctl=1" --onboot 1 + ) + [ -f "$SSH_PUBKEY_FILE" ] && args+=(--ssh-public-keys "$SSH_PUBKEY_FILE") + say "Erstelle LXC $ctid ($name) — ${cores} Kerne, ${ram} MB RAM, ${disk} GB…" + pct create "${args[@]}" >/dev/null + pct start "$ctid"; sleep 5 + CTID="$ctid" +} + +install_docker() { + say "Installiere Docker in $1…" + pct exec "$1" -- bash -euo pipefail -c ' + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq ca-certificates curl git openssl >/dev/null + curl -fsSL https://get.docker.com | sh >/dev/null + systemctl enable --now docker + ' +} + +ip_of() { pct exec "$1" -- hostname -I 2>/dev/null | awk '{print $1}'; } + +################################# Dienst ################################# +REPO="${1:-}"; NAME="${2:-}"; DISK="${3:-20}"; RAM="${4:-4096}" +[ -n "$REPO" ] || die "Bitte eine Repo-URL angeben (Arg 1)." +[ -n "$NAME" ] || NAME="$(basename "${REPO%.git}" | tr '[:upper:]' '[:lower:]')" +case "$REPO" in http*://*) : ;; *) REPO="https://$REPO" ;; esac + +create_lxc "$NAME" "$DISK" "$RAM" +install_docker "$CTID" + +say "Klone $REPO und starte den Stack…" +pct exec "$CTID" -- bash -euo pipefail -c " + git clone --quiet '$REPO' '/opt/$NAME' || { echo 'Clone fehlgeschlagen.'; exit 1; } + cd '/opt/$NAME' + CF=\$(ls docker-compose.y*ml compose.y*ml */docker-compose.y*ml */compose.y*ml 2>/dev/null | head -1) + [ -n \"\$CF\" ] || { echo 'Keine docker-compose-Datei gefunden.'; exit 1; } + cd \"\$(dirname \"\$CF\")\" + if [ ! -f .env ] && [ -f .env.example ]; then + cp .env.example .env + echo 'HINWEIS: .env aus .env.example kopiert — Secrets ggf. anpassen.' + fi + docker compose up -d --build +" + +ok "$NAME (LXC $CTID) läuft unter $(ip_of "$CTID")." +warn "App-spezifische Secrets (.env) ggf. prüfen: pct enter $CTID" diff --git a/proxmox/install.sh b/proxmox/install.sh new file mode 100755 index 0000000..641a9d3 --- /dev/null +++ b/proxmox/install.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# +# OPENBUREAU-Suite — der Dialog-Installer für das Selbsthosting-Set auf Proxmox. +# +# Fragt im Dialog, welche Dienste man will, und installiert dann einen LXC nach +# dem anderen — jeder über sein eigenes, eigenständiges Skript (proxmox/*-lxc.sh). +# Fortgeschrittene können diese Einzelskripte auch direkt curlen, ohne die Suite. +# +# AUF DEM PROXMOX-HOST (nicht im Container), als root: +# bash <(curl -fsSL https://git.kgva.ch/karim/OPENBUREAU/raw/branch/main/proxmox/install.sh) +# +# Direkt, ohne Dialog: +# … install.sh openbureau +# … install.sh nextcloud [disk_gb] [ram_mb] +# … install.sh empty [name] [disk_gb] [ram_mb] +# … install.sh git [name] [disk_gb] [ram_mb] +# +set -euo pipefail + +RAW="https://git.kgva.ch/karim/OPENBUREAU/raw/branch/main" + +say() { echo -e "\n\033[1;36m▸ $*\033[0m"; } +die() { echo -e "\033[1;31m✗ $*\033[0m" >&2; exit 1; } + +[ "$(id -u)" -eq 0 ] || die "Bitte als root auf dem Proxmox-Host ausführen." +command -v pct >/dev/null || die "pct nicht gefunden — läuft das wirklich auf Proxmox VE?" + +# Jeder Dienst = ein eigenständiges Skript. Die Suite ruft sie nur auf. +run_service() { + local svc="$1"; shift || true + case "$svc" in + openbureau) say "OPENBUREAU — Website + CMS…"; bash <(curl -fsSL "$RAW/cms/proxmox/create-openbureau-lxc.sh") ;; + nextcloud) say "Nextcloud…"; bash <(curl -fsSL "$RAW/proxmox/nextcloud-lxc.sh") "$@" ;; + empty) say "Leerer Docker-LXC…"; bash <(curl -fsSL "$RAW/proxmox/empty-lxc.sh") "$@" ;; + git) say "Git-Compose-Dienst…"; bash <(curl -fsSL "$RAW/proxmox/git-compose-lxc.sh") "$@" ;; + *) die "Unbekannter Dienst: $svc" ;; + esac +} + +# --------------------------------------------------------------------------- +# Stufe 1: Vorhaben. schlüssel | Beschreibung | Liste der Dienste +# (Sonderfall "custom" → Einzelauswahl, siehe service_checklist.) +PROFILES=( + "buero|Komplettes Büro einrichten — Website/CMS + Nextcloud|openbureau nextcloud" + "cloud|Office 365 + Synology ersetzen — nur Nextcloud|nextcloud" + "web|Nur die öffentliche Website + CMS|openbureau" + "custom|Einzeln auswählen … (für Fortgeschrittene)|custom" +) + +# Stufe 2 (custom): einzelne Dienste. schlüssel | Beschreibung +SERVICES=( + "openbureau|OPENBUREAU — Website + CMS (Hugo + Supabase)" + "nextcloud|Nextcloud — Dateien, Kalender, Kontakte, Office (ersetzt 365/Synology)" + "empty|Leerer Docker-LXC — Gerüst für eigene Dienste" + "git|Git-Compose-Dienst — eigenes Repo (z. B. RAPPORT / DOSSIER)" +) + +# Holt für die interaktive Auswahl die Zusatzangaben (Repo-URL, Name) nach. +run_from_menu() { + case "$1" in + git) + local repo name + read -rp " Repo-URL (z. B. git.kgva.ch/karim/RAPPORT-SERVER.git): " repo + [ -n "$repo" ] || { echo " übersprungen (keine URL)."; return 0; } + read -rp " Name [auto]: " name + run_service git "$repo" "$name" ;; + empty) + local name + read -rp " Name des Containers [docker]: " name + run_service empty "${name:-docker}" ;; + *) run_service "$1" ;; + esac +} + +# Stufe 2: Einzelauswahl der Dienste (Checkliste). +service_checklist() { + local choices=() + if command -v whiptail >/dev/null && [ -t 0 ]; then + local items=() + for s in "${SERVICES[@]}"; do items+=("${s%%|*}" "${s#*|}" OFF); done + local sel + sel="$(whiptail --title "OPENBUREAU — Dienste auswählen" \ + --checklist "Mit der Leertaste auswählen, Enter bestätigt:" 20 78 ${#SERVICES[@]} \ + "${items[@]}" 3>&1 1>&2 2>&3)" || { echo "Abgebrochen."; exit 0; } + eval "choices=($sel)" # whiptail liefert die Tags in Anführungszeichen + else + echo "Welche Dienste installieren? (Nummern mit Komma/Leerzeichen, Enter = nichts)" + local i=1 + for s in "${SERVICES[@]}"; do printf " %d) %s\n" "$i" "${s#*|}"; i=$((i+1)); done + read -rp "Auswahl: " line + for n in ${line//,/ }; do + [ "$n" -ge 1 ] 2>/dev/null && [ "$n" -le "${#SERVICES[@]}" ] && choices+=("${SERVICES[$((n-1))]%%|*}") + done + fi + [ "${#choices[@]}" -gt 0 ] || { echo "Nichts ausgewählt."; exit 0; } + for c in "${choices[@]}"; do run_from_menu "$c"; done +} + +# Stufe 1: Vorhaben wählen, dann das passende Bündel installieren. +choose_profile() { + local key="" + if command -v whiptail >/dev/null && [ -t 0 ]; then + local items=() first=ON + for p in "${PROFILES[@]}"; do items+=("${p%%|*}" "$(echo "$p" | cut -d'|' -f2)" "$first"); first=OFF; done + key="$(whiptail --title "OPENBUREAU — Was hast du vor?" \ + --radiolist "Vorhaben wählen (Leertaste markiert, Enter bestätigt):" 20 78 ${#PROFILES[@]} \ + "${items[@]}" 3>&1 1>&2 2>&3)" || { echo "Abgebrochen."; exit 0; } + else + echo "Was hast du vor? (eine Nummer)" + local i=1 + for p in "${PROFILES[@]}"; do printf " %d) %s\n" "$i" "$(echo "$p" | cut -d'|' -f2)"; i=$((i+1)); done + read -rp "Auswahl [1]: " n; n="${n:-1}" + [ "$n" -ge 1 ] 2>/dev/null && [ "$n" -le "${#PROFILES[@]}" ] || die "Ungültige Auswahl." + key="${PROFILES[$((n-1))]%%|*}" + fi + + if [ "$key" = "custom" ]; then + service_checklist + else + local svcs="" + for p in "${PROFILES[@]}"; do [ "${p%%|*}" = "$key" ] && svcs="${p##*|}"; done + say "Vorhaben '$key': installiere -> $svcs" + for s in $svcs; do run_service "$s"; done + fi +} + +################################# Einstieg ################################# +if [ "$#" -gt 0 ]; then + run_service "$@" # direkt, ohne Dialog +else + choose_profile # geführter Dialog: erst Vorhaben, dann Dienste +fi +say "Fertig. Alle ausgewählten Dienste sind durch." diff --git a/proxmox/nextcloud-lxc.sh b/proxmox/nextcloud-lxc.sh new file mode 100755 index 0000000..4ac42ac --- /dev/null +++ b/proxmox/nextcloud-lxc.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# +# Nextcloud (All-in-One) als eigener LXC für Proxmox VE. +# Dateien, Kalender, Kontakte, Office in einem verwalteten Container — +# ersetzt OneDrive / Synology-Drive + Office 365. +# +# AUF DEM PROXMOX-HOST, als root: +# bash <(curl -fsSL https://git.kgva.ch/karim/OPENBUREAU/raw/branch/main/proxmox/nextcloud-lxc.sh) [disk_gb] [ram_mb] +# +set -euo pipefail + +############################# gemeinsamer Kopf ############################# +SSH_PUBKEY_FILE="${SSH_PUBKEY_FILE:-$HOME/.ssh/id_ed25519.pub}" +ROOTFS_STORAGE="${ROOTFS_STORAGE:-local-lvm}" +TEMPLATE_STORAGE="${TEMPLATE_STORAGE:-local}" +BRIDGE="${BRIDGE:-vmbr0}" + +say() { echo -e "\n\033[1;36m▸ $*\033[0m"; } +ok() { echo -e "\033[1;32m✓ $*\033[0m"; } +warn() { echo -e "\033[1;33m! $*\033[0m" >&2; } +die() { echo -e "\033[1;31m✗ $*\033[0m" >&2; exit 1; } + +[ "$(id -u)" -eq 0 ] || die "Bitte als root auf dem Proxmox-Host ausführen." +command -v pct >/dev/null || die "pct nicht gefunden — läuft das wirklich auf Proxmox VE?" + +ensure_template() { + [ -n "${TEMPLATE_REF:-}" ] && return 0 + pveam update >/dev/null 2>&1 || true + local tpl + tpl="$(pveam available --section system | awk '/debian-12-standard/{print $2}' | sort -V | tail -1)" + [ -n "$tpl" ] || die "Kein debian-12-Template verfügbar." + pveam list "$TEMPLATE_STORAGE" | grep -q "$tpl" || { say "Lade Template $tpl…"; pveam download "$TEMPLATE_STORAGE" "$tpl" >/dev/null; } + TEMPLATE_REF="${TEMPLATE_STORAGE}:vztmpl/${tpl}" +} + +create_lxc() { + local name="$1" disk="$2" ram="$3" cores="${4:-2}" ctid + ensure_template + ctid="$(pvesh get /cluster/nextid)" + local args=( + "$ctid" "$TEMPLATE_REF" --hostname "$name" + --cores "$cores" --memory "$ram" --swap 1024 + --rootfs "${ROOTFS_STORAGE}:${disk}" + --net0 "name=eth0,bridge=${BRIDGE},ip=dhcp" + --unprivileged 1 --features "nesting=1,keyctl=1" --onboot 1 + ) + [ -f "$SSH_PUBKEY_FILE" ] && args+=(--ssh-public-keys "$SSH_PUBKEY_FILE") + say "Erstelle LXC $ctid ($name) — ${cores} Kerne, ${ram} MB RAM, ${disk} GB…" + pct create "${args[@]}" >/dev/null + pct start "$ctid"; sleep 5 + CTID="$ctid" +} + +install_docker() { + say "Installiere Docker in $1…" + pct exec "$1" -- bash -euo pipefail -c ' + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq ca-certificates curl git openssl >/dev/null + curl -fsSL https://get.docker.com | sh >/dev/null + systemctl enable --now docker + ' +} + +ip_of() { pct exec "$1" -- hostname -I 2>/dev/null | awk '{print $1}'; } + +################################# Dienst ################################# +# Grosszügig dimensioniert — hier leben die Bürodaten. +DISK="${1:-500}"; RAM="${2:-8192}" + +create_lxc nextcloud "$DISK" "$RAM" +install_docker "$CTID" + +say "Starte Nextcloud All-in-One (mastercontainer)…" +pct exec "$CTID" -- bash -euo pipefail -c ' + docker run -d --name nextcloud-aio-mastercontainer --restart always \ + -p 8080:8080 -e APACHE_PORT=11000 \ + -v nextcloud_aio_mastercontainer:/mnt/docker-aio-config \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + nextcloud/all-in-one:latest >/dev/null +' + +IP="$(ip_of "$CTID")" +ok "Nextcloud-LXC $CTID läuft." +cat <