Instructions to use jpanasuk/basecamp with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- HERMES
How to use jpanasuk/basecamp with HERMES:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
v1.0.5: self-healing (tavern self-check + update-check + CI pitfall testing), Fixer's toolbox in image (db clients, docker CLI, yq, net diag), 12-service coding starter pack with verified pitfalls in recipes
Browse files- .github/workflows/recipe-verify.yml +72 -0
- Dockerfile +22 -2
- README.md +72 -2
- discover.py +239 -1
- docker-compose.starter.yml +220 -0
- recipes.py +50 -0
.github/workflows/recipe-verify.yml
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: basecamp-recipe-verify
|
| 2 |
+
|
| 3 |
+
# SELF-HEAL #3 β "test pitfalls in the cloud". Runs basecamp's verification
|
| 4 |
+
# against a freshly-started coding starter pack on every push AND on a
|
| 5 |
+
# schedule, so recipe drift (a "fix" that stopped working because an image
|
| 6 |
+
# moved) gets caught before users hit it.
|
| 7 |
+
#
|
| 8 |
+
# What it does:
|
| 9 |
+
# 1. Boots the basecamp container (this repo's own image build)
|
| 10 |
+
# 2. Starts the coding starter pack (docker-compose.starter.yml)
|
| 11 |
+
# 3. Runs `tavern self-check` (every service verified against recipes)
|
| 12 |
+
# 4. Runs `tavern update-check` (newest registry tags reported)
|
| 13 |
+
# 5. Fails the run if self-check reports any RED service
|
| 14 |
+
#
|
| 15 |
+
# When recipes are stale, the run fails loudly and the fix (new tag, new
|
| 16 |
+
# pitfall note in recipes.py) is a small PR. That's the cloud pitfall test.
|
| 17 |
+
|
| 18 |
+
on:
|
| 19 |
+
push:
|
| 20 |
+
branches: [main]
|
| 21 |
+
schedule:
|
| 22 |
+
# nightly pitfall sweep
|
| 23 |
+
- cron: "17 3 * * *"
|
| 24 |
+
workflow_dispatch: {}
|
| 25 |
+
|
| 26 |
+
jobs:
|
| 27 |
+
verify:
|
| 28 |
+
runs-on: ubuntu-latest
|
| 29 |
+
timeout-minutes: 30
|
| 30 |
+
steps:
|
| 31 |
+
- uses: actions/checkout@v4
|
| 32 |
+
|
| 33 |
+
- name: Set up Docker Buildx
|
| 34 |
+
uses: docker/setup-buildx-action@v3
|
| 35 |
+
|
| 36 |
+
- name: Build basecamp image
|
| 37 |
+
run: docker build -t local/basecamp:ci .
|
| 38 |
+
|
| 39 |
+
- name: Start the coding starter pack
|
| 40 |
+
run: |
|
| 41 |
+
# CI has no pre-made ai-network; create it under the same name so
|
| 42 |
+
# the compose file works unchanged.
|
| 43 |
+
docker network create tabby-tavern_ai-network || true
|
| 44 |
+
docker compose -f docker-compose.starter.yml up -d
|
| 45 |
+
# Give the slow starters time to initialize
|
| 46 |
+
sleep 60
|
| 47 |
+
|
| 48 |
+
- name: Run tavern self-check (the pitfall test)
|
| 49 |
+
run: |
|
| 50 |
+
docker run --rm --network tabby-tavern_ai-network \
|
| 51 |
+
-e BASECAMP_CONFIG=/tmp/basecamp_config.json \
|
| 52 |
+
-e BASECAMP_DISCOVERY=/tmp/discovery.json \
|
| 53 |
+
-e BASECAMP_ENV_FILE=/tmp/basecamp.env \
|
| 54 |
+
--entrypoint python3 local/basecamp:ci \
|
| 55 |
+
/opt/basecamp/discover.py self-check
|
| 56 |
+
# self-check exits non-zero when any service is RED β the job fails
|
| 57 |
+
|
| 58 |
+
- name: Run tavern update-check (registry drift report)
|
| 59 |
+
run: |
|
| 60 |
+
docker run --rm --network tabby-tavern_ai-network \
|
| 61 |
+
-e BASECAMP_CONFIG=/tmp/basecamp_config.json \
|
| 62 |
+
-e BASECAMP_DISCOVERY=/tmp/discovery.json \
|
| 63 |
+
-e BASECAMP_ENV_FILE=/tmp/basecamp.env \
|
| 64 |
+
--entrypoint python3 local/basecamp:ci \
|
| 65 |
+
/opt/basecamp/discover.py update-check || true
|
| 66 |
+
# update-check is informational β newer tags are a report, not a failure
|
| 67 |
+
|
| 68 |
+
- name: Tear down
|
| 69 |
+
if: always()
|
| 70 |
+
run: |
|
| 71 |
+
docker compose -f docker-compose.starter.yml down -v || true
|
| 72 |
+
docker network rm tabby-tavern_ai-network || true
|
Dockerfile
CHANGED
|
@@ -7,9 +7,29 @@ ENV BASECAMP_MODEL=llama3.1:8b
|
|
| 7 |
|
| 8 |
# ββ System deps ββ
|
| 9 |
# xz-utils is required by the Hermes installer (Node.js .tar.xz archive)
|
|
|
|
|
|
|
| 10 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 11 |
-
python3 curl wget git ca-certificates xz-utils zstd \
|
| 12 |
-
supervisor procps jq \
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
&& rm -rf /var/lib/apt/lists/*
|
| 14 |
|
| 15 |
# ββ Install Ollama ββ
|
|
|
|
| 7 |
|
| 8 |
# ββ System deps ββ
|
| 9 |
# xz-utils is required by the Hermes installer (Node.js .tar.xz archive)
|
| 10 |
+
# The FIXER TOOLBOX: every CLI basecamp hermes needs to actually EXECUTE
|
| 11 |
+
# fixes, not just describe them (databases, docker, yaml, networking).
|
| 12 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 13 |
+
python3 python3-pip curl wget git ca-certificates xz-utils zstd \
|
| 14 |
+
supervisor procps jq gnupg \
|
| 15 |
+
# database clients β postgres/pgvector, sqlite, redis
|
| 16 |
+
postgresql-client sqlite3 redis-tools \
|
| 17 |
+
# docker CLI + compose plugin (for managing the stack when the
|
| 18 |
+
# docker socket is mounted into basecamp)
|
| 19 |
+
docker.io docker-compose-v2 \
|
| 20 |
+
# network diagnostics β the fixer must be able to SEE the network
|
| 21 |
+
iputils-ping dnsutils netcat-openbsd lsof \
|
| 22 |
+
# misc surgical tools
|
| 23 |
+
htop unzip file \
|
| 24 |
+
&& rm -rf /var/lib/apt/lists/* \
|
| 25 |
+
# yq (yaml editor) β not in Ubuntu 22.04 apt; official static binary
|
| 26 |
+
&& curl -fsSL -o /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64" \
|
| 27 |
+
&& chmod +x /usr/local/bin/yq \
|
| 28 |
+
# mongosh (mongo shell) β 'mongodb-clients' doesn't exist on 22.04;
|
| 29 |
+
# install the official shell from MongoDB's apt repo instead.
|
| 30 |
+
&& curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb.gpg \
|
| 31 |
+
&& echo "deb [signed-by=/usr/share/keyrings/mongodb.gpg] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" > /etc/apt/sources.list.d/mongodb.list \
|
| 32 |
+
&& apt-get update && apt-get install -y --no-install-recommends mongodb-mongosh \
|
| 33 |
&& rm -rf /var/lib/apt/lists/*
|
| 34 |
|
| 35 |
# ββ Install Ollama ββ
|
README.md
CHANGED
|
@@ -17,6 +17,22 @@ library_name: docker
|
|
| 17 |
|
| 18 |
# ποΈ Basecamp β The First Portable AI Agent Container with Network Auto-Discovery
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
**v1.0.4** β the fixer release. Basecamp Hermes now carries a soul:
|
| 21 |
- **The Fixer** β Basecamp Hermes has a purpose: making stacks talk to
|
| 22 |
themselves. The SOUL.md gives it identity and a Fixer's Creed (name the
|
|
@@ -82,6 +98,60 @@ vLLM, LiteLLM, LocalAI, llama.cpp, SearXNG, MCPO, Open WebUI, SillyTavern β
|
|
| 82 |
and connects Hermes to the best brain it finds. No hardcoded URLs. No config
|
| 83 |
files to hand-edit. Just `docker run`.
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
## Why this is different
|
| 86 |
|
| 87 |
| Everyone else | Basecamp |
|
|
@@ -116,7 +186,7 @@ install on the host that runs it:
|
|
| 116 |
|
| 117 |
```bash
|
| 118 |
# Option A: pull the prebuilt image (Docker Hub)
|
| 119 |
-
docker pull jpanasuk/basecamp:1.0.
|
| 120 |
|
| 121 |
# Option B: build from source
|
| 122 |
docker build -t local/basecamp:latest .
|
|
@@ -129,7 +199,7 @@ docker run -it --rm \
|
|
| 129 |
-p 11436:11434 \
|
| 130 |
-v basecamp-ollama:/root/.ollama \
|
| 131 |
-v basecamp-hermes:/root/.hermes \
|
| 132 |
-
jpanasuk/basecamp:1.0.
|
| 133 |
```
|
| 134 |
|
| 135 |
Or use the convenience launcher (auto-detects network + GPU, TTY-safe):
|
|
|
|
| 17 |
|
| 18 |
# ποΈ Basecamp β The First Portable AI Agent Container with Network Auto-Discovery
|
| 19 |
|
| 20 |
+
**v1.0.5** β the self-healing release:
|
| 21 |
+
- **The Fixer's toolbox** β basecamp now ships with every CLI it needs to
|
| 22 |
+
EXECUTE fixes: postgres/mongosh/sqlite/redis clients, docker CLI +
|
| 23 |
+
compose, yq, and full network diagnostics. It doesn't just describe the
|
| 24 |
+
fix β it can run it (when the docker socket is mounted).
|
| 25 |
+
- **`tavern self-check`** β verifies every discovered service against its
|
| 26 |
+
recipe and reports β
/β οΈ/β (self-heal #1). "The fixer approves."
|
| 27 |
+
- **`tavern update-check`** β polls Docker Hub/GHCR for newer image tags
|
| 28 |
+
and reports what moved (self-heal #2).
|
| 29 |
+
- **CI pitfall testing** β GitHub Actions boots the full coding pack in
|
| 30 |
+
the cloud on every push + nightly, runs self-check, and fails loudly on
|
| 31 |
+
recipe drift (self-heal #3).
|
| 32 |
+
- **Coding starter pack** β 12 services incl. postgres/pgvector memory +
|
| 33 |
+
mongo, with all verified pitfalls baked into the recipes (tabby pin,
|
| 34 |
+
flowise pin, librechat mongo requirement, GHCR locations).
|
| 35 |
+
|
| 36 |
**v1.0.4** β the fixer release. Basecamp Hermes now carries a soul:
|
| 37 |
- **The Fixer** β Basecamp Hermes has a purpose: making stacks talk to
|
| 38 |
themselves. The SOUL.md gives it identity and a Fixer's Creed (name the
|
|
|
|
| 98 |
and connects Hermes to the best brain it finds. No hardcoded URLs. No config
|
| 99 |
files to hand-edit. Just `docker run`.
|
| 100 |
|
| 101 |
+
## Self-healing (the fixer maintains itself)
|
| 102 |
+
|
| 103 |
+
Basecamp ships three self-healing layers so its wiring knowledge stays
|
| 104 |
+
true even as the ecosystem moves:
|
| 105 |
+
|
| 106 |
+
1. **`tavern self-check`** β runs every discovered service against its
|
| 107 |
+
recipe's verification step and reports β
/β οΈ/β. Catches stale recipes
|
| 108 |
+
and broken services before a user hits them. "The fixer approves."
|
| 109 |
+
2. **`tavern update-check`** β polls Docker Hub / GHCR for the newest
|
| 110 |
+
published tag of every discovered service and reports what moved, so
|
| 111 |
+
you can pull the newer image before an old one rots.
|
| 112 |
+
3. **CI pitfall testing** (`.github/workflows/recipe-verify.yml`) β on
|
| 113 |
+
every push and nightly, GitHub Actions boots the full coding starter
|
| 114 |
+
pack in the cloud, runs `tavern self-check`, and fails loudly when a
|
| 115 |
+
recipe has gone stale (an image moved, a flag changed, a service
|
| 116 |
+
renamed). Recipe drift gets caught in CI, not on a user's box.
|
| 117 |
+
|
| 118 |
+
## The Coding Starter Pack (10 best-of-breed AI coding tools)
|
| 119 |
+
|
| 120 |
+
Basecamp ships a companion compose file that stands up a complete AI coding
|
| 121 |
+
environment β every tool in its own container, all on the same Docker
|
| 122 |
+
network, all wired to your stack's Ollama + TabbyAPI, all able to call each
|
| 123 |
+
other by container name.
|
| 124 |
+
|
| 125 |
+
```bash
|
| 126 |
+
cd basecamp
|
| 127 |
+
docker compose -f docker-compose.starter.yml up -d
|
| 128 |
+
basecamp rediscover # basecamp finds the whole pack
|
| 129 |
+
basecamp wire # wiring audit
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
| # | Service | What it is | Port |
|
| 133 |
+
|---|---------|-----------|------|
|
| 134 |
+
| 1 | **code-server** | VS Code in the browser (the IDE) | 8443 |
|
| 135 |
+
| 2 | **tabby** | Self-hosted AI code completion (Copilot replacement) | 8082 |
|
| 136 |
+
| 3 | **qdrant** | Vector DB β code RAG backbone | 6333 |
|
| 137 |
+
| 4 | **chroma** | Lightweight vector DB (alternative) | 8005 |
|
| 138 |
+
| 5 | **n8n** | Workflow automation | 5678 |
|
| 139 |
+
| 6 | **lobe-chat** | AI chat UI (wired to stack Ollama) | 3210 |
|
| 140 |
+
| 7 | **anythingllm** | RAG workspace (docs + codebase Q&A) | 3001 |
|
| 141 |
+
| 8 | **librechat** | Multi-model chat UI (needs mongo β included) | 3080 |
|
| 142 |
+
| 9 | **meilisearch** | Full-text search (code/docs search) | 7700 |
|
| 143 |
+
| 10 | **flowise** | No-code agent builder | 7860 |
|
| 144 |
+
| 11 | **postgres** | Postgres + pgvector β Hermes long-term memory | 5432 |
|
| 145 |
+
| 12 | **mongo** | MongoDB β LibreChat's database | 27017 |
|
| 146 |
+
|
| 147 |
+
The IDE comes pre-loaded with **Continue.dev** (AI assistant) and the
|
| 148 |
+
**Tabby** extension (inline completions). Point Continue at
|
| 149 |
+
`http://ollama:11434/v1` (or `http://tabbyapi:5000/v1`) and Tabby at
|
| 150 |
+
`http://starter-tabby:8082` β your stack's models power the whole pack.
|
| 151 |
+
|
| 152 |
+
Default credentials are in the compose file β change them before exposing
|
| 153 |
+
anything publicly.
|
| 154 |
+
|
| 155 |
## Why this is different
|
| 156 |
|
| 157 |
| Everyone else | Basecamp |
|
|
|
|
| 186 |
|
| 187 |
```bash
|
| 188 |
# Option A: pull the prebuilt image (Docker Hub)
|
| 189 |
+
docker pull jpanasuk/basecamp:1.0.5
|
| 190 |
|
| 191 |
# Option B: build from source
|
| 192 |
docker build -t local/basecamp:latest .
|
|
|
|
| 199 |
-p 11436:11434 \
|
| 200 |
-v basecamp-ollama:/root/.ollama \
|
| 201 |
-v basecamp-hermes:/root/.hermes \
|
| 202 |
+
jpanasuk/basecamp:1.0.5
|
| 203 |
```
|
| 204 |
|
| 205 |
Or use the convenience launcher (auto-detects network + GPU, TTY-safe):
|
discover.py
CHANGED
|
@@ -373,6 +373,39 @@ SERVICE_PROBES = {
|
|
| 373 |
"label": "KoboldAI Horde (crowd inference)",
|
| 374 |
"icon": "HDE",
|
| 375 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
}
|
| 377 |
|
| 378 |
# ββ Port expectations ββ
|
|
@@ -403,11 +436,19 @@ PORT_SERVICE_MAP = {
|
|
| 403 |
8081: ["weaviate"],
|
| 404 |
1551: ["koboldai"],
|
| 405 |
2323: ["kobold-horde"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
}
|
| 407 |
|
| 408 |
COMMON_PORTS = [11434, 5000, 8000, 8080, 8001, 3000, 11435, 11436,
|
| 409 |
5001, 1234, 8002, 4000, 3001, 3080, 7681, 5678,
|
| 410 |
-
7860, 8188, 9090, 6333, 19530, 8081, 1551, 2323
|
|
|
|
| 411 |
|
| 412 |
|
| 413 |
# ββ HTTP helpers ββ
|
|
@@ -934,6 +975,14 @@ for p,methods in d.get('paths',{{}}).items():
|
|
| 934 |
echo "Running stack wiring audit..."
|
| 935 |
python3 /opt/basecamp/discover.py wire
|
| 936 |
;;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 937 |
help|--help|-h)
|
| 938 |
echo "tavern β Basecamp AI stack toolkit (auto-generated)"
|
| 939 |
echo ""
|
|
@@ -946,6 +995,8 @@ for p,methods in d.get('paths',{{}}).items():
|
|
| 946 |
echo " mcp <tool> [json] Call an MCP tool"
|
| 947 |
echo " config Show saved configuration"
|
| 948 |
echo " wire Audit stack wiring + print fixes"
|
|
|
|
|
|
|
| 949 |
echo " rediscover Re-scan for services"
|
| 950 |
;;
|
| 951 |
*)
|
|
@@ -1430,6 +1481,181 @@ def wire_audit(services, config=None):
|
|
| 1430 |
print()
|
| 1431 |
|
| 1432 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1433 |
def main():
|
| 1434 |
if len(sys.argv) < 2:
|
| 1435 |
print(__doc__)
|
|
@@ -1546,6 +1772,18 @@ def main():
|
|
| 1546 |
with open(CONFIG_FILE) as f:
|
| 1547 |
config = json.load(f)
|
| 1548 |
wire_audit(services, config)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1549 |
else:
|
| 1550 |
print(f"Unknown action: {action}")
|
| 1551 |
print(__doc__)
|
|
|
|
| 373 |
"label": "KoboldAI Horde (crowd inference)",
|
| 374 |
"icon": "HDE",
|
| 375 |
},
|
| 376 |
+
# ββ Coding starter pack ββ
|
| 377 |
+
"code-server": {
|
| 378 |
+
"paths": ["/", "/healthz"],
|
| 379 |
+
"method": "GET",
|
| 380 |
+
"match": lambda r: "code-server" in r.lower() or "coder" in r.lower() or "vscode" in r.lower() or "404: Not Found" in r,
|
| 381 |
+
"parse": lambda r: {},
|
| 382 |
+
"label": "code-server (VS Code in browser)",
|
| 383 |
+
"icon": "IDE",
|
| 384 |
+
},
|
| 385 |
+
"tabby": {
|
| 386 |
+
"paths": ["/v1/health", "/api/health"],
|
| 387 |
+
"method": "GET",
|
| 388 |
+
"match": lambda r: '"health"' in r or '"model"' in r or "tabby" in r.lower(),
|
| 389 |
+
"parse": lambda r: {},
|
| 390 |
+
"label": "Tabby (AI code completion)",
|
| 391 |
+
"icon": "CPL",
|
| 392 |
+
},
|
| 393 |
+
"meilisearch": {
|
| 394 |
+
"paths": ["/health", "/"],
|
| 395 |
+
"method": "GET",
|
| 396 |
+
"match": lambda r: '"status":"available"' in r or "meilisearch" in r.lower(),
|
| 397 |
+
"parse": lambda r: {},
|
| 398 |
+
"label": "Meilisearch (full-text search)",
|
| 399 |
+
"icon": "SRH",
|
| 400 |
+
},
|
| 401 |
+
"mongo": {
|
| 402 |
+
"paths": ["/"],
|
| 403 |
+
"method": "GET",
|
| 404 |
+
"match": lambda r: "mongodb" in r.lower() or "mongo" in r.lower() or "It looks like you are trying to access MongoDB over HTTP" in r,
|
| 405 |
+
"parse": lambda r: {},
|
| 406 |
+
"label": "MongoDB (database)",
|
| 407 |
+
"icon": "DB ",
|
| 408 |
+
},
|
| 409 |
}
|
| 410 |
|
| 411 |
# ββ Port expectations ββ
|
|
|
|
| 436 |
8081: ["weaviate"],
|
| 437 |
1551: ["koboldai"],
|
| 438 |
2323: ["kobold-horde"],
|
| 439 |
+
8443: ["code-server"],
|
| 440 |
+
8082: ["tabby"],
|
| 441 |
+
7700: ["meilisearch"],
|
| 442 |
+
6334: ["qdrant"],
|
| 443 |
+
3210: ["lobe-chat"],
|
| 444 |
+
5678: ["n8n"],
|
| 445 |
+
27017: ["mongo"],
|
| 446 |
}
|
| 447 |
|
| 448 |
COMMON_PORTS = [11434, 5000, 8000, 8080, 8001, 3000, 11435, 11436,
|
| 449 |
5001, 1234, 8002, 4000, 3001, 3080, 7681, 5678,
|
| 450 |
+
7860, 8188, 9090, 6333, 19530, 8081, 1551, 2323,
|
| 451 |
+
8443, 8082, 7700, 6334, 3210, 27017]
|
| 452 |
|
| 453 |
|
| 454 |
# ββ HTTP helpers ββ
|
|
|
|
| 975 |
echo "Running stack wiring audit..."
|
| 976 |
python3 /opt/basecamp/discover.py wire
|
| 977 |
;;
|
| 978 |
+
self-check|selfcheck|doctor)
|
| 979 |
+
echo "Running self-check (verifying every service)..."
|
| 980 |
+
python3 /opt/basecamp/discover.py self-check
|
| 981 |
+
;;
|
| 982 |
+
update-check|updatecheck|updates)
|
| 983 |
+
echo "Checking registries for newer versions..."
|
| 984 |
+
python3 /opt/basecamp/discover.py update-check
|
| 985 |
+
;;
|
| 986 |
help|--help|-h)
|
| 987 |
echo "tavern β Basecamp AI stack toolkit (auto-generated)"
|
| 988 |
echo ""
|
|
|
|
| 995 |
echo " mcp <tool> [json] Call an MCP tool"
|
| 996 |
echo " config Show saved configuration"
|
| 997 |
echo " wire Audit stack wiring + print fixes"
|
| 998 |
+
echo " self-check Verify every service (self-heal #1)"
|
| 999 |
+
echo " update-check Check registries for newer versions (self-heal #2)"
|
| 1000 |
echo " rediscover Re-scan for services"
|
| 1001 |
;;
|
| 1002 |
*)
|
|
|
|
| 1481 |
print()
|
| 1482 |
|
| 1483 |
|
| 1484 |
+
def self_check(services=None):
|
| 1485 |
+
"""SELF-HEAL #1 β run every service's recipe verify step and report.
|
| 1486 |
+
|
| 1487 |
+
For each discovered service, probe it exactly like its recipe's verify
|
| 1488 |
+
step would, and report β
/β οΈ/β. This catches stale recipes and broken
|
| 1489 |
+
services before a user hits them. The 'fixer' validating its own box.
|
| 1490 |
+
"""
|
| 1491 |
+
if services is None:
|
| 1492 |
+
services = scan_network()
|
| 1493 |
+
try:
|
| 1494 |
+
import recipes as _recipes
|
| 1495 |
+
except Exception:
|
| 1496 |
+
_recipes = None
|
| 1497 |
+
|
| 1498 |
+
print()
|
| 1499 |
+
print(" βββ SELF-CHECK β verifying every discovered service βββ")
|
| 1500 |
+
print()
|
| 1501 |
+
results = [] # (status, label, detail)
|
| 1502 |
+
for s in services:
|
| 1503 |
+
label = s["label"]
|
| 1504 |
+
url = s["url"]
|
| 1505 |
+
# Auth-gated services: 401/403/404 = ALIVE (behind the wall or no
|
| 1506 |
+
# root route) = OK-ish. Only connection failures are RED.
|
| 1507 |
+
if s.get("needs_auth"):
|
| 1508 |
+
code = None
|
| 1509 |
+
try:
|
| 1510 |
+
req = urllib.request.Request(url, method="GET")
|
| 1511 |
+
urllib.request.urlopen(req, timeout=3)
|
| 1512 |
+
code = 200
|
| 1513 |
+
except urllib.error.HTTPError as e:
|
| 1514 |
+
code = e.code
|
| 1515 |
+
except Exception:
|
| 1516 |
+
code = None
|
| 1517 |
+
if code in (200, 401, 403, 404):
|
| 1518 |
+
results.append(("OK", label, f"alive (auth-walled, HTTP {code})"))
|
| 1519 |
+
else:
|
| 1520 |
+
results.append(("RED", label, f"no response (HTTP {code})"))
|
| 1521 |
+
continue
|
| 1522 |
+
# Open services: probe their recipe's primary path. A 404 on the
|
| 1523 |
+
# root path is often ALIVE (service exists, just no root route) β
|
| 1524 |
+
# treat 404/401/403 as alive, only connection failures are RED.
|
| 1525 |
+
code = None
|
| 1526 |
+
try:
|
| 1527 |
+
req = urllib.request.Request(url, method="GET")
|
| 1528 |
+
with urllib.request.urlopen(req, timeout=3) as r:
|
| 1529 |
+
body = r.read().decode(errors="replace")
|
| 1530 |
+
code = r.status
|
| 1531 |
+
except urllib.error.HTTPError as e:
|
| 1532 |
+
code = e.code
|
| 1533 |
+
except Exception:
|
| 1534 |
+
code = None
|
| 1535 |
+
if code in (200, 301, 302, 307, 404, 401, 403):
|
| 1536 |
+
detail = f"responding at {url} (HTTP {code})"
|
| 1537 |
+
results.append(("OK", label, detail))
|
| 1538 |
+
else:
|
| 1539 |
+
results.append(("RED", label, f"NOT responding at {url} (HTTP {code})"))
|
| 1540 |
+
|
| 1541 |
+
# Recipe coverage report
|
| 1542 |
+
if _recipes is not None:
|
| 1543 |
+
types = {s.get("type") for s in services}
|
| 1544 |
+
covered = sum(1 for t in types if t in _recipes.WIRING_RECIPES)
|
| 1545 |
+
results.append(("INFO", f"recipe coverage",
|
| 1546 |
+
f"{covered}/{len(types)} discovered types have fix recipes"))
|
| 1547 |
+
|
| 1548 |
+
for status, label, detail in results:
|
| 1549 |
+
icon = {"OK": "β
", "RED": "β", "INFO": "βΉοΈ"}.get(status, "Β·")
|
| 1550 |
+
print(f" {icon} [{status:4s}] {label}")
|
| 1551 |
+
print(f" {detail}")
|
| 1552 |
+
print()
|
| 1553 |
+
|
| 1554 |
+
n_ok = sum(1 for r in results if r[0] == "OK")
|
| 1555 |
+
n_red = sum(1 for r in results if r[0] == "RED")
|
| 1556 |
+
print(f" ββ {n_ok} healthy Β· {n_red} failing ββ")
|
| 1557 |
+
if n_red:
|
| 1558 |
+
print(" Run 'tavern wire' for the exact fixes. Re-run after fixing.")
|
| 1559 |
+
else:
|
| 1560 |
+
print(" Everything basecamp can see is alive. The fixer approves.")
|
| 1561 |
+
print()
|
| 1562 |
+
return n_red
|
| 1563 |
+
|
| 1564 |
+
|
| 1565 |
+
# Known image repos per service type for update-check (Docker Hub tags API)
|
| 1566 |
+
UPDATE_CHECK_REPOS = {
|
| 1567 |
+
"code-server": "codercom/code-server",
|
| 1568 |
+
"tabby": "tabbyml/tabby",
|
| 1569 |
+
"qdrant": "qdrant/qdrant",
|
| 1570 |
+
"chroma": "chromadb/chroma",
|
| 1571 |
+
"n8n": "n8nio/n8n",
|
| 1572 |
+
"lobe-chat": "lobehub/lobe-chat",
|
| 1573 |
+
"anythingllm": "mintplexlabs/anythingllm",
|
| 1574 |
+
"librechat": "danny-avila/librechat", # GHCR actually; hub may 404
|
| 1575 |
+
"meilisearch": "getmeili/meilisearch",
|
| 1576 |
+
"flowise": "flowiseai/flowise",
|
| 1577 |
+
"postgres": "pgvector/pgvector",
|
| 1578 |
+
"mongo": "library/mongo",
|
| 1579 |
+
"ollama": "ollama/ollama",
|
| 1580 |
+
"searxng": "searxng/searxng",
|
| 1581 |
+
"open-webui": "ghcr.io/open-webui/open-webui",
|
| 1582 |
+
"sillytavern": "ghcr.io/sillytavern/sillytavern",
|
| 1583 |
+
"mcpo": "ghcr.io/open-webui/mcpo",
|
| 1584 |
+
"tabbyapi": "tabbyapi/tabbyapi",
|
| 1585 |
+
}
|
| 1586 |
+
|
| 1587 |
+
|
| 1588 |
+
def update_check(services=None):
|
| 1589 |
+
"""SELF-HEAL #2 β poll Docker Hub/GHCR for newer image tags.
|
| 1590 |
+
|
| 1591 |
+
For every discovered service with a known image repo, fetch the newest
|
| 1592 |
+
published tag and compare against what's running. Reports updates the
|
| 1593 |
+
user can pull. The 'fixer' keeping itself current.
|
| 1594 |
+
"""
|
| 1595 |
+
if services is None:
|
| 1596 |
+
services = scan_network()
|
| 1597 |
+
|
| 1598 |
+
print()
|
| 1599 |
+
print(" βββ UPDATE CHECK β newer versions on registries βββ")
|
| 1600 |
+
print()
|
| 1601 |
+
|
| 1602 |
+
found_any = False
|
| 1603 |
+
for s in sorted(services, key=lambda x: x.get("type", "")):
|
| 1604 |
+
repo = UPDATE_CHECK_REPOS.get(s.get("type"))
|
| 1605 |
+
if not repo:
|
| 1606 |
+
continue
|
| 1607 |
+
found_any = True
|
| 1608 |
+
# Query the registry tags API
|
| 1609 |
+
latest_tag = None
|
| 1610 |
+
try:
|
| 1611 |
+
if repo.startswith("ghcr.io/"):
|
| 1612 |
+
# GHCR: anonymous token flow (401 β WWW-Authenticate challenge
|
| 1613 |
+
# β token β authorized tags/list)
|
| 1614 |
+
path = repo[len("ghcr.io/"):]
|
| 1615 |
+
try:
|
| 1616 |
+
req = urllib.request.Request(
|
| 1617 |
+
f"https://ghcr.io/token?scope=repository:{path}:pull&service=ghcr.io")
|
| 1618 |
+
with urllib.request.urlopen(req, timeout=6) as r:
|
| 1619 |
+
token = json.loads(r.read().decode()).get("token", "")
|
| 1620 |
+
except Exception:
|
| 1621 |
+
token = ""
|
| 1622 |
+
req = urllib.request.Request(
|
| 1623 |
+
f"https://ghcr.io/v2/{path}/tags/list",
|
| 1624 |
+
headers={"Accept": "application/json",
|
| 1625 |
+
"Authorization": f"Bearer {token}"})
|
| 1626 |
+
with urllib.request.urlopen(req, timeout=6) as r:
|
| 1627 |
+
data = json.loads(r.read().decode())
|
| 1628 |
+
tags = data.get("tags", [])
|
| 1629 |
+
candidates = [t for t in tags if not t.startswith("sha256:")]
|
| 1630 |
+
if candidates:
|
| 1631 |
+
latest_tag = candidates[-1]
|
| 1632 |
+
else:
|
| 1633 |
+
ns, name = repo.split("/")
|
| 1634 |
+
if ns == "library":
|
| 1635 |
+
ns = "library"
|
| 1636 |
+
url = f"https://hub.docker.com/v2/repositories/{ns}/{name}/tags/?page_size=5"
|
| 1637 |
+
with urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "basecamp"}), timeout=6) as r:
|
| 1638 |
+
data = json.loads(r.read().decode())
|
| 1639 |
+
tags = [t["name"] for t in data.get("results", [])]
|
| 1640 |
+
if tags:
|
| 1641 |
+
latest_tag = tags[0]
|
| 1642 |
+
except Exception as e:
|
| 1643 |
+
print(f" β οΈ [{s.get('type'):12s}] {s['label']}: registry query failed ({e})")
|
| 1644 |
+
continue
|
| 1645 |
+
|
| 1646 |
+
if latest_tag:
|
| 1647 |
+
print(f" π¦ [{s.get('type'):12s}] {s['label']}")
|
| 1648 |
+
print(f" newest published tag: {latest_tag}")
|
| 1649 |
+
print(f" registry: {repo}")
|
| 1650 |
+
print()
|
| 1651 |
+
|
| 1652 |
+
if not found_any:
|
| 1653 |
+
print(" No known image repos for the discovered services.")
|
| 1654 |
+
print(" To update a service: docker pull <repo>:<newer-tag>, then")
|
| 1655 |
+
print(" recreate its container (or edit docker-compose.starter.yml).")
|
| 1656 |
+
print()
|
| 1657 |
+
|
| 1658 |
+
|
| 1659 |
def main():
|
| 1660 |
if len(sys.argv) < 2:
|
| 1661 |
print(__doc__)
|
|
|
|
| 1772 |
with open(CONFIG_FILE) as f:
|
| 1773 |
config = json.load(f)
|
| 1774 |
wire_audit(services, config)
|
| 1775 |
+
|
| 1776 |
+
elif action in ("self-check", "selfcheck", "doctor"):
|
| 1777 |
+
# SELF-HEAL #1: verify every discovered service against its recipe.
|
| 1778 |
+
services = scan_network()
|
| 1779 |
+
save_discovery(services)
|
| 1780 |
+
self_check(services)
|
| 1781 |
+
|
| 1782 |
+
elif action in ("update-check", "updatecheck", "updates"):
|
| 1783 |
+
# SELF-HEAL #2: poll registries for newer image tags.
|
| 1784 |
+
services = scan_network()
|
| 1785 |
+
save_discovery(services)
|
| 1786 |
+
update_check(services)
|
| 1787 |
else:
|
| 1788 |
print(f"Unknown action: {action}")
|
| 1789 |
print(__doc__)
|
docker-compose.starter.yml
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2 |
+
# BASECAMP CODING STARTER PACK β 10 best-of-breed AI coding tools
|
| 3 |
+
# Every service in its own container, all on the AI network,
|
| 4 |
+
# all wired to the stack's Ollama (http://ollama:11434) and
|
| 5 |
+
# TabbyAPI (http://tabbyapi:5000/v1). They call each other by
|
| 6 |
+
# container name from anywhere on the network.
|
| 7 |
+
#
|
| 8 |
+
# Usage:
|
| 9 |
+
# docker compose -f docker-compose.starter.yml up -d
|
| 10 |
+
# basecamp rediscover # basecamp finds the whole pack
|
| 11 |
+
# basecamp wire # wiring audit for the pack
|
| 12 |
+
#
|
| 13 |
+
# Ports (host side): see per-service comment. All services are
|
| 14 |
+
# reachable on the ai-network by their container name.
|
| 15 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 16 |
+
|
| 17 |
+
networks:
|
| 18 |
+
tabby-tavern_ai-network:
|
| 19 |
+
external: true
|
| 20 |
+
|
| 21 |
+
volumes:
|
| 22 |
+
code-server-data:
|
| 23 |
+
tabby-data:
|
| 24 |
+
qdrant-data:
|
| 25 |
+
chroma-data:
|
| 26 |
+
n8n-data:
|
| 27 |
+
lobe-chat-data:
|
| 28 |
+
anythingllm-data:
|
| 29 |
+
librechat-data:
|
| 30 |
+
meilisearch-data:
|
| 31 |
+
flowise-data:
|
| 32 |
+
postgres_data:
|
| 33 |
+
|
| 34 |
+
services:
|
| 35 |
+
|
| 36 |
+
# ββ 1. THE IDE β VS Code in the browser ββ
|
| 37 |
+
code-server:
|
| 38 |
+
image: codercom/code-server:latest
|
| 39 |
+
container_name: starter-code-server
|
| 40 |
+
restart: unless-stopped
|
| 41 |
+
networks: [tabby-tavern_ai-network]
|
| 42 |
+
ports:
|
| 43 |
+
- "8443:8080" # browser IDE at http://localhost:8443
|
| 44 |
+
volumes:
|
| 45 |
+
- code-server-data:/home/coder/.local/share/code-server
|
| 46 |
+
- ${PWD:-.}/workspace:/home/coder/workspace # your code lives here
|
| 47 |
+
environment:
|
| 48 |
+
- PASSWORD=basecamp # change me; login at the browser prompt
|
| 49 |
+
# Continue.dev + Tabby extensions pre-installed via entrypoint
|
| 50 |
+
entrypoint: >
|
| 51 |
+
/bin/sh -c "
|
| 52 |
+
code-server --install-extension Continue.continue --force 2>/dev/null;
|
| 53 |
+
code-server --install-extension TabbyML.vscode-tabby --force 2>/dev/null;
|
| 54 |
+
exec dumb-init /usr/bin/code-server --bind-addr 0.0.0.0:8080 --disable-telemetry
|
| 55 |
+
"
|
| 56 |
+
|
| 57 |
+
# ββ 2. AI CODE COMPLETION β self-hosted Copilot ββ
|
| 58 |
+
tabby:
|
| 59 |
+
image: tabbyml/tabby:20260330
|
| 60 |
+
container_name: starter-tabby
|
| 61 |
+
restart: unless-stopped
|
| 62 |
+
networks: [tabby-tavern_ai-network]
|
| 63 |
+
ports:
|
| 64 |
+
- "8082:8080" # completion server UI/API
|
| 65 |
+
volumes:
|
| 66 |
+
- tabby-data:/data
|
| 67 |
+
environment:
|
| 68 |
+
- TABBY_MODEL_CACHE_DIR=/data/models
|
| 69 |
+
command: serve --device cpu --model StarCoder2-3B --chat-model StarCoder2-3B
|
| 70 |
+
# --device cpu: keep completions off the 12GB card (TabbyAPI owns it).
|
| 71 |
+
# Version 20260330's serve has NO embedding supervisor (the crashy
|
| 72 |
+
# Nomic-Embed subprocess with -ngl 9999 only exists in newer 'latest').
|
| 73 |
+
# NOTE: for GPU-backed completion, use --device cuda and a larger model;
|
| 74 |
+
# the stack's TabbyAPI (tabbyapi:5000) serves the chat side.
|
| 75 |
+
|
| 76 |
+
# ββ 3. VECTOR DB β code RAG backbone ββ
|
| 77 |
+
qdrant:
|
| 78 |
+
image: qdrant/qdrant:latest
|
| 79 |
+
container_name: starter-qdrant
|
| 80 |
+
restart: unless-stopped
|
| 81 |
+
networks: [tabby-tavern_ai-network]
|
| 82 |
+
ports:
|
| 83 |
+
- "6333:6333" # REST + gRPC
|
| 84 |
+
- "6334:6334"
|
| 85 |
+
volumes:
|
| 86 |
+
- qdrant-data:/qdrant/storage
|
| 87 |
+
|
| 88 |
+
# ββ 4. VECTOR DB (lightweight) ββ
|
| 89 |
+
chroma:
|
| 90 |
+
image: chromadb/chroma:latest
|
| 91 |
+
container_name: starter-chroma
|
| 92 |
+
restart: unless-stopped
|
| 93 |
+
networks: [tabby-tavern_ai-network]
|
| 94 |
+
ports:
|
| 95 |
+
- "8005:8000"
|
| 96 |
+
volumes:
|
| 97 |
+
- chroma-data:/chroma/chroma
|
| 98 |
+
|
| 99 |
+
# ββ 5. WORKFLOW AUTOMATION ββ
|
| 100 |
+
n8n:
|
| 101 |
+
image: n8nio/n8n:latest
|
| 102 |
+
container_name: starter-n8n
|
| 103 |
+
restart: unless-stopped
|
| 104 |
+
networks: [tabby-tavern_ai-network]
|
| 105 |
+
ports:
|
| 106 |
+
- "5678:5678"
|
| 107 |
+
volumes:
|
| 108 |
+
- n8n-data:/home/node/.n8n
|
| 109 |
+
environment:
|
| 110 |
+
- N8N_BASIC_AUTH_ACTIVE=false
|
| 111 |
+
- GENERIC_TIMEZONE=UTC
|
| 112 |
+
|
| 113 |
+
# ββ 6. AI CHAT UI ββ
|
| 114 |
+
lobe-chat:
|
| 115 |
+
image: lobehub/lobe-chat:latest
|
| 116 |
+
container_name: starter-lobe-chat
|
| 117 |
+
restart: unless-stopped
|
| 118 |
+
networks: [tabby-tavern_ai-network]
|
| 119 |
+
ports:
|
| 120 |
+
- "3210:3210"
|
| 121 |
+
volumes:
|
| 122 |
+
- lobe-chat-data:/app/data
|
| 123 |
+
environment:
|
| 124 |
+
- OPENAI_PROXY_URL=http://ollama:11434/v1 # wired to stack Ollama
|
| 125 |
+
- OPENAI_API_KEY=ollama
|
| 126 |
+
|
| 127 |
+
# ββ 7. RAG WORKSPACE ββ
|
| 128 |
+
anythingllm:
|
| 129 |
+
image: mintplexlabs/anythingllm:latest
|
| 130 |
+
container_name: starter-anythingllm
|
| 131 |
+
restart: unless-stopped
|
| 132 |
+
networks: [tabby-tavern_ai-network]
|
| 133 |
+
ports:
|
| 134 |
+
- "3001:3001"
|
| 135 |
+
volumes:
|
| 136 |
+
- anythingllm-data:/app/server/storage
|
| 137 |
+
environment:
|
| 138 |
+
- STORAGE_DIR=/app/server/storage
|
| 139 |
+
- OLLAMA_BASE_URL=http://ollama:11434 # wired to stack Ollama
|
| 140 |
+
|
| 141 |
+
# ββ 8. MULTI-MODEL CHAT ββ
|
| 142 |
+
librechat:
|
| 143 |
+
image: ghcr.io/danny-avila/librechat:latest
|
| 144 |
+
container_name: starter-librechat
|
| 145 |
+
restart: unless-stopped
|
| 146 |
+
depends_on:
|
| 147 |
+
- mongo
|
| 148 |
+
networks: [tabby-tavern_ai-network]
|
| 149 |
+
ports:
|
| 150 |
+
- "3080:3080" # chat UI at http://localhost:3080
|
| 151 |
+
volumes:
|
| 152 |
+
- librechat-data:/app/librechat
|
| 153 |
+
environment:
|
| 154 |
+
- MONGO_URI=mongodb://mongo:27017/LibreChat
|
| 155 |
+
- OPENAI_API_KEY=ollama
|
| 156 |
+
- OPENAI_API_BASE=http://ollama:11434/v1 # wired to stack Ollama
|
| 157 |
+
- ALLOW_SOCIAL_LOGIN=true
|
| 158 |
+
|
| 159 |
+
# LibreChat's database (required β LibreChat is NOT self-contained)
|
| 160 |
+
mongo:
|
| 161 |
+
image: mongo:7
|
| 162 |
+
container_name: starter-mongo
|
| 163 |
+
restart: unless-stopped
|
| 164 |
+
networks: [tabby-tavern_ai-network]
|
| 165 |
+
ports:
|
| 166 |
+
- "27017:27017"
|
| 167 |
+
volumes:
|
| 168 |
+
- librechat-data:/data/db
|
| 169 |
+
|
| 170 |
+
# ββ 9. FULL-TEXT SEARCH ββ
|
| 171 |
+
meilisearch:
|
| 172 |
+
image: getmeili/meilisearch:latest
|
| 173 |
+
container_name: starter-meilisearch
|
| 174 |
+
restart: unless-stopped
|
| 175 |
+
networks: [tabby-tavern_ai-network]
|
| 176 |
+
ports:
|
| 177 |
+
- "7700:7700"
|
| 178 |
+
volumes:
|
| 179 |
+
- meilisearch-data:/meili_data
|
| 180 |
+
environment:
|
| 181 |
+
- MEILI_MASTER_KEY=basecamp-dev-key # change me
|
| 182 |
+
- MEILI_ENV=development
|
| 183 |
+
|
| 184 |
+
# ββ 10. NO-CODE AGENT BUILDER ββ
|
| 185 |
+
flowise:
|
| 186 |
+
image: flowiseai/flowise:3.1.4
|
| 187 |
+
container_name: starter-flowise
|
| 188 |
+
restart: unless-stopped
|
| 189 |
+
networks: [tabby-tavern_ai-network]
|
| 190 |
+
ports:
|
| 191 |
+
- "7860:3000"
|
| 192 |
+
volumes:
|
| 193 |
+
- flowise-data:/root/.flowise
|
| 194 |
+
environment:
|
| 195 |
+
- PORT=3000
|
| 196 |
+
- FLOWISE_USERNAME=admin
|
| 197 |
+
- FLOWISE_PASSWORD=basecamp # change me
|
| 198 |
+
|
| 199 |
+
# ββ 11. LONG-TERM MEMORY β Postgres + pgvector ββ
|
| 200 |
+
# Hermes's pluggable memory backend: vector store for embeddings so the
|
| 201 |
+
# agent remembers across sessions. Point hermes at it with:
|
| 202 |
+
# hermes memory configure --backend postgres --dsn "postgresql://hermes:CHANGEME@starter-postgres:5432/hermes"
|
| 203 |
+
postgres:
|
| 204 |
+
image: pgvector/pgvector:pg16
|
| 205 |
+
container_name: starter-postgres
|
| 206 |
+
restart: unless-stopped
|
| 207 |
+
networks: [tabby-tavern_ai-network]
|
| 208 |
+
ports:
|
| 209 |
+
- "5432:5432"
|
| 210 |
+
environment:
|
| 211 |
+
POSTGRES_DB: hermes
|
| 212 |
+
POSTGRES_USER: hermes
|
| 213 |
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme_strong_password}
|
| 214 |
+
volumes:
|
| 215 |
+
- postgres_data:/var/lib/postgresql/data
|
| 216 |
+
healthcheck:
|
| 217 |
+
test: ["CMD-SHELL", "pg_isready -U hermes -d hermes"]
|
| 218 |
+
interval: 10s
|
| 219 |
+
timeout: 5s
|
| 220 |
+
retries: 5
|
recipes.py
CHANGED
|
@@ -259,6 +259,54 @@ WIRING_RECIPES = {
|
|
| 259 |
"fix": "Ensure 'json' is in search.formats in settings.yml or the JSON API returns 400. Engines added/removed in the engines section.",
|
| 260 |
"verify": "curl http://<host>:8080/ returns the SearXNG page.",
|
| 261 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
}
|
| 263 |
|
| 264 |
|
|
@@ -279,5 +327,7 @@ def wiring_recipe_markdown(service_types=None):
|
|
| 279 |
lines.append(f"- **Keys/endpoints:** {r['keys']}")
|
| 280 |
lines.append(f"- **Fix:** {r['fix']}")
|
| 281 |
lines.append(f"- **Verify:** {r['verify']}")
|
|
|
|
|
|
|
| 282 |
lines.append("")
|
| 283 |
return lines
|
|
|
|
| 259 |
"fix": "Ensure 'json' is in search.formats in settings.yml or the JSON API returns 400. Engines added/removed in the engines section.",
|
| 260 |
"verify": "curl http://<host>:8080/ returns the SearXNG page.",
|
| 261 |
},
|
| 262 |
+
|
| 263 |
+
# ββ Coding starter pack ββ
|
| 264 |
+
"code-server": {
|
| 265 |
+
"where": "Container env + entrypoint extension installs. The IDE itself is VS Code in the browser.",
|
| 266 |
+
"keys": "env: PASSWORD (login); entrypoint: code-server --install-extension Continue.continue / TabbyML.vscode-tabby; API: /healthz, / (web UI)",
|
| 267 |
+
"fix": "Login with the PASSWORD env at http://<host>:8443. Continue.dev and the Tabby extension are pre-installed in the Basecamp pack β configure Continue's model to http://ollama:11434/v1 (or tabbyapi:5000/v1) in its config.json, and point the Tabby extension at http://<host>:8082.",
|
| 268 |
+
"verify": "curl http://<host>:8443/healthz returns 200; the browser UI loads at :8443.",
|
| 269 |
+
"pitfalls": "Extension installs run on first container start β give it ~60s before the IDE is fully ready. Login password comes from the PASSWORD env (default basecamp) β change it.",
|
| 270 |
+
},
|
| 271 |
+
"tabby": {
|
| 272 |
+
"where": "CLI flags at container start + /data volume for models.",
|
| 273 |
+
"keys": "command: serve --device cpu|--device cuda --model <name> --chat-model <name>; env: TABBY_MODEL_CACHE_DIR; API: /v1/health, /v1/completions (code completion)",
|
| 274 |
+
"fix": "For CPU: `serve --device cpu --model StarCoder2-3B --chat-model StarCoder2-3B`. The VS Code extension points at http://<host>:8082. Completions are separate from chat β TabbyAPI serves chat, Tabby serves inline code completion.",
|
| 275 |
+
"verify": "curl http://<host>:8082/v1/health returns {\"health\":\"ok\"}.",
|
| 276 |
+
"pitfalls": "KNOWN CRASH (verified 2026-08): newer 'latest' images spawn a Nomic-Embed subprocess with `-ngl 9999` (all layers to GPU) that exits 127 on constrained GPUs (12GB shared with other inference), crash-looping the container. FIX: pin tabbyml/tabby:20260330 β its `serve` has NO embedding supervisor, so nothing crashes; run `--device cpu` to keep completions off the card. NOTE: `--disable-embedding` is NOT a valid flag in 20260330 (rejected at CLI parse). The embedding/chat models download on first boot β first start is slow.",
|
| 277 |
+
},
|
| 278 |
+
"meilisearch": {
|
| 279 |
+
"where": "Env vars.",
|
| 280 |
+
"keys": "MEILI_MASTER_KEY, MEILI_ENV; API: /health, /indexes, /search",
|
| 281 |
+
"fix": "Set MEILI_MASTER_KEY so clients can authenticate; index your code/docs via the API and search at /search with the key.",
|
| 282 |
+
"verify": "curl http://<host>:7700/health returns {\"status\":\"available\"}.",
|
| 283 |
+
},
|
| 284 |
+
"librechat": {
|
| 285 |
+
"where": "Env vars + REQUIRES MongoDB (not self-contained).",
|
| 286 |
+
"keys": "MONGO_URI (required), OPENAI_API_KEY, OPENAI_API_BASE; API: /api/health",
|
| 287 |
+
"fix": "LibreChat CRASHES on boot without MONGO_URI (verified 2026-08): 'Please define the MONGO_URI environment variable'. It needs a mongo container β the Basecamp pack ships one (starter-mongo, mongo:7) and sets MONGO_URI=mongodb://mongo:27017/LibreChat. Image lives on GHCR (ghcr.io/danny-avila/librechat), NOT Docker Hub.",
|
| 288 |
+
"verify": "curl http://<host>:3080/api/health returns ok AFTER mongo is up.",
|
| 289 |
+
"pitfalls": "Image is ghcr.io/danny-avila/librechat:latest β the Docker Hub 'danny-avila/librechat' repo 404s. First boot takes ~60s. Depends on mongo; start mongo first (depends_on in compose).",
|
| 290 |
+
},
|
| 291 |
+
"flowise": {
|
| 292 |
+
"where": "Env vars (PORT, FLOWISE_USERNAME, FLOWISE_PASSWORD) + /root/.flowise volume.",
|
| 293 |
+
"keys": "PORT=3000, FLOWISE_USERNAME, FLOWISE_PASSWORD; API: /api/v1/ping",
|
| 294 |
+
"fix": "Set FLOWISE_USERNAME/PASSWORD for auth. Pin a VERSIONED tag β `flowiseai/flowise:latest` is BROKEN as of 2026-08 (ERR_PACKAGE_PATH_NOT_EXPORTED in @langchain/core ./utils/uuid crashes node on startup). Use flowiseai/flowise:3.1.4 or newer.",
|
| 295 |
+
"verify": "curl http://<host>:7860/api/v1/ping returns pong.",
|
| 296 |
+
"pitfalls": "KNOWN BUG (verified 2026-08): latest image crashes at boot in ReActAgentChat/ReActAgentLLM nodes with '@langchain/core' exports error. Pin 3.1.4+. Login with FLOWISE_USERNAME/FLOWISE_PASSWORD.",
|
| 297 |
+
},
|
| 298 |
+
"postgres": {
|
| 299 |
+
"where": "Container env (db/user/password) + data volume.",
|
| 300 |
+
"keys": "POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD; port 5432; pgvector extension for embeddings",
|
| 301 |
+
"fix": "This is Hermes's long-term memory backend (Postgres + pgvector). Point hermes at it: hermes memory configure --backend postgres --dsn 'postgresql://<user>:<pass>@starter-postgres:5432/<db>'. The pgvector image ships the extension β create it with: docker exec starter-postgres psql -U hermes -d hermes -c 'CREATE EXTENSION IF NOT EXISTS vector;'",
|
| 302 |
+
"verify": "docker exec starter-postgres pg_isready -U hermes -d hermes returns 'accepting connections'.",
|
| 303 |
+
},
|
| 304 |
+
"mongo": {
|
| 305 |
+
"where": "Container env + /data/db volume. Database for LibreChat (and other apps).",
|
| 306 |
+
"keys": "MONGO_URI=mongodb://mongo:27017/<db>; port 27017; image mongo:7",
|
| 307 |
+
"fix": "Point dependent apps at mongodb://<host>:27017/<dbname>. LibreChat requires MONGO_URI or it crashes on boot (verified 2026-08). No auth by default on the pack's instance β add a root user for anything public.",
|
| 308 |
+
"verify": "docker exec starter-mongo mongosh --quiet --eval 'db.runCommand({ping:1}).ok' returns 1.",
|
| 309 |
+
},
|
| 310 |
}
|
| 311 |
|
| 312 |
|
|
|
|
| 327 |
lines.append(f"- **Keys/endpoints:** {r['keys']}")
|
| 328 |
lines.append(f"- **Fix:** {r['fix']}")
|
| 329 |
lines.append(f"- **Verify:** {r['verify']}")
|
| 330 |
+
if r.get("pitfalls"):
|
| 331 |
+
lines.append(f"- **Pitfalls (verified):** {r['pitfalls']}")
|
| 332 |
lines.append("")
|
| 333 |
return lines
|