SimpleStrichliste is a small self-hosted balance and store application with the support for optional features that add more functionality.
The easiest way to run the application is Docker Compose:
docker compose up -dThe default docker-compose.yml starts the app on:
http://localhost:3000
Persistent data is stored in mounted folders:
./storage:/app/storage
./installed_features:/app/installed_features
storage contains the SQLite database, uploaded images, generated assets, and backups. Keep this folder when updating or recreating the container.
For local development with a locally built image, use:
docker compose -f docker-compose.dev.wsl.yml up -d --buildOn first startup the app creates/migrates the database automatically. If no users exist, the setup page lets you create the first user. The first user is created as an administrator by default. When OAuth is enabled, the first successful OAuth login also creates the first local user as an administrator.
Most runtime configuration is done through environment variables in Docker Compose or .env.
Common settings:
APPLICATION=Strichliste
DOMAIN=http://localhost:3000
APPLICATION_TIMEZONE=Europe/Berlin
FALLBACKLANG=de
PORT=3000
BINDIP=0.0.0.0
SALTROUNDS=12
WebTokenDurationH=9600DOMAIN must match the public URL users open in their browser. This matters for generated links, static assets, manifests, and OAuth callbacks.
APPLICATION_TIMEZONE must be an IANA timezone such as Europe/Berlin; it is used for application-rendered timestamps like system logs and notification emails.
Email notifications use an SQLite-backed queue. Configure an SMTP server with:
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=notifications@example.com
SMTP_PASSWORD=change-me
SMTP_FROM=notifications@example.com
SMTP_POOL_MAX_CONNECTIONS=1
SMTP_POOL_MAX_MESSAGES=1000
EMAIL_MAX_RETRIES=5SMTP_SECURE=true enables implicit TLS. Port 465 also enables it automatically. SMTP pooling reuses authenticated connections; one connection can send up to SMTP_POOL_MAX_MESSAGES messages before reconnecting. Failed sends are retried by the notification worker until EMAIL_MAX_RETRIES is reached.
Notification registrations live in config/notifications/*.js. Email HTML templates live in config/templates/email/*.ejs. Core email translations live in config/templates/email/locales/<language>.json; feature translations can use config/templates/email/locales/<featureName>/<language>.json. The recipient's saved language is used with FALLBACKLANG as fallback.
Notification definitions are channel-independent. Current channel is email; future channels can be added without changing notification callers. Definitions use category system or newsletter. Newsletter registrations automatically appear in user settings.
Place feature-owned templates inside the feature package:
installed_features/foodorders/templates/email/FoodOrderReady.ejs
During feature installation, templates/ is copied into the application's
config/templates/ directory. The example above becomes:
config/templates/email/FoodOrderReady.ejs
// installed_features/foodorders/config/notifications/foodorders.js
module.exports = [
{
type: 'FoodOrderReady',
constant: 'FOOD_ORDER_READY',
category: 'newsletter',
preferenceKey: 'foodorder-ready',
translationKeyBase: 'FoodOrders.Notifications.Ready',
requiresMessage: true,
channels: {
email: {
templatePath: 'config/templates/email/FoodOrderReady.ejs',
buildContext: (task) => ({
order: JSON.parse(task.custom_message),
}),
buildText: (context) => `${context.name}, deine Bestellung ist fertig.`,
},
},
},
];
const { sendNotification } = require('@lib/notifications');
await sendNotification(userId, 0, 'FoodOrderReady', JSON.stringify(order));The notification type is stored in email_tasks.type; the user preference channel is stored in user_notifications.type. templatePath may be absolute or relative to the application root. Copied registration, locale, and template files are tracked in the feature manifest and removed by the feature uninstaller. Notification config files are loaded before the email worker starts.
When EBG_OAUTH_URL is set, local password login and local registration are replaced by OAuth. In this mode:
/loginand/registershow OAuth buttons instead of local forms.- Local password login and local registration API calls are rejected.
- The registration-code settings are hidden because registration is managed by the OAuth provider.
- Users cannot change their local password in user settings, because OAuth users do not log in with that password.
- The first OAuth-created local user becomes admin automatically.
Required OAuth environment variables:
EBG_OAUTH_URL=https://ebg.pw
EBG_OAUTH_CLIENT_ID=your-client-id
EBG_OAUTH_CLIENT_SECRET=your-client-secret
EBG_OAUTH_SCOPE=your-scopeDefault OAuth endpoints derived from EBG_OAUTH_URL:
Authorize user: /auth/oauth
Exchange code: /oauth/authorize
Load user data: /oauth/user
Callback URL: <DOMAIN>/auth/oauth/callback
Backups can be managed from the admin settings page.
Creating a backup stores a zip file in:
storage/backups
Each backup contains:
application.db, the SQLite database- item images from storage
- static stored images such as favicons or warning images
Admins can create, list, download, and delete backups from the admin settings page.
Backup import is available only while the application has zero users. This is intentional so a restore cannot overwrite an active installation from inside a logged-in session.
To restore:
- Start the app with an empty database or no users.
- Open the setup page.
- Upload a backup
.zip. - The app restores
application.dband stored images. - The app exits after restore so it can restart cleanly with the restored database.
With Docker Compose, the container uses restart: unless-stopped, so it should start again automatically after restore. If it does not, run:
docker compose up -dOptional features are installed and enabled by placing independent folders in installed_features.
Each feature folder needs a feature.json or config.json manifest:
{
"name": "foodorders",
"version": "0.0.1",
"minCoreVersion": "0.3.0",
"navbar": {
"insert": true,
"href": "/foodorders",
"translationKey": "Navbar.FoodOrders",
"order": 60
},
"adminCard": {
"href": "/admin/foodorders",
"translationKeyBase": "Admin.FeatureCards.foodorders"
},
"db": {
"migrations": [],
"seeds": []
}
}On startup the app scans installed_features/<featureName>. Folder presence enables the feature. minCoreVersion defines the oldest compatible SimpleStrichliste version. Incompatible source versions are skipped and logged without stopping startup or replacing an already installed compatible version. If the feature version is newer than config/features/<featureName>.json, or if the installed config does not exist yet, the feature files are copied into the application.
Supported feature folders include application folders such as api, lib, src, views, public, and config. A top-level templates folder is installed into config/templates. Feature page translations live in:
installed_features/<featureName>/locales/<language>/features/<featureName>.json
and are installed to:
config/locales/<language>/features/<featureName>.json
Feature email translations live in:
installed_features/<featureName>/templates/email/locales/<featureName>/<language>.json
and are installed to:
config/templates/email/locales/<featureName>/<language>.json
Feature public files can also be served from:
/features/<featureName>/<file>
which maps to:
installed_features/<featureName>/public/<file>
Feature migrations can be placed in:
installed_features/<featureName>/migrations
Feature seed files can be placed in:
installed_features/<featureName>/seeds
During install they are copied to migrations/features/<featureName> and seeds/features/<featureName>. Feature migration records are stored as feature:<featureName>:<migrationVersion> so their versions do not collide with base app migrations.
Feature migrations should NEVER touch the base application DB structure. They may create, update, or seed feature-owned tables and indexes only.
Run:
node tools/uninstallFeature.js <featureName>This removes files recorded in config/features/<featureName>.json. It does not roll back database migrations. To also remove the source folder in installed_features, add --remove-source.