3DKenji

Self-hosted knowledge keeper for 3D printing projects

View the Project on GitHub bodybybuddha/3DKenji

Configuration

3DKenji configuration through environment variables.

Contents

Environment Variables

Database

DATABASE_URL (Required) Connection string for PostgreSQL database.

DATABASE_URL=postgresql://username:password@localhost:5432/kenji

Format: postgresql://[user[:password]@][host][:port][/dbname]

Examples:

Storage

STORAGE_ROOT (Optional, default: /data/storage) Root directory for project filesystem data.

STORAGE_ROOT=/data/storage

Projects are stored under:

STORAGE_ROOT/Projects/<owner_nickname>/<slug>

Example:

/data/storage/Projects/alice-prints/test-project-2

Note: set STORAGE_ROOT to the root path only (for example ./data/storage), not to a nested .../Projects path.

Ensure directory exists and is writable:

mkdir -p /data/storage
chmod 755 /data/storage

Legacy project paths (Projects/<owner_id>/<slug> or Projects/<category>/<slug>) are supported during migration, but new and updated projects are persisted to owner-nickname paths.

Email (SMTP)

Invitation emails are delivered through SMTP configuration stored in admin settings (/admin/settings) and can be overridden for test/dev use with environment flags.

APP_BASE_URL (Optional, default: http://localhost:8000) Base URL used to generate invitation claim links in emails.

APP_BASE_URL=http://localhost:8000

SMTP_MOCK_DELIVERY (Optional, default: false) When true, email sending is simulated and no external SMTP server is contacted.

SMTP_MOCK_DELIVERY=true

SMTP host, port, credentials, sender identity, and TLS mode are managed in the admin SMTP settings UI and persisted in application settings.

PLUGINS_ROOT (Optional, default: /data/plugins) Root directory for installation-specific plugins.

PLUGINS_ROOT=/data/plugins

Plugins are discovered only at application startup in v1. Each plugin lives in its own folder beneath PLUGINS_ROOT and must include a plugin.yaml manifest.

PLUGINS_ROOT/
├── _system/
│   └── plugin-registry.yaml
└── core-themes/
  ├── plugin.yaml
  ├── settings.yaml
  └── assets/
    └── themes/

Notes:

Ensure the plugin root exists and is writable:

mkdir -p /data/plugins/_system
chmod -R 755 /data/plugins

Project Structure and Frontmatter

Project creation seeds each project directory with:

Both markdown files use YAML-like frontmatter for structured metadata. This hybrid approach means:

For complete details on ProjectInfo.md and PrintHistory.md frontmatter schemas, see docs/project-storage-architecture.md.

Logging

LOG_DIR (Optional, default: /data/logs) Directory for application log files.

LOG_DIR=/data/logs

LOG_MAX_SIZE_MB (Optional, default: 10) Maximum size of each log file in megabytes before rotation.

LOG_MAX_SIZE_MB=10

LOG_BACKUP_COUNT (Optional, default: 5) Number of backup log files to keep during rotation.

LOG_BACKUP_COUNT=5

Security

SECRET_KEY (Required) Secret key for JWT token signing. Generate with:

python -c "import secrets; print(secrets.token_urlsafe(32))"

Set to a strong random value:

SECRET_KEY=your-secret-key-here-min-32-chars

Warning: Keep SECRET_KEY stable after initial setup. Changing this value will prevent decryption of stored OAuth client secrets and may invalidate existing sessions and encrypted data.

ALGORITHM (Optional, default: HS256) JWT signing algorithm (HS256 recommended for single server).

ALGORITHM=HS256

TOKEN_EXPIRE_HOURS (Optional, default: 24) JWT token expiration time in hours.

TOKEN_EXPIRE_HOURS=24

Server

HOST (Optional, default: 0.0.0.0) Server listen address.

HOST=0.0.0.0

PORT (Optional, default: 8000) Server listen port.

PORT=8000

LOG_LEVEL (Optional, default: INFO) Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL

LOG_LEVEL=INFO

Development Configuration

.env File (Development)

Create .env file in project root:

DATABASE_URL=postgresql://kenji:kenji@localhost:5432/kenji
STORAGE_ROOT=./data/storage
PLUGINS_ROOT=./working/plugins
LOG_DIR=./data/logs
LOG_LEVEL=DEBUG
SECRET_KEY=dev-secret-key-change-in-production
TOKEN_EXPIRE_HOURS=24
HOST=0.0.0.0
PORT=8000

Load with:

source .env
make dev

Docker Configuration

docker-compose.yml

Environment variables in docker-compose.yml:

services:
  api:
    environment:
      - DATABASE_URL=postgresql://kenji:kenji@postgres:5432/kenji
      - STORAGE_ROOT=/data/storage
      - PLUGINS_ROOT=/data/plugins
      - LOG_DIR=/data/logs
      - SECRET_KEY=${SECRET_KEY:-dev-secret-key}
      - LOG_LEVEL=INFO
      - PORT=8000
    volumes:
      - storage_data:/data/storage
      - logs_data:/data/logs

.env.docker

For Docker Compose, create .env file:

SECRET_KEY=your-production-secret-key
DATABASE_URL=postgresql://kenji:kenji@postgres:5432/kenji
STORAGE_ROOT=/data/storage
PLUGINS_ROOT=/data/plugins
LOG_DIR=/data/logs

Start with:

docker-compose --env-file .env up

Production Configuration

Environment Variables (Required)

# REQUIRED CHANGES FROM DEFAULTS
SECRET_KEY=your-very-secure-random-key-min-32-chars
DATABASE_URL=postgresql://user:secure-password@db.example.com/kenji_prod
STORAGE_ROOT=/var/lib/kenji/storage
PLUGINS_ROOT=/var/lib/kenji/plugins

# RECOMMENDED
LOG_DIR=/var/log/kenji
LOG_LEVEL=WARNING
HOST=127.0.0.1  # Use reverse proxy (nginx) for external access
PORT=8000
TOKEN_EXPIRE_HOURS=24

Generate Secure SECRET_KEY

python -c "import secrets; print(secrets.token_urlsafe(32))"
# Output: TlJ_V1z3...  (copy this value)
export SECRET_KEY=TlJ_V1z3...

Note: OAuth client secrets saved in the admin settings are encrypted using a key derived from SECRET_KEY. If you rotate SECRET_KEY, re-enter any saved OAuth client secrets.

Database Setup

Create PostgreSQL database and user:

CREATE USER kenji WITH PASSWORD 'secure-password-here';
CREATE DATABASE kenji_prod OWNER kenji;
GRANT ALL PRIVILEGES ON DATABASE kenji_prod TO kenji;

Then set:

DATABASE_URL=postgresql://kenji:secure-password-here@db.example.com/kenji_prod

Storage Directory

Create and set permissions:

sudo mkdir -p /var/lib/kenji/storage
sudo chown kenji:kenji /var/lib/kenji/storage
sudo chmod 750 /var/lib/kenji/storage

sudo mkdir -p /var/lib/kenji/plugins/_system
sudo chown -R kenji:kenji /var/lib/kenji/plugins
sudo chmod -R 750 /var/lib/kenji/plugins

Log Directory

Create and set permissions:

sudo mkdir -p /var/log/kenji
sudo chown kenji:kenji /var/log/kenji
sudo chmod 750 /var/log/kenji

Run with Systemd

Create /etc/systemd/system/kenji.service:

[Unit]
Description=3DKenji API Server
After=network.target postgresql.service
Wants=postgresql.service

[Service]
Type=simple
User=kenji
WorkingDirectory=/opt/kenji
EnvironmentFile=/opt/kenji/.env.prod
ExecStart=/opt/kenji/venv/bin/python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable kenji
sudo systemctl start kenji
sudo systemctl status kenji

Nginx Reverse Proxy

Configure nginx to proxy requests to 3DKenji:

upstream kenji {
    server 127.0.0.1:8000;
}

server {
    listen 80;
    server_name api.example.com;
    
    # Redirect to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;
    
    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    
    client_max_body_size 10M;  # Match max file size
    
    location / {
        proxy_pass http://kenji;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Kubernetes Configuration

ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: kenji-config
data:
  LOG_LEVEL: "INFO"
  STORAGE_ROOT: "/data/storage"
  LOG_DIR: "/data/logs"

Secret

apiVersion: v1
kind: Secret
metadata:
  name: kenji-secret
type: Opaque
stringData:
  DATABASE_URL: postgresql://user:password@postgres:5432/kenji
  SECRET_KEY: your-secret-key-here

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kenji
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: kenji
        image: kenji:0.2.0
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: kenji-secret
              key: DATABASE_URL
        - name: SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: kenji-secret
              key: SECRET_KEY
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: kenji-config
              key: LOG_LEVEL
        ports:
        - containerPort: 8000
        livenessProbe:
          httpGet:
            path: /api/v1/health/live
            port: 8000
          initialDelaySeconds: 10
          periodSeconds: 30
        readinessProbe:
          httpGet:
            path: /api/v1/health/ready
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 10

Troubleshooting

“Cannot connect to database”

Check DATABASE_URL:

psql "$DATABASE_URL"  # Should connect successfully

“Permission denied” on storage

Fix permissions:

sudo chown kenji:kenji $STORAGE_ROOT
sudo chmod 755 $STORAGE_ROOT

Logs not being written

Check LOG_DIR exists and is writable:

touch $LOG_DIR/test.log && rm $LOG_DIR/test.log

Port 8000 already in use

Change PORT or kill existing process:

export PORT=8001
# or
lsof -i :8000 | grep -v PID | awk '{print $2}' | xargs kill -9

Security Checklist