Skip to content

Latest commit

 

History

History
65 lines (44 loc) · 5.24 KB

File metadata and controls

65 lines (44 loc) · 5.24 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Status

Work in progress. Per README, "no functioning is guaranteed." The tree compiles and the Django admin is runnable, but the runtime orchestration has never been end-to-end tested in this environment — expect to patch hardcoded paths/ports before the dankindab_* management commands do anything useful.

Stack

  • Python 3 (tested on 3.10+). All code has been ported off Python 2; do not reintroduce print <expr>, xmlrpclib, etc.
  • Django 5.1 with Django's native migrations. There is no south anymore. Migration history in server/migrations/ was wiped on upgrade — run ./manage.py makemigrations server once to regenerate (the SQLite DB at /tmp/test is disposable).
  • gevent + pyzmq + supervisor (with supervisor_twiddler). Load-bearing; don't swap them.
  • Dependencies pinned in requirements.txt (Django>=5.1,<5.2, gevent>=24.2, pyzmq>=26.0, supervisor>=4.2, supervisor-twiddler>=1.1).

Common commands

pip install -r requirements.txt

./manage.py makemigrations server      # first run only — migrations dir is empty
./manage.py migrate                    # SQLite at /tmp/test (override via DANKINDAB_DB env)
./manage.py createsuperuser
./manage.py runserver                  # only real endpoint is /admin/
./manage.py test server                # django test runner

# Orchestration commands (server/management/commands/):
./manage.py dankindab_start_listeners  # spawn listener procs via supervisor_twiddler
./manage.py dankindab_stop_listeners
./manage.py dankindab_start_handlers
./manage.py dankindab_stop_handlers
./manage.py dankindab_dispatcher       # in-process dispatcher (legacy, superseded by scripts/)

supervisord -c ds.conf                 # start supervisor (needs twiddler rpcinterface)
supervisorctl -c ds.conf

settings.py reads three env vars: DANKINDAB_SECRET_KEY, DANKINDAB_DEBUG ("1"/"0"), DANKINDAB_ALLOWED_HOSTS (comma-separated), DANKINDAB_DB. Defaults are dev-only — override before any non-local run.

ds.conf now binds supervisor to 127.0.0.1:9091 with placeholder creds (user/123). Change both before exposing.

Architecture: ZMQ-fronted multi-tenant WSGI dispatcher

The system is a reverse-proxy-ish layer for running many WSGI apps behind shared TCP listeners. The flow spans Django models, supervisor RPC, and ZMQ pub/sub — reading one file in isolation won't make it click:

  1. Data model (server/models.py) — the control plane lives in the DB:

    • Listener = a bound ip:port (optionally TLS via keyfile/certfile).
    • VirtualHost + VirtualHostName = hostnames routed to a particular Deployment behind a Listener.
    • App = a WSGI callable (wsgi is a dotted path) pulled from repo.
    • Server = a remote host running supervisor; stores supervisor HTTP RPC creds and whether it is the is_main controller. Server.ip is a GenericIPAddressField (v4 or v6).
    • Deployment = an App installed at a path on a Server.
    • All ForeignKeys are on_delete=CASCADE.
  2. Listener processes (scripts/run_dankindab_dispatcher.py, invoked via supervisor): each listener is a gevent WSGIServer whose request handler is connected_dispatcher(zmq_sub, zmq_pub). For every HTTP request it serializes a trimmed environ to JSON, PUBlishes it on a ZMQ topic keyed by HTTP_HOST, then blocks on SUB.recv() for the response body. The listener does not import app code — it only brokers bytes. Payloads go over the wire as UTF-8 bytes; the code uses send/recv with explicit .encode(), so do not switch to send_string/recv_string without checking both sides.

  3. Management commands are the deploy/scale control plane. dankindab_start_listeners / dankindab_start_handlers iterate Listener rows, connect to each main Server's supervisor over XML-RPC (xmlrpc.client.ServerProxy(server.get_url()) → http://user:pass@ip:port), and call twiddler.addProgramToGroup(...) to spawn the dispatcher scripts inside the listeners / apps supervisor groups. The stop commands mirror this with supervisor.stopProcessGroup + twiddler.removeProcessFromGroup. The twiddler extension (from supervisor_twiddler) is what makes process sets dynamic — without it these commands fail.

  4. Hardcoded wiring to watch for: dispatcher script path is /root/www/DaNKInDaB/scripts/run_dankindab_dispatcher.py (baked into dankindab_start_*), ZMQ endpoints are tcp://localhost:42712 (sub) and tcp://*:42713 (pub), and dankindab_stop_handlers targets http://localhost:9091 directly rather than going through Server.get_url(). These are hot-wired to the author's box — expect to patch them before anything runs.

  5. The ui Django app is a stub (empty models.py/views.py) and is not in INSTALLED_APPS. urls.py only wires /admin/. All real work happens in server/.

Known pre-existing issues (not from the modernization)

  • server/management/commands/dankindab_dispatcher.py references vh.base_dir, which is not a field on VirtualHost. Calling this command will AttributeError once it hits a VirtualHost row.
  • server/views.py:handle_request is not wired to any URL — it's a stub kept around for reference.