Architecture

VPS Lean Projects

Shared infrastructure, per-project component diagrams, and deployment model for all 10 projects on 2 vCPU / 4 GB RAM.

1. Shared Infrastructure

All 10 projects share a common base β€” this is the foundation each project runs on top of:

LayerComponentDetails
OSDebian 13 (3.12.x kernel)OpenClaw lean config applied
RuntimePython 3.11+Per-project virtualenv, shared system packages
DatabaseSQLiteOne .db file per project in /var/data/
LLM RuntimeOllama (shared service, port 11434)One Ollama instance for all projects. llama3.2 2B loaded on demand.
Bot/AlertingTelegram Bot APIOne bot token shared across projects. Messages via common sender.
Schedulersystemd timers + cronLightweight. No APScheduler daemon (saves RAM).
Web serverFlask (per-project)One Flask app per project, separate ports. Behind reverse proxy.
Reverse proxynginxRoutes babu.thotas.com/[project]/ to correct port. Single entry point.

2. Shared Ollama Service

πŸ¦™ Ollama Service (shared)
Port 11434 Β· llama3.2 2B + nomic-embed-text
One system-wide Ollama process. Projects that need AI call localhost:11434. Models loaded on first request, kept warm for ~10 min, then unloaded to free RAM. If WhisperBox needs to run whisper.cpp stand-alone, it pauses Ollama to free ~1.5GB.
↑ LLM API calls (localhost)
πŸ“Š WatchDog
RAM: ~200MB Β· Port 5xxx Β· 5-min cron checks
πŸ“¬ PaperTrail
RAM: ~700MB Β· Port 6xxx Β· Always-on
πŸ”— LinkVault
RAM: ~600MB Β· Port 7xxx Β· Always-on
πŸ“° PulseRSS
RAM: ~400MB Β· Port 8xxx Β· Cron-triggered (daily)
πŸ“ HabitForge
RAM: ~350MB Β· Port 9xxx Β· Always-on + daily cron
↑ reverse proxy
🌐 nginx reverse proxy
babu.thotas.com/watchdog β†’ :5xxx Β· babu.thotas.com/papertrail β†’ :6xxx Β· etc.

3. nginx Reverse Proxy Config

server {
    listen 80;
    server_name babu.thotas.com;

    # WatchDog
    location /watchdog/ {
        proxy_pass http://127.0.0.1:5001/;
        proxy_set_header Host $host;
    }

    # PaperTrail
    location /papertrail/ {
        proxy_pass http://127.0.0.1:5002/;
        proxy_set_header Host $host;
    }

    # LinkVault
    location /linkvault/ {
        proxy_pass http://127.0.0.1:5003/;
        proxy_set_header Host $host;
    }

    # PulseRSS
    location /pulserss/ {
        proxy_pass http://127.0.0.1:5004/;
        proxy_set_header Host $host;
    }

    # HabitForge
    location /habitforge/ {
        proxy_pass http://127.0.0.1:5005/;
        proxy_set_header Host $host;
    }

    # TinySync
    location /tinysync/ {
        proxy_pass http://127.0.0.1:5006/;
        proxy_set_header Host $host;
    }

    # PastePit
    location /pastepit/ {
        proxy_pass http://127.0.0.1:5007/;
        proxy_set_header Host $host;
    }

    # Nomad Board
    location /nomad/ {
        proxy_pass http://127.0.0.1:5008/;
        proxy_set_header Host $host;
    }

    # WhisperBox
    location /whisperbox/ {
        proxy_pass http://127.0.0.1:5009/;
        proxy_set_header Host $host;
    }

    # AutoGrok
    location /autogrok/ {
        proxy_pass http://127.0.0.1:5010/;
        proxy_set_header Host $host;
    }
}

Each project gets its own sub-path. SSL via Let's Encrypt (certbot). No port forwarding needed beyond 80/443.

4. Per-Project Architecture

WatchDog (Uptime Monitor)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Flask Web App   │────▢│  SQLite DB       β”‚     β”‚  Telegram Bot    β”‚
β”‚  Port 5001      β”‚     β”‚  watchdog.db    β”‚     β”‚  (alert sender)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                                                β”‚
         β”‚ check every 5min via systemd timer            β”‚ push
         β–Ό                                                β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                     β”‚
β”‚  health_checks   β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚  (Python script) β”‚
β”‚  - HTTP ping     β”‚
β”‚  - SSL check     β”‚
β”‚  - Latency       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Storage: /var/data/watchdog.db β€” sites, check history, alert log

PaperTrail (Knowledge Base + AI Q&A)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Flask Web App   │────▢│  SQLite DB       │────▢│  Ollama          β”‚
β”‚  Port 5002      β”‚     β”‚  papertrail.db   β”‚     β”‚  localhost:11434 β”‚
β”‚  /notes, /ask   β”‚     β”‚  notes + vectors β”‚     β”‚  nomic-embed     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
    β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  Markdown files in /var/notes/ β”‚
    β”‚  (one .md per note)            β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Storage: notes as .md files in /var/notes/, embeddings in SQLite. Ollama for semantic search + QA.

LinkVault (Bookmark Saver)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Flask Web App   │────▢│  SQLite DB       β”‚     β”‚  Ollama          β”‚
β”‚  Port 5003      β”‚     β”‚  linkvault.db    β”‚     β”‚  localhost:11434 β”‚
β”‚  /add, /list    β”‚     β”‚  links + metadataβ”‚     β”‚  llama3.2 (sum)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
    β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  Puppeteer/requests             β”‚
    β”‚  - Fetch page title/desc        β”‚
    β”‚  - Store OG image URL           β”‚
    β”‚  - Optional: LLM summary        β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Storage: /var/data/linkvault.db. Thumbnails/favicon cached locally.

PulseRSS (Daily Digest)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Flask Web App   │────▢│  SQLite DB       β”‚     β”‚  Ollama          β”‚
β”‚  Port 5004      β”‚     β”‚ arterss.db      β”‚     β”‚  localhost:11434 β”‚
β”‚  /feeds, /digestβ”‚     β”‚  feeds + articlesβ”‚    β”‚  llama3.2 (sum)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                                              β”‚
    β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”
    β”‚  Cron job (daily at 7am PT)                         β”‚
    β”‚  - fetchparser: parse all feeds                      β”‚
    β”‚  - New articles β†’ store in DB                        β”‚
    β”‚  - Build digest prompt β†’ Ollama β†’ summarized email   β”‚
    β”‚  - SMTP: send to Thota's inbox                       β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Storage: /var/data/pulserss.db. Feed URLs stored, articles deduplicated by URL.

WhisperBox (Transcription Hub)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Flask Web App   │────▢│  SQLite DB       β”‚     β”‚  Whisper.cpp    β”‚
β”‚  Port 5009      β”‚     β”‚  whisperbox.db  β”‚     β”‚  (shell call)   β”‚
β”‚  /upload        β”‚     β”‚  transcripts    β”‚     β”‚  ~700MB RAM     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
    β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  Audio files stored in /var/audio/           β”‚
    β”‚  (mp3, wav, m4a supported)                  β”‚
    β”‚  - Normalize audio                         β”‚
    β”‚  - Run whisper.cpp base.en                  β”‚
    β”‚  - Store transcript text in DB              β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Storage: Audio files in /var/audio/ (10GB volume recommended), transcripts in SQLite.

Note: WhisperBox runs as standalone β€” Ollama is stopped during transcription to free ~1.5GB RAM.

HabitForge (Habit Tracker)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Flask Web App   │────▢│  SQLite DB       β”‚     β”‚  Ollama          β”‚
β”‚  Port 5005      β”‚     β”‚  habitforge.db  β”‚     β”‚  localhost:11434 β”‚
β”‚  /habits, /checkβ”‚     β”‚  habits + checksβ”‚     β”‚  llama3.2 (mot.) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                                              β”‚
    β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”
    β”‚  Cron job (weekly, Sunday 9am PT)                    β”‚
    β”‚  - Build streak report                               β”‚
    β”‚  - Ollama: generate motivational text               β”‚
    β”‚  - SMTP: send weekly check-in email                  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Storage: /var/data/habitforge.db. Streak data and check-in history.

5. Storage Layout

/var/
β”œβ”€β”€ data/                    # All project databases
β”‚   β”œβ”€β”€ watchdog.db
β”‚   β”œβ”€β”€ papertrail.db
β”‚   β”œβ”€β”€ linkvault.db
β”‚   β”œβ”€β”€ pulserss.db
β”‚   β”œβ”€β”€ habitforge.db
β”‚   β”œβ”€β”€ nomadboard.db
β”‚   └── whisperbox.db
β”œβ”€β”€ notes/                   # PaperTrail markdown files
β”œβ”€β”€ audio/                   # WhisperBox uploads (large, separate volume)
β”œβ”€β”€ uploads/                 # PastePit pastes, TinySync files
└── backups/                 # Nightly .tar.gz of /var/data + /var/notes

/etc/
β”œβ”€β”€ nginx/
β”‚   └── sites-available/vps-projects  # nginx config
└── systemd/system/
    β”œβ”€β”€ watchdog-check.timer
    β”œβ”€β”€ watchdog-check.service
    β”œβ”€β”€ pulserss-digest.timer
    └── pulserss-digest.service

/opt/
└── ollama/                  # Ollama binary + models
    └── models/              # llama3.2 2B + nomic-embed-text

6. Deployment Order (Priority)

OrderProjectWhy FirstEst. Setup Time
1WatchDogDead simple, validates VPS setup, Telegram bot reuse~1 hour
2PastePitSimplest web app, warm up nginx + Flask stack~30 min
3HabitForgeEasy Flask + SQLite, good for daily-use testing~1 hour
4PaperTrailFirst AI project β€” validates Ollama setup~2 hours
5LinkVaultGood second AI project, tests Puppeteer + LLM~2 hours
6PulseRSSRSS parsing + AI digest + email β€” complex but fits RAM~2 hours
7Nomad BoardLeaflet.js map β€” different tech, light RAM~1 hour
8TinySyncWebDAV + file handling β€” more storage planning~2 hours
9WhisperBoxWhisper.cpp standalone β€” validates audio pipeline~2 hours
10AutoGrokMost ambitious β€” only if all else is stable~4+ hours

7. RAM Budget at Full Capacity

ComponentRAMNotes
Debian OS + kernel~500 MBBaseline after boot
nginx~30 MBReverse proxy
Ollama (idle, no model loaded)~100 MBModel loading adds ~1.5GB
WatchDog Flask~200 MBAlways-on
HabitForge Flask~350 MBAlways-on
PaperTrail Flask~700 MBAlways-on
LinkVault Flask~600 MBAlways-on
PulseRSS Flask~400 MBCron-triggered only
TinySync Flask~250 MBAlways-on
PastePit Flask~150 MBAlways-on
Nomad Board Flask~300 MBAlways-on
WhisperBox Flask~700 MBStandalone when active
Total always-on~3.6 GBGetting tight β€” consider which 3-4 are truly always-on
With Ollama + llama3.2 loaded~5.1 GBExceeds 4GB β€” need to be strategic

⚠️ Strategic insight: 10 always-on Flask apps + Ollama exceeds 4GB. Choice: run only 3-4 projects always-on, the rest as cron-triggered or on-demand. Recommended always-on: WatchDog, PaperTrail, HabitForge, LinkVault. Others trigger via systemd timers.