This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
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.
- 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
southanymore. Migration history inserver/migrations/was wiped on upgrade — run./manage.py makemigrations serveronce to regenerate (the SQLite DB at/tmp/testis 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).
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.confsettings.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.
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:
-
Data model (
server/models.py) — the control plane lives in the DB:Listener= a boundip:port(optionally TLS viakeyfile/certfile).VirtualHost+VirtualHostName= hostnames routed to a particularDeploymentbehind aListener.App= a WSGI callable (wsgiis a dotted path) pulled fromrepo.Server= a remote host running supervisor; stores supervisor HTTP RPC creds and whether it is theis_maincontroller.Server.ipis aGenericIPAddressField(v4 or v6).Deployment= anAppinstalled at apathon aServer.- All
ForeignKeys areon_delete=CASCADE.
-
Listener processes (
scripts/run_dankindab_dispatcher.py, invoked via supervisor): each listener is a gevent WSGIServer whose request handler isconnected_dispatcher(zmq_sub, zmq_pub). For every HTTP request it serializes a trimmedenvironto JSON,PUBlishes it on a ZMQ topic keyed byHTTP_HOST, then blocks onSUB.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 usessend/recvwith explicit.encode(), so do not switch tosend_string/recv_stringwithout checking both sides. -
Management commands are the deploy/scale control plane.
dankindab_start_listeners/dankindab_start_handlersiterateListenerrows, connect to each mainServer's supervisor over XML-RPC (xmlrpc.client.ServerProxy(server.get_url())→http://user:pass@ip:port), and calltwiddler.addProgramToGroup(...)to spawn the dispatcher scripts inside thelisteners/appssupervisor groups. The stop commands mirror this withsupervisor.stopProcessGroup+twiddler.removeProcessFromGroup. The twiddler extension (fromsupervisor_twiddler) is what makes process sets dynamic — without it these commands fail. -
Hardcoded wiring to watch for: dispatcher script path is
/root/www/DaNKInDaB/scripts/run_dankindab_dispatcher.py(baked intodankindab_start_*), ZMQ endpoints aretcp://localhost:42712(sub) andtcp://*:42713(pub), anddankindab_stop_handlerstargetshttp://localhost:9091directly rather than going throughServer.get_url(). These are hot-wired to the author's box — expect to patch them before anything runs. -
The
uiDjango app is a stub (emptymodels.py/views.py) and is not inINSTALLED_APPS.urls.pyonly wires/admin/. All real work happens inserver/.
server/management/commands/dankindab_dispatcher.pyreferencesvh.base_dir, which is not a field onVirtualHost. Calling this command willAttributeErroronce it hits aVirtualHostrow.server/views.py:handle_requestis not wired to any URL — it's a stub kept around for reference.