feat: initial ThinkCentre setup repo — Android, Ollama, Otto migration docs + scripts

This commit is contained in:
Otto
2026-03-17 11:15:12 +01:00
commit 95f90c7dcb
8 changed files with 539 additions and 0 deletions
+70
View File
@@ -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
```
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# Android APK Build Wrapper — ThinkCentre 1
# Usage: bash build.sh <app> [branch]
# app: staysober | blockblitz | ticked
# branch: main (default)
#
# Output: /var/www/downloads/<app>/<app>-latest.apk
# Served: http://192.168.0.91:8080/<app>/
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 <app> [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"
+60
View File
@@ -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"
+62
View File
@@ -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 |
+102
View File
@@ -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
+66
View File
@@ -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 <model> # Download model
ollama rm <model> # Remove model
ollama run <model> # 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"
]
}
}
}
}
```
+26
View File
@@ -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}'"
+71
View File
@@ -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"