{"id":50932058,"url":"https://github.com/kisaesdevlab/vibe-linux-setup","last_synced_at":"2026-06-17T05:04:45.048Z","repository":{"id":352287396,"uuid":"1213935687","full_name":"KisaesDevLab/vibe-linux-setup","owner":"KisaesDevLab","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-18T19:52:22.000Z","size":33,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-18T21:25:30.083Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/KisaesDevLab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-17T23:28:57.000Z","updated_at":"2026-04-18T19:52:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/KisaesDevLab/vibe-linux-setup","commit_stats":null,"previous_names":["kisaesdevlab/vibe-linux-setup"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/KisaesDevLab/vibe-linux-setup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KisaesDevLab%2Fvibe-linux-setup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KisaesDevLab%2Fvibe-linux-setup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KisaesDevLab%2Fvibe-linux-setup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KisaesDevLab%2Fvibe-linux-setup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KisaesDevLab","download_url":"https://codeload.github.com/KisaesDevLab/vibe-linux-setup/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KisaesDevLab%2Fvibe-linux-setup/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34434498,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-17T02:00:05.408Z","response_time":127,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-17T05:04:30.055Z","updated_at":"2026-06-17T05:04:45.030Z","avatar_url":"https://github.com/KisaesDevLab.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Vibe Linux Setup — Kisaes LLC\n\nAutomated provisioning for Ubuntu Server 24.04 LTS machines running the Kisaes application stack. Idempotent single-shot installer — re-runnable if anything fails mid-way, and safe against re-runs once everything's up.\n\n## What Gets Installed\n\n| Service | Purpose | Access |\n|---|---|---|\n| **Vibe Trial Balance** | Trial balance, tax workpapers, AI classification | `http://tb.kisaes.local/` |\n| **Vibe MyBooks** | Bookkeeping, transaction coding, client portal | `http://mb.kisaes.local/` |\n| **Vibe Payroll Time** | Time tracking, payroll runs, QR badge punch-ins | `http://pt.kisaes.local/` |\n| **Landing page** | Card-selector for the three apps | `http://kisaes.local/` |\n| **GLM-OCR** | Self-hosted OCR appliance (llama.cpp + GLM-OCR GGUF) | `http://127.0.0.1:8090/` (host-only) |\n| **Webmin** | Web-based server + network management | `https://\u003cip\u003e:10000` |\n| **Portainer CE** | Docker container management UI | `https://\u003cip\u003e:9443` |\n| **Duplicati** | Scheduled backups | `http://\u003cip\u003e:8200` |\n| **Tailscale** | Mesh VPN for secure remote access | All services via `100.x.x.x` |\n| **Nginx** | Reverse proxy (hostname-based) | Port 80 |\n| **Avahi mDNS** | Publishes `.local` hostnames to the LAN | UDP 5353 |\n| **UFW** | Firewall for host-level ports | — |\n\n## Quick Start\n\nOn a fresh Ubuntu Server 24.04 LTS box:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/KisaesDevLab/vibe-Linux-Setup/main/bootstrap.sh | bash\n```\n\n`bootstrap.sh` installs git, clones this repo, and runs `provision.sh`, which executes every phase end-to-end (base packages + Avahi, Docker, shared network, GLM-OCR, Vibe TB, Vibe MB, Vibe PT, Portainer, Duplicati, Tailscale, Webmin, landing page, nginx, mDNS aliases, IP:port fallback listeners, UFW, verification). It also installs Claude Code for ongoing maintenance.\n\nThe script is **idempotent** — containers, volumes, secrets, and firewall rules all no-op if they already exist, so re-running after a failure resumes cleanly.\n\n### Unattended mode\n\nProvide a Tailscale pre-auth key to skip the interactive browser-auth step:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/KisaesDevLab/vibe-Linux-Setup/main/bootstrap.sh \\\n  | TAILSCALE_AUTHKEY=tskey-auth-xxxxx bash\n```\n\nTailscale auth has a 10-minute timeout — if no key is provided and nobody clicks the browser URL, the phase gives up and provisioning finishes. Resume later with `sudo tailscale up --ssh --accept-dns`.\n\n### Environment overrides\n\n- `VIBE_DOMAIN` — base hostname (default `kisaes.local`). The four services are served as:\n  - `http://\u003cVIBE_DOMAIN\u003e/` → landing page\n  - `http://tb.\u003cVIBE_DOMAIN\u003e/` → Vibe Trial Balance\n  - `http://mb.\u003cVIBE_DOMAIN\u003e/` → Vibe MyBooks\n  - `http://pt.\u003cVIBE_DOMAIN\u003e/` → Vibe Payroll Time\n\n  A `.local` domain is published automatically via mDNS (avahi) and resolves on every modern client with zero client-side DNS config (see [DNS resolution](#dns-resolution) below). Any other TLD works but you must configure DNS yourself (router, Pi-hole, `/etc/hosts`, or Tailscale Split DNS).\n\n- `TAILSCALE_AUTHKEY` — pre-auth key for unattended Tailscale registration.\n- `LOG_FILE` — path for the provisioning log (default `~/vibe-provision.log`).\n\n### Flags\n\n- `--skip-tailscale` — skip the Tailscale phase entirely.\n- `--skip-claude` — skip installing Claude Code.\n\n### Running locally after clone\n\n```bash\ncd ~/vibe-Linux-Setup\n./provision.sh\n```\n\n## Requirements\n\n- **Hardware:** x86_64 mini PC (tested on GMKtec NucBox M6)\n- **OS:** Ubuntu Server 24.04 LTS (clean install)\n- **RAM:** 12 GB minimum, **16 GB recommended.** GLM-OCR alone is ~2–4 GB resident; add Vibe TB server (1 GB heap), two Postgres + Redis, and Vibe MB which bundles Puppeteer + Chromium (~500 MB–1 GB at runtime for PDF generation) and concurrent light use easily tops 6–7 GB before headroom.\n- **Storage:** 40 GB+ available\n- **Network:** Internet connection during provisioning (for apt, Docker Hub, GHCR, Tailscale)\n\n## DNS resolution\n\nWith the default `kisaes.local` domain, provisioning publishes three mDNS A-records and every modern client resolves them without further config:\n\n- **macOS / iOS / Android 12+** — native mDNS, works out of the box.\n- **Ubuntu / most Linux desktops** — `nss-mdns` talks to avahi via `/etc/nsswitch.conf`. Works on install.\n- **Windows 10 1803+ / Windows 11** — native mDNS resolver in the DNS Client service. **Set the network profile to Private, not Public** — Windows Defender Firewall blocks inbound mDNS on Public. `nslookup` won't find it (nslookup is unicast-only); use `ping kisaes.local` or `Resolve-DnsName kisaes.local` to test.\n- **Windows 7/8** — install Bonjour Print Services (rare in 2026).\n\n### Fallback when mDNS doesn't work\n\nmDNS doesn't traverse Tailscale and may be blocked on corporate networks or VLANs. The final phase of `provision.sh` prints exact `/etc/hosts` entries for both your LAN IP and (if present) your Tailscale IP. Or use Tailscale split-DNS: Machines → DNS → Split DNS → add `kisaes.local` pointing to the Tailscale IP.\n\n## Architecture\n\n```\n┌──────────────────────────── Port 80 (host nginx) ──────────────────────────┐\n│                                                                            │\n│  Host: kisaes.local      → /var/www/kisaes/index.html (landing)            │\n│  Host: tb.kisaes.local   → 127.0.0.1:3000 → vibe-tb-client (nginx + SPA)  │\n│                                           → vibe-tb-server (Node API)     │\n│                                           → vibe-tb-db (Postgres)         │\n│  Host: mb.kisaes.local   → 127.0.0.1:3001 → vibe-mb-app (API + SPA)       │\n│                                           → vibe-mb-db (Postgres)         │\n│                                           → vibe-mb-redis                  │\n│  Host: pt.kisaes.local   → /api/*    → 127.0.0.1:4002 → vibe-pt-backend   │\n│                          → /         → 127.0.0.1:3002 → vibe-pt-frontend  │\n│                                                         → vibe-pt-db       │\n│  Host: \u003canything else\u003e   → 302 → http://kisaes.local (catch-all)          │\n│                                                                            │\n└────────────────────────────────────────────────────────────────────────────┘\n\nPort 3080 / 3081 / 3082  → IP:port fallback listeners for TB / MB / PT\n                           (used by Chrome/Edge Secure DNS and Firefox DoH\n                           clients that bypass the mDNS resolver)\nPort 10000               → Webmin (server mgmt; UFW-restricted to LAN; PAM)\nPort 9443                → Portainer (Docker UI; admin password pre-seeded)\nPort 8200                → Duplicati (backups; WebUI password pre-seeded)\nPort 8090                → GLM-OCR (bound to 127.0.0.1; container-to-\n                           container via kisaes-net at vibe-glm-ocr:8090)\n\nTailscale                → Mesh VPN overlay reachable from anywhere. mDNS\n                           does NOT traverse Tailscale — use Tailscale IPs\n                           or split-DNS.\n```\n\nThe TB client (`:3000`), MB app (`:3001`), and PT backend/frontend (`:4002` / `:3002`) are bound to `127.0.0.1` so only host nginx can reach them. This forces all LAN traffic through the hostname / IP:port listeners so CORS, cookies, and rate limits behave consistently. PT is the only app where host nginx does the `/api/*` vs `/` split itself — TB's client container and MB's app container handle that internally.\n\n## Container map\n\n| Container | Image | Port binding | Restart |\n|---|---|---|---|\n| `vibe-glm-ocr` | `ghcr.io/kisaesdevlab/vibe-glm-ocr:latest` | `127.0.0.1:8090` | always |\n| `vibe-tb-db` | `postgres:16-alpine` | internal | always |\n| `vibe-tb-server` | `ghcr.io/kisaesdevlab/vibe-tb-server:${IMAGE_TAG:-latest}` | internal (`:3001`) | always |\n| `vibe-tb-client` | `ghcr.io/kisaesdevlab/vibe-tb-client:${IMAGE_TAG:-latest}` | `127.0.0.1:3000` | always |\n| `vibe-mb-db` | `postgres:16-alpine` | internal | always |\n| `vibe-mb-redis` | `redis:7-alpine` | internal | always |\n| `vibe-mb-app` | `ghcr.io/kisaesdevlab/vibe-mybooks:${VIBE_MYBOOKS_TAG:-latest}` | `127.0.0.1:3001` | always |\n| `vibe-pt-db` | `postgres:16-alpine` | internal | always |\n| `vibe-pt-backend` | `ghcr.io/kisaesdevlab/vibept-backend:latest` | `127.0.0.1:4002` | always |\n| `vibe-pt-frontend` | `ghcr.io/kisaesdevlab/vibept-frontend:latest` | `127.0.0.1:3002` | always |\n| `portainer` | `portainer/portainer-ce:lts` | `0.0.0.0:9443`, `0.0.0.0:8000` | always |\n| `duplicati` | `lscr.io/linuxserver/duplicati:latest` | `0.0.0.0:8200` | always |\n\nAll containers share the `kisaes-net` Docker network so they address each other by container name.\n\n**Optional tunnel containers** (dormant unless `CLOUDFLARE_TUNNEL_TOKEN` is set in the matching `.env`):\n\n| Container | Image | Purpose |\n|---|---|---|\n| `vibe-mb-cloudflared` | `cloudflare/cloudflared:latest` | CF tunnel → `vibe-mb-app:3001` |\n| `vibe-pt-caddy` | `caddy:2-alpine` | Internal `/api/*` vs `/` split so the tunnel has one target |\n| `vibe-pt-cloudflared` | `cloudflare/cloudflared:latest` | CF tunnel → `vibe-pt-caddy:8080` |\n\nTB is LAN-only and has no tunnel sidecar.\n\n## Secrets and credentials\n\n`provision.sh` auto-generates secrets on first run and persists them under `$HOME` with `chmod 600`. Re-runs reuse existing values — secrets never rotate automatically.\n\n| File | Contents |\n|---|---|\n| `~/vibe-tb/.env` | `DB_PASSWORD`, `JWT_SECRET`, `ENCRYPTION_KEY`, `ALLOWED_ORIGIN`, `APP_BASE_URL`, `IMAGE_TAG` |\n| `~/vibe-mb/.env` | `POSTGRES_PASSWORD`, `JWT_SECRET`, `ENCRYPTION_KEY`, `PLAID_ENCRYPTION_KEY`, `BACKUP_ENCRYPTION_KEY`, `CORS_ORIGIN`, `TRUST_PROXY`, `VIBE_MYBOOKS_TAG`, `CLOUDFLARE_TUNNEL_TOKEN` (opt-in), `TUNNEL_ORIGIN` (opt-in) |\n| `~/vibe-pt/.env` | `POSTGRES_PASSWORD`, `JWT_SECRET`, `SECRETS_ENCRYPTION_KEY`, `APPLIANCE_ID`, `CORS_ORIGIN`, `CLOUDFLARE_TUNNEL_TOKEN` (opt-in), `TUNNEL_ORIGIN` (opt-in) |\n| `~/vibe-portainer/admin-password` | Portainer `admin` user password (pre-seeded; no 5-min signup lockout) |\n| `~/vibe-duplicati/webui-password` | Duplicati WebUI password (prevents unauthenticated FS access on `:8200`) |\n| `~/vibe-duplicati/vibe-default-backup.json` | Pre-generated Duplicati backup-job config (import via UI) |\n\n**Back these files up separately from your database dumps.** The DBs are encrypted at rest using `ENCRYPTION_KEY` / `BACKUP_ENCRYPTION_KEY` — losing the `.env` means losing the DB, full stop.\n\n## Post-provisioning checklist\n\nThe final phase prints this with your actual IPs substituted:\n\n1. **Visit the landing page** (`http://kisaes.local/`) to verify mDNS works from a client device.\n2. **Log in to Portainer** at `https://\u003cip\u003e:9443` using `admin` and the password in `~/vibe-portainer/admin-password`. Change it if you want, but the pre-seeded one is already strong.\n3. **Log in to Duplicati** at `http://\u003cip\u003e:8200` using the password in `~/vibe-duplicati/webui-password`. Then Add backup → Import from a file → `~/vibe-duplicati/vibe-default-backup.json`. **Change the destination to somewhere off-box** (S3, Backblaze B2, SFTP, external disk) — the default points at `/backups/vibe-appliance` locally, which doesn't survive an SSD failure.\n4. **If you want Claude-backed AI features in Vibe TB**, add `ANTHROPIC_API_KEY=sk-ant-…` to `~/vibe-tb/.env` and restart:\n   ```bash\n   cd ~/vibe-tb \u0026\u0026 sudo docker compose --env-file .env up -d\n   ```\n5. **Optional: expose MB or PT over Cloudflare Tunnel.** Create a tunnel at [one.dash.cloudflare.com](https://one.dash.cloudflare.com) → Networks → Tunnels, route it to `http://vibe-mb-app:3001` (MB) or `http://vibe-pt-caddy:8080` (PT), then edit the matching `.env`:\n   ```\n   CLOUDFLARE_TUNNEL_TOKEN=\u003cpaste from CF\u003e\n   TUNNEL_ORIGIN=https://\u003cyour-public-hostname\u003e\n   ```\n   Re-run `./provision.sh`. The provisioner detects the token and brings up the `cloudflared` sidecar (plus a Caddy sidecar for PT), and `CORS_ORIGIN` is rewritten to the public hostname. TB does not have a tunnel sidecar — LAN only.\n6. **CORS origins are single-valued.** If you need to reach the apps via a different URL than the canonical port URL / hostname (e.g. Tailscale IP, LAN IP, different HTTPS proxy), edit `ALLOWED_ORIGIN` in `~/vibe-tb/.env` or `CORS_ORIGIN` in `~/vibe-mb/.env` / `~/vibe-pt/.env` and restart the stack. When `TUNNEL_ORIGIN` is set on MB/PT, the provisioner auto-manages `CORS_ORIGIN` — don't hand-edit it there.\n\n## Known limitations\n\n- **GLM-OCR is not yet called by Vibe TB / Vibe MB.** The apps currently expect an Ollama-style OCR endpoint; GLM-OCR serves an OpenAI-compatible API at `/v1/chat/completions`. Wiring requires an app-side change tracked upstream.\n- **CORS is single-origin per app.** Multiple origins (LAN IP + Tailscale + hostname + Cloudflare Tunnel all at once) requires either a PR to the apps' CORS parsing or an HTTPS reverse proxy in front. Tracked at [KisaesDevLab/Vibe-Trial-Balance#6](https://github.com/KisaesDevLab/Vibe-Trial-Balance/issues/6), [KisaesDevLab/Vibe-MyBooks#32](https://github.com/KisaesDevLab/Vibe-MyBooks/issues/32), and for PT in the main repo. Until then, enabling a Cloudflare Tunnel flips `CORS_ORIGIN` to the public hostname and LAN port URLs CORS-break.\n- **Docker-published ports bypass UFW.** Docker's iptables rules run before UFW's `DOCKER-USER` chain on a default Ubuntu install. UFW firewalls host-level services (22/80/10000/5353) only. Ports 9443, 8200, and on-the-LAN access to 80 are reachable regardless of UFW. **Fine for a LAN appliance behind NAT; do not expose this host to the public internet without fronting it with a separate firewall or installing `ufw-docker`.**\n- **Future: consolidate under a single hostname via subpaths** (`/tb/`, `/mb/`) — blocked on upstream app changes; tracked at [KisaesDevLab/Vibe-Trial-Balance#5](https://github.com/KisaesDevLab/Vibe-Trial-Balance/issues/5) and [KisaesDevLab/Vibe-MyBooks#31](https://github.com/KisaesDevLab/Vibe-MyBooks/issues/31).\n\n## File structure\n\n```\nvibe-Linux-Setup/\n├── CLAUDE.md         # Phase-by-phase reference (for human debugging and Claude Code)\n├── README.md         # This file\n├── bootstrap.sh      # One-line entry point: installs git, clones repo, runs provision.sh\n├── provision.sh      # Idempotent 14-phase provisioner (does the actual work)\n└── assets/\n    └── landing.html  # Landing page source (deployed to /var/www/kisaes/)\n```\n\n## License\n\nProprietary — Kisaes LLC. Internal use only.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkisaesdevlab%2Fvibe-linux-setup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkisaesdevlab%2Fvibe-linux-setup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkisaesdevlab%2Fvibe-linux-setup/lists"}