From 95f90c7dcbaf2031a2f5a792416d3dd27cdacce0 Mon Sep 17 00:00:00 2001 From: Otto Date: Tue, 17 Mar 2026 11:15:12 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20initial=20ThinkCentre=20setup=20repo=20?= =?UTF-8?q?=E2=80=94=20Android,=20Ollama,=20Otto=20migration=20docs=20+=20?= =?UTF-8?q?scripts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 70 ++++++++++++++++++++++++++++ android/build.sh | 82 +++++++++++++++++++++++++++++++++ android/setup.sh | 60 ++++++++++++++++++++++++ docs/management.md | 62 +++++++++++++++++++++++++ docs/otto-migration.md | 102 +++++++++++++++++++++++++++++++++++++++++ ollama/models.md | 66 ++++++++++++++++++++++++++ ollama/setup.sh | 26 +++++++++++ scripts/provision.sh | 71 ++++++++++++++++++++++++++++ 8 files changed, 539 insertions(+) create mode 100644 README.md create mode 100644 android/build.sh create mode 100644 android/setup.sh create mode 100644 docs/management.md create mode 100644 docs/otto-migration.md create mode 100644 ollama/models.md create mode 100644 ollama/setup.sh create mode 100644 scripts/provision.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..dfe2660 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# ZeitAnker ThinkCentre Setup + +Infrastructure docs, scripts, and runbooks for ThinkCentre 1 and 2. + +> ⚠️ No credentials in this repo. Secrets live in `/home/secondclaw/.openclaw/secrets/` and are managed manually. + +## Hardware + +| | ThinkCentre 1 | ThinkCentre 2 | +|---|---|---| +| **IP** | 192.168.0.91 | TBD | +| **User** | secondclaw | TBD | +| **CPU** | i5-13400T (10C/16T) | i5-13400T (10C/16T, baugleich) | +| **RAM** | 16 GB | 16 GB | +| **Disk** | 233 GB NVMe | 233 GB NVMe | +| **GPU** | Intel UHD 730 | Intel UHD 730 | +| **OS** | Ubuntu 24.04 LTS | Ubuntu 24.04 LTS | +| **Role** | Build Server + LLM | Otto (OpenClaw) | + +## ThinkCentre 1 — Roles + +### 1. Android APK Build Server +Local Gradle builds for StaySober, BlockBlitz, Ticked — replaces GitHub Actions. +- See `android/` for setup scripts and build wrapper +- Builds served via `lighttpd` on port 8080 (same as Pi currently) +- Trigger: SSH from Pi or manual + +### 2. Local LLM (Ollama) +- Model: `qwen2.5:7b` (default), swappable +- API: `http://192.168.0.91:11434` +- Use: Otto fallback inference, code review, summarization +- See `ollama/` for model management and prompt scripts + +### 3. Potential: Otto performance offload +- Heavy subagent tasks can be delegated here via API +- TBD once ThinkCentre 2 (Otto's new home) is set up + +## ThinkCentre 2 — Roles + +### Otto Migration +- OpenClaw fresh install (not in-place migration from Pi) +- Pi remains active until migration is validated +- See `docs/otto-migration.md` for migration plan + +## SSH Access (from Pi) + +```bash +ssh secondclaw@192.168.0.91 # ThinkCentre 1 +# Key already deployed: ~/.ssh/id_ed25519 +``` + +## Repo Structure + +``` +thinkcentre-setup/ +├── android/ +│ ├── setup.sh # One-shot Android SDK install +│ ├── build.sh # Build wrapper (takes app name + optional branch) +│ └── README.md +├── ollama/ +│ ├── setup.sh # Ollama install + default model pull +│ ├── models.md # Model comparison and RAM requirements +│ └── README.md +├── scripts/ +│ ├── provision.sh # Base Ubuntu provisioning (apt, docker, node, etc.) +│ └── ssh-keygen.sh # Deploy Pi SSH key to ThinkCentre +└── docs/ + ├── otto-migration.md # ThinkCentre 2 migration plan + └── management.md # Who manages what, runbooks +``` diff --git a/android/build.sh b/android/build.sh new file mode 100644 index 0000000..d4eb2df --- /dev/null +++ b/android/build.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# Android APK Build Wrapper — ThinkCentre 1 +# Usage: bash build.sh [branch] +# app: staysober | blockblitz | ticked +# branch: main (default) +# +# Output: /var/www/downloads//-latest.apk +# Served: http://192.168.0.91:8080// + +set -e + +APP="${1:-}" +BRANCH="${2:-main}" +BUILD_DIR="/tmp/builds" +SERVE_DIR="/var/www/downloads" +GIT_BASE="git@192.168.0.34" # Pi git server + +# App → repo mapping +declare -A REPOS=( + [staysober]="ottobringts/staysober" + [blockblitz]="ottobringts/blockblitz" + [ticked]="ottobringts/obsidian-tasks-app" +) + +if [[ -z "$APP" || -z "${REPOS[$APP]}" ]]; then + echo "Usage: $0 [branch]" + echo "Apps: ${!REPOS[@]}" + exit 1 +fi + +REPO="${REPOS[$APP]}" +WORK_DIR="$BUILD_DIR/$APP" +APK_OUT="$SERVE_DIR/$APP" + +echo "=== Building $APP ($BRANCH) ===" +echo "Repo: $REPO" + +# Clone / pull +if [ -d "$WORK_DIR/.git" ]; then + echo "Pulling latest..." + cd "$WORK_DIR" + git fetch origin + git checkout "$BRANCH" + git pull origin "$BRANCH" +else + echo "Cloning..." + mkdir -p "$BUILD_DIR" + git clone --branch "$BRANCH" "https://github.com/$REPO.git" "$WORK_DIR" + cd "$WORK_DIR" +fi + +# Set ANDROID_HOME +export ANDROID_HOME="$HOME/android-sdk" +export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools/34.0.0" + +# Expo prebuild if needed +if [ -f "app.json" ] && grep -q '"expo"' app.json; then + echo "Expo project detected — running prebuild..." + npx expo prebuild --platform android --no-install 2>&1 | tail -5 +fi + +# Gradle build +echo "Running Gradle assembleRelease..." +cd android 2>/dev/null || cd . +chmod +x gradlew +./gradlew assembleRelease --no-daemon --quiet 2>&1 | tail -20 + +# Find and copy APK +APK=$(find . -name "*.apk" -path "*/release/*" | head -1) +if [ -z "$APK" ]; then + echo "ERROR: No APK found!" + exit 1 +fi + +mkdir -p "$APK_OUT" +cp "$APK" "$APK_OUT/$APP-latest.apk" + +echo "" +echo "=== Build complete ===" +echo "APK: $APK_OUT/$APP-latest.apk" +echo "URL: http://192.168.0.91:8080/$APP/$APP-latest.apk" +ls -lh "$APK_OUT/$APP-latest.apk" diff --git a/android/setup.sh b/android/setup.sh new file mode 100644 index 0000000..6ec8f07 --- /dev/null +++ b/android/setup.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Android SDK Setup for ThinkCentre 1 +# Run as secondclaw — installs Java 17 + Android SDK + NDK +# Usage: bash setup.sh + +set -e + +ANDROID_HOME="$HOME/android-sdk" +SDK_TOOLS_URL="https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" + +echo "=== Android Build Environment Setup ===" + +# Java 17 +echo "[1/5] Java 17..." +sudo apt-get update -qq +sudo apt-get install -y -qq openjdk-17-jdk-headless wget unzip curl +java --version + +# Android CLI Tools +echo "[2/5] Android Command Line Tools..." +mkdir -p "$ANDROID_HOME/cmdline-tools" +cd /tmp +wget -q "$SDK_TOOLS_URL" -O cmdline-tools.zip +unzip -q cmdline-tools.zip -d "$ANDROID_HOME/cmdline-tools" +mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest" +rm cmdline-tools.zip + +# Environment +echo "[3/5] Environment variables..." +if ! grep -q ANDROID_HOME ~/.bashrc; then + cat >> ~/.bashrc << EOF + +# Android SDK +export ANDROID_HOME=\$HOME/android-sdk +export PATH=\$PATH:\$ANDROID_HOME/cmdline-tools/latest/bin:\$ANDROID_HOME/platform-tools:\$ANDROID_HOME/build-tools/34.0.0 +EOF +fi +export ANDROID_HOME +export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools" + +# SDK Components +echo "[4/5] SDK components (platform-tools, android-34, build-tools, NDK r26b)..." +yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \ + --sdk_root="$ANDROID_HOME" \ + "platform-tools" \ + "platforms;android-34" \ + "build-tools;34.0.0" \ + "ndk;26.1.10909125" + +# Accept licenses +echo "[5/5] Accepting licenses..." +yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$ANDROID_HOME" --licenses > /dev/null 2>&1 || true + +echo "" +echo "=== Done ===" +java --version +"$ANDROID_HOME/platform-tools/adb" version +echo "ANDROID_HOME=$ANDROID_HOME" +echo "" +echo "Reload shell: source ~/.bashrc" diff --git a/docs/management.md b/docs/management.md new file mode 100644 index 0000000..fd4c6cf --- /dev/null +++ b/docs/management.md @@ -0,0 +1,62 @@ +# ThinkCentre Management + +## Wer managed was + +| Aufgabe | Zuständig | Wie | +|---------|-----------|-----| +| Hardware (Ein/Aus, LAN) | Chris | physisch | +| OS Updates | Otto | `sudo apt upgrade` via SSH, monatlich | +| Android SDK Updates | Otto | `sdkmanager --update` bei Bedarf | +| Ollama Modelle | Otto | `ollama pull/rm` nach Absprache | +| OpenClaw Config | Otto + Chris | gemeinsam | +| Secrets | Chris | manuell, nie per Git | +| Backups | Otto | Cron-Job (täglich, nach Pi-Backup-Muster) | + +## Monitoring + +- **Uptime Kuma** (`uptime.zeitanker.digital`) — Push-Heartbeat von ThinkCentre 2 (Otto) +- **ThinkCentre 1** — kein Push-Monitor nötig (Build-Server, on-demand) + +## SSH Zugang + +```bash +# Von Pi aus: +ssh secondclaw@192.168.0.91 # ThinkCentre 1 +# Key: ~/.ssh/id_ed25519 (bereits deployed) + +# Von extern: via Pi als Jumphost +ssh -J claw@192.168.0.84 secondclaw@192.168.0.91 +``` + +## Runbooks + +### Neustart ThinkCentre 1 +```bash +ssh secondclaw@192.168.0.91 "sudo reboot" +# Nach ~60s wieder erreichbar +``` + +### Android Build auslösen (manuell) +```bash +ssh secondclaw@192.168.0.91 "bash ~/scripts/build.sh staysober main" +``` + +### Ollama Modell wechseln +```bash +ssh secondclaw@192.168.0.91 "ollama pull mistral:7b && ollama rm qwen2.5:7b" +``` + +### OS Update +```bash +ssh secondclaw@192.168.0.91 "sudo apt update && sudo apt upgrade -y && sudo apt autoremove -y" +``` + +## Netzwerk + +| Host | IP | Port | Dienst | +|------|----|----- |--------| +| ThinkCentre 1 | 192.168.0.91 | 22 | SSH | +| ThinkCentre 1 | 192.168.0.91 | 8080 | APK Download Server | +| ThinkCentre 1 | 192.168.0.91 | 11434 | Ollama API | +| ThinkCentre 2 | TBD | 22 | SSH | +| ThinkCentre 2 | TBD | 18789 | OpenClaw Gateway | diff --git a/docs/otto-migration.md b/docs/otto-migration.md new file mode 100644 index 0000000..07a4455 --- /dev/null +++ b/docs/otto-migration.md @@ -0,0 +1,102 @@ +# Otto Migration — Pi → ThinkCentre 2 + +## Ziel + +OpenClaw (Otto) von `192.168.0.84` (Pi, ARM64) auf ThinkCentre 2 (x86, i5-13400T, 16 GB) migrieren. + +## Vorteile + +| | Pi (aktuell) | ThinkCentre 2 | +|---|---|---| +| CPU | Cortex-A76 (4C) | i5-13400T (10C/16T) | +| RAM | 8 GB | 16 GB | +| Arch | ARM64 | x86_64 | +| Node builds | langsam | schnell | +| Expo/Gradle | hakelig | nativ | + +## Migrationsstrategie + +**Fresh Install** (nicht in-place) — sauberer Start, keine ARM-Reste. + +Pi bleibt aktiv bis ThinkCentre 2 vollständig validiert ist. + +## Schritt für Schritt + +### Phase 1 — ThinkCentre 2 vorbereiten + +```bash +# Base provisioning +bash scripts/provision.sh + +# OpenClaw fresh install +npm install -g openclaw +openclaw setup +``` + +### Phase 2 — Daten übertragen + +```bash +# Workspace (ohne secrets, ohne node_modules) +rsync -av --exclude='node_modules' --exclude='.git' \ + pi@192.168.0.84:~/.openclaw/workspace/ \ + ~/.openclaw/workspace/ + +# Secrets manuell übertragen (nie per Git!) +# - ~/.openclaw/secrets/ +# - ~/.openclaw/openclaw.json (enthält API Keys) + +# QMD collections neu indexieren (ARM → x86, Embeddings neu) +qmd embed -c vault +qmd embed -c workspace +``` + +### Phase 3 — Services migrieren + +```bash +# APK Server (lighttpd) +sudo apt install lighttpd +# Config: /etc/lighttpd/lighttpd.conf → serve /var/www/downloads auf :8080 + +# PM2 Services +pm2 start ecosystem.config.cjs # zeitanker-tasks +pm2 save +pm2 startup +``` + +### Phase 4 — Cutover + +```bash +# DNS/IP in relevanten Configs aktualisieren: +# - TOOLS.md: APK Server URL +# - MEMORY.md: Otto Pi IP +# - Uptime Kuma: Monitor auf neue IP +# - Discord Webhooks falls nötig + +# Pi-Heartbeat-Cron auf ThinkCentre 2 umziehen +# Uptime Kuma Push-URL aktualisieren +``` + +### Phase 5 — Validierung + +- [ ] OpenClaw antwortet auf Discord +- [ ] Heartbeat läuft (Uptime Kuma grün) +- [ ] QMD search funktioniert +- [ ] PM2 Services alle online +- [ ] APK Server erreichbar + +### Phase 6 — Pi dekommissionieren + +```bash +# Pi-Services stoppen +pm2 stop all + +# Pi als Fallback/Backup behalten oder neu nutzen für: +# - Home Assistant (wenn .59 stirbt) +# - Reserve +``` + +## Offene Fragen + +- [ ] ThinkCentre 2 IP bestimmen (nach Netzwerk-Einbindung) +- [ ] Neuer Hostname: `otto-tc` oder `otto-2`? +- [ ] Pi-Rolle nach Migration klären diff --git a/ollama/models.md b/ollama/models.md new file mode 100644 index 0000000..7b9f12d --- /dev/null +++ b/ollama/models.md @@ -0,0 +1,66 @@ +# Ollama Models — ThinkCentre 1 + +CPU-only inference (Intel UHD 730, no dedicated GPU). 16 GB RAM. + +## Installed + +| Model | Size | RAM | Speed (tok/s) | Best for | +|-------|------|-----|---------------|----------| +| `qwen2.5:7b` | ~4.7 GB | ~6 GB | ~15-25 | Default — code, German, reasoning | + +## Recommended Candidates + +| Model | Size | RAM needed | Notes | +|-------|------|-----------|-------| +| `qwen2.5:7b` ✅ | 4.7 GB | 6 GB | Best quality/speed ratio on CPU | +| `mistral:7b` | 4.1 GB | 5 GB | Strong English reasoning | +| `llama3.2:3b` | 2.0 GB | 3 GB | Fastest, lower quality | +| `qwen2.5:14b` | 9.0 GB | 11 GB | Better quality, slower (~8 tok/s) | +| `deepseek-r1:7b` | 4.7 GB | 6 GB | Strong at reasoning/math | +| `nomic-embed-text` | 0.3 GB | 1 GB | Embeddings (QMD alternative) | + +## API + +```bash +# Chat +curl http://192.168.0.91:11434/api/generate -d '{ + "model": "qwen2.5:7b", + "prompt": "Your prompt here", + "stream": false +}' + +# Via OpenAI-compatible endpoint +curl http://192.168.0.91:11434/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{"model":"qwen2.5:7b","messages":[{"role":"user","content":"Hello"}]}' +``` + +## Management + +```bash +ollama list # Installed models +ollama pull # Download model +ollama rm # Remove model +ollama run # Interactive chat +sudo systemctl status ollama +sudo systemctl restart ollama +``` + +## OpenClaw Integration (future) + +Add to `openclaw.json` as fallback: +```json +{ + "agents": { + "defaults": { + "model": { + "fallbacks": [ + "openrouter/anthropic/claude-sonnet-4-6", + "ollama/qwen2.5:7b@http://192.168.0.91:11434", + "google/gemini-2.5-flash" + ] + } + } + } +} +``` diff --git a/ollama/setup.sh b/ollama/setup.sh new file mode 100644 index 0000000..404fe20 --- /dev/null +++ b/ollama/setup.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Ollama Setup — ThinkCentre 1 +# Usage: bash setup.sh [model] +# Default model: qwen2.5:7b + +set -e +MODEL="${1:-qwen2.5:7b}" + +echo "=== Ollama Setup ===" + +echo "[1/3] Installing Ollama..." +curl -fsSL https://ollama.ai/install.sh | sh + +echo "[2/3] Enabling service..." +sudo systemctl enable ollama +sudo systemctl start ollama +sleep 5 + +echo "[3/3] Pulling default model: $MODEL (~5 GB, dauert je nach Netz 5-15 min)..." +ollama pull "$MODEL" + +echo "" +echo "=== Done ===" +ollama list +echo "" +echo "Test: curl http://localhost:11434/api/generate -d '{\"model\":\"$MODEL\",\"prompt\":\"Hello\",\"stream\":false}'" diff --git a/scripts/provision.sh b/scripts/provision.sh new file mode 100644 index 0000000..b002e1b --- /dev/null +++ b/scripts/provision.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# Base Provisioning — ThinkCentre 1 + 2 +# Run as target user (secondclaw) with NOPASSWD sudo +# Usage: bash provision.sh + +set -e + +echo "=== ZeitAnker ThinkCentre Base Provisioning ===" + +echo "[1/6] System update..." +sudo apt-get update -qq +sudo apt-get upgrade -y -qq +sudo apt-get install -y -qq \ + curl wget git unzip build-essential \ + htop tmux vim jq \ + lighttpd \ + ca-certificates gnupg lsb-release + +echo "[2/6] Node.js 22 (LTS)..." +if ! command -v node &>/dev/null; then + curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - + sudo apt-get install -y nodejs +fi +node --version +npm --version + +echo "[3/6] Docker (if not installed)..." +if ! command -v docker &>/dev/null; then + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker "$USER" +fi +docker --version + +echo "[4/6] PM2..." +npm install -g pm2 2>/dev/null || true +pm2 --version + +echo "[5/6] lighttpd APK Server..." +sudo mkdir -p /var/www/downloads +sudo chown "$USER:$USER" /var/www/downloads +sudo tee /etc/lighttpd/lighttpd.conf > /dev/null << 'EOF' +server.modules = ( "mod_dirlisting" ) +server.document-root = "/var/www/downloads" +server.port = 8080 +dir-listing.activate = "enable" +mimetype.assign = ( + ".apk" => "application/vnd.android.package-archive", + ".html" => "text/html", +) +EOF +sudo systemctl enable lighttpd +sudo systemctl restart lighttpd + +echo "[6/6] SSH key (from Pi)..." +mkdir -p ~/.ssh && chmod 700 ~/.ssh +# Pi's public key — allows Otto to SSH in without password +PI_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP0AOGdfsP2pewLTZ5yq9podhDaPinPqLeI148tecY5o ottobringts@gmail.com" +grep -qF "$PI_KEY" ~/.ssh/authorized_keys 2>/dev/null || echo "$PI_KEY" >> ~/.ssh/authorized_keys +chmod 600 ~/.ssh/authorized_keys + +echo "" +echo "=== Provisioning complete ===" +echo "Node: $(node --version)" +echo "Docker: $(docker --version)" +echo "PM2: $(pm2 --version)" +echo "lighttpd: http://$(hostname -I | awk '{print $1}'):8080" +echo "" +echo "Next steps:" +echo " Android: bash android/setup.sh" +echo " Ollama: bash ollama/setup.sh" +echo " OpenClaw (TC2 only): npm install -g openclaw && openclaw setup"