- Python 75.1%
- Makefile 15%
- Dockerfile 9.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| src/hello | ||
| test | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.dev.yml | ||
| docker-compose.e2e.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| Makefile | ||
| pytest.ini | ||
| README.md | ||
VSKI App Template
Starter template for a VSKI deployment with plugins enabled: the official standalone image (web frontend embedded) plus a Python environment for supervised plugin processes, wired up with Docker Compose and a Makefile.
Layout
.
├── Makefile # build, run, dev, test — see `make help`
├── Dockerfile # standalone image + Python + vski-sdk + plugin deps
├── docker-compose.yml # the stack: one vski service, plugins baked in
├── docker-compose.dev.yml # dev overlay: mounts ./src over /app/plugins
├── docker-compose.e2e.yml # e2e overlay: isolated env + state (used by make test-e2e)
├── .env.example # server config template (copy to .env)
├── src/ # plugin source — BAKED into the image at /app/plugins
│ └── hello/ # minimal starter plugin — copy it to start your own
│ ├── vski-plugin.json # manifest: entry point, config schema, functions
│ ├── requirements.txt # plugin dependencies (installed at image build)
│ └── hello/
│ └── main.py # handlers: @on_event / @function / @cron
├── test/
│ ├── unit/ # pytest over plugin code — no server needed
│ └── e2e/ # pytest over the running stack's REST API
└── data/ # SQLite databases, files, plugin registry (gitignored)
Plugins are part of the image: Dockerfile copies src/ to /app/plugins
and installs vski-sdk (from the vski.sh package registry) plus every plugin's
requirements.txt into one locked Python environment. The ./src bind mount
exists only as a dev-time overlay (make dev) — deployments run the baked
image.
Quick start
cp .env.example .env
# set JWT_SECRET (openssl rand -hex 32) and ALLOWED_ORIGINS
make up
Open http://localhost:3000, create the first admin account, then create a plugin instance of the starter plugin (dashboard: Plugins, or the API):
TOKEN="<admin token>"
curl -X POST http://localhost:3000/api/plugin-instances \
-H "Authorization: $TOKEN" -H "Content-Type: application/json" \
-d '{"name": "hello_1", "plugin": "hello", "dbName": "default", "enabled": true}'
# invoke a function over the warm stdio pipe
curl -X POST http://localhost:3000/api/plugin-instances/hello_1/invoke?db=default \
-H "Authorization: $TOKEN" -H "Content-Type: application/json" \
-d '{"function": "echo", "params": {"message": "world"}}'
Testing
make test # everything below
make test-unit # plugin code against fake ctx, no server (needs make venv)
make test-e2e # builds the image, boots an isolated stack, tests the real thing
- Unit (
test/unit): imports each plugin fromsrc/, checks the manifest/entry layout, and exercises handlers with a fakectx(test/unit/fakes.py). Runs in seconds; iterate here first. - E2E (
test/e2e): the full lifecycle against a real server — health, web UI, catalog discovery, instance start, warm invoke, realtime event delivery, log capture, config-patch restart.make test-e2ewrites a throwaway.env.e2e(test port 3999, random JWT secret), runs the stack with state in./.e2e-dataviadocker-compose.e2e.yml, and always tears it down. To debug the suite against a stack that is already running:VSKI_E2E_URL=http://localhost:3000 .venv/bin/pytest test/e2e -v.
Developing plugins
make dev boots the stack with ./src mounted over the baked-in plugins:
plugin code changes need an instance restart (dashboard/API) or a container
restart — no image rebuild. Dependency changes always do need a rebuild
(the image owns the Python environment).
Adding a plugin
- Copy
src/hello/tosrc/<your-plugin>/, rename the package and updateruntime.entry(<package>.main:app) in the manifest. - List its third-party dependencies in its
requirements.txt. make test-unit— the layout checks import your entry module and verify the declared functions exist; add unit tests for your handlers besidetest/unit/test_hello.py.make test-e2e— the baked image gets your plugin; e2e covers the runtime path.- Create an instance (dashboard/API).
POST /api/plugins/scanpicks up new manifests without a container restart.
Instance API keys hold the plugin role and are subject to collection
access rules (default-deny) — grant access like for any other service.
Production notes
- Pin the base image in the Dockerfile to a versioned tag
(e.g.
vski.sh/x/vski:v0.14.0-standalone) instead oflatest-standalone, and pinvski-sdkand plugin dependencies to exact versions. The vski.sh PyPI registry is public for reads (https://vski.sh/api/packages/x/pypi/simple/), so no registry credentials are needed at build time. - Set
JWT_SECRETandALLOWED_ORIGINSin.env; never ship the defaults. data/is the single stateful volume — back it up (see the wiki's Backup page). Instance configs and secrets live in the database; treat it likeJWT_SECRET.
Reference
- Plugins wiki page — manifest,
lifecycle,
plugin://triggers, security model, troubleshooting. examples/plugins/in the platform repository — more complete plugins (embeddings indexer, graph sync, calendar index).