Skip to content

Configuring Local Development

Eric Fitzgerald edited this page Jun 10, 2026 · 2 revisions

Configuring Local Development

This guide explains how to configure TMI for local development: where the config file lives, how secrets are managed, and how to authenticate against a running local server.

TMI configuration is split into two layers. Bootstrap config — database connection, JWT secret, server port, logging — comes from a config-*.yml file (plus TMI_* environment overrides) and is read once at startup. Operational config — OAuth providers, Timmy AI settings, timeouts, feature flags — is stored in the database, seeded from registry defaults on first run, and changed at runtime via the admin API without restarting the server. See Configuration-Model for the full picture.


Quick Start

# 1. Copy the template
cp config-example.yml config-development.yml

# 2. Open config-development.yml and replace the vault:// placeholders
#    with real dev values (see "The dev config file" below)

# 3. Create a local cluster and deploy the dev environment
make dev-cluster-up    # local kind cluster + registry (laptop path)
make start-dev

# 4. Verify the server is up
curl http://localhost:8080/

make start-dev deploys the TMI server and the component platform (Redis, NATS, the controller, and the workers) into a local Kubernetes cluster, delivering config-development.yml as a ConfigMap, then port-forwards the server to port 8080. The database is external — the server connects to whatever database.url your config specifies. See Local Development Cluster for prerequisites, kind/k3s setup, database options (including DB=oracle), and the optional Tilt fast loop.

Note: There is no /health endpoint. Use the root endpoint (/) to confirm the server is running.


The Dev Config File

Template vs. working files

File Tracked in git? Purpose
config-example.yml Yes Template; shows all bootstrap keys with vault:// placeholders for secrets
config-development.yml No (gitignored) Your local working config; contains real secrets
config-test.yml No (gitignored) Used by the test suite
config-production.yml No (gitignored) Production deployments

Never commit config-development.yml. It contains real credentials and is gitignored for that reason.

What's in the dev config file

The config file carries only the bootstrap keys — settings the server must have before it can connect to the database:

  • database.url — PostgreSQL connection string (user, password, host, port, database name). This is where you look up your local dev DB credentials.
  • server.port, server.tls_* — HTTP/TLS settings
  • auth.jwt.secret — Key used to sign and verify JWTs
  • logging.* — Log level and format
  • secrets.provider — Where TMI should resolve vault:// references at runtime

Everything operational (OAuth providers, Timmy, timeouts) is not present here — it lives in the database.

Populating secrets

The vault:// placeholders in config-example.yml represent secret values. For local development you can:

  • Inline real values — simplest for a dev machine only you control.
  • Use env:// references — e.g. env://MY_JWT_SECRET reads the value from that environment variable at startup.
  • Use file:// references — e.g. file:///run/secrets/jwt_secret reads from a file.

For a local dev machine, inlining values is the most straightforward option.

Regenerating the template

If the bootstrap key registry changes (for example, after a config refactor), regenerate config-example.yml to pick up new keys:

make generate-config-example

Commit the updated config-example.yml. Do not commit the working file.


Environment Variable Overrides

Any bootstrap setting can be overridden with a TMI_ environment variable. Environment variables take highest priority — they win over both the config file and the database.

The naming convention is TMI_ followed by the YAML key path in uppercase with dots replaced by underscores:

# Override the server port without editing the config file
export TMI_SERVER_PORT=9090

# Override the database URL
export TMI_DATABASE_URL="postgres://user:pass@localhost:5432/tmidev"

# Override the JWT secret
export TMI_JWT_SECRET="my-local-dev-secret"

This is useful for quick iteration (testing a different port, pointing at a different database) without touching config-development.yml.


Authenticating in Development

TMI requires OAuth authentication for all protected endpoints. For local development, use the built-in "tmi" OAuth provider together with the OAuth callback stub.

Starting the callback stub

make start-oauth-stub

Stub logs are written to /tmp/oauth-stub.log. Stop it with:

make oauth-stub-stop

Getting a token quickly

# Start the stub (if not already running)
make start-oauth-stub

# Start an automated OAuth flow for user "alice"
curl -X POST http://localhost:8079/flows/start \
  -H 'Content-Type: application/json' \
  -d '{"userid": "alice"}'

# Wait a moment for the flow to complete, then retrieve the token
curl "http://localhost:8079/creds?userid=alice" | jq '.access_token'

Use the returned access_token as a Bearer token in your API requests:

curl -H "Authorization: Bearer <token>" http://localhost:8080/threat_models

The login_hint parameter

The "tmi" provider supports a login_hint query parameter that creates a predictable test user instead of a random one:

http://localhost:8080/oauth2/authorize?idp=tmi&login_hint=alice
  • Creates alice@tmi.local with display name Alice (TMI User)
  • Format: 3–20 characters, alphanumeric plus hyphens, case-insensitive
  • Without login_hint, you get a random user like testuser-12345678@tmi.local
  • login_hint is only available in the "tmi" provider — it does not exist in production builds

User naming conventions

User Role When to use
charlie Administrator Admin API calls, settings management
alice Regular user General development and testing
bob Regular user Multi-user scenarios

Operational Settings in Development

On the first run against an empty database, TMI seeds all operational settings from the built-in registry defaults. You do not need to configure them manually to get a working server.

To inspect or change an operational setting after first run, use the /admin/settings admin API as the charlie admin user. For example, to check Timmy's configuration:

# Get a token for charlie
TOKEN=$(curl -s "http://localhost:8079/creds?userid=charlie" | jq -r '.access_token')

# List all settings (the endpoint takes no query parameters); filter for Timmy with jq
curl -s -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8080/admin/settings" | jq '[.[] | select(.key | startswith("timmy."))]'

Timmy is the clearest example of a fully DB-backed subsystem: there is no timmy: block in the config file. To enable or configure Timmy in a local environment, update its timmy.* operational settings via the admin API. No server restart is needed — Timmy's access to content sources is toggled at runtime.

You can also inspect settings directly in the database — the /db skill runs queries against the local PostgreSQL instance (e.g. SELECT setting_key, value FROM system_settings WHERE setting_key LIKE 'timmy.%'). Editing should still go through the admin API so caching and encryption are handled correctly.

See Managing-Operational-Settings for a full guide to the admin settings API.


Where Logs Go

In development and test mode, TMI writes structured logs to:

logs/tmi.log

This file is in the project root directory. It is the first place to look when debugging unexpected server behavior. The log level is controlled by logging.level in config-development.yml or the TMI_LOG_LEVEL environment variable.


See Also

Home

Releases


Getting Started

Deployment

Operation

Troubleshooting

Development

Integrations

Tools

API Reference

Reference

Clone this wiki locally