bunker-admin 08d8066157 Add ticketed events, Jitsi meeting integration, social features, and calendar system
- Ticketed events: full CRUD, ticket tiers (free/paid/donation), Stripe checkout,
  QR-based check-in scanner, public event pages, ticket confirmation emails
- Event formats: IN_PERSON/ONLINE/HYBRID with auto Jitsi meeting room lifecycle,
  ticket-gated meeting access, moderator JWT tokens, feature-flag guarded
- Social engagement: challenges with scoring/leaderboards, referral tracking,
  volunteer spotlight, impact stories, campaign celebrations, wall of fame
- Social calendar: personal calendar layers, shared calendar items with
  recurrence, scheduling polls, mobile day view
- MCP server: events tool pack with full admin CRUD + meeting token generation
- Unified calendar: eventFormat-aware tags, online event indicators
- Updated docs site, pangolin configs, and various admin UI improvements

Bunker Admin
2026-03-06 14:33:33 -07:00

4386 lines
145 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Complete reference for every .env variable in Changemaker Lite.">
<meta name="author" content="Bunker Operations">
<link rel="canonical" href="https://bnkserve.org/docs/getting-started/environment-variables/">
<link rel="prev" href="../installation/">
<link rel="next" href="../first-steps/">
<link rel="icon" href="../../../assets/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.2">
<title>Environment Variables - Changemaker Lite</title>
<link rel="stylesheet" href="../../../assets/stylesheets/main.484c7ddc.min.css">
<link rel="stylesheet" href="../../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="../../../stylesheets/extra.css">
<link rel="stylesheet" href="../../../stylesheets/home.css">
<link rel="stylesheet" href="../../../stylesheets/docs-comments.css">
<link rel="stylesheet" href="../../../assets/css/video-player.css">
<link rel="stylesheet" href="../../../assets/css/image-gallery.css">
<link rel="stylesheet" href="../../../assets/css/payment-widgets.css">
<script>__md_scope=new URL("../../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";
// Anonymous session UUID (sessionStorage — dies with tab close, no cookies)
function getSessionHash() {
var key = "__docs_sh";
var hash = sessionStorage.getItem(key);
if (!hash) {
hash = crypto.randomUUID
? crypto.randomUUID()
: "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0;
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
});
sessionStorage.setItem(key, hash);
}
return hash;
}
function trackPageView(path) {
var payload = JSON.stringify({
path: path,
referrer: document.referrer || undefined,
sessionHash: getSessionHash(),
});
// Prefer sendBeacon for reliability (works during tab close)
if (navigator.sendBeacon) {
navigator.sendBeacon(trackUrl, new Blob([payload], { type: "application/json" }));
} else {
fetch(trackUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: payload,
keepalive: true,
}).catch(function () {});
}
}
// Track initial page load
trackPageView(location.pathname);
// Subscribe to Material's SPA navigation observable (instant loading)
if (typeof document$ !== "undefined") {
document$.subscribe(function () {
trackPageView(location.pathname);
});
}
})();
</script>
<script>"undefined"!=typeof __md_analytics&&__md_analytics()</script>
<meta property="og:type" content="website" />
<meta property="og:title" content="Environment Variables - Changemaker Lite" />
<meta property="og:description" content="Complete reference for every .env variable in Changemaker Lite." />
<meta property="og:image" content="https://bnkserve.org/assets/images/social/docs/getting-started/environment-variables.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content="https://bnkserve.org/docs/getting-started/environment-variables/" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content="Environment Variables - Changemaker Lite" />
<meta property="twitter:description" content="Complete reference for every .env variable in Changemaker Lite." />
<meta property="twitter:image" content="https://bnkserve.org/assets/images/social/docs/getting-started/environment-variables.png" />
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#environment-variables" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
<aside class="md-banner">
<div class="md-banner__inner md-grid md-typeset">
<button class="md-banner__button md-icon" aria-label="Don't show this again">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">
<nav class="cm-header-nav" role="navigation" aria-label="Application">
<div class="cm-header-nav__brand">
<a href="#" data-path="/home" class="cm-header-nav__brand-link">
<span class="cm-header-nav__brand-text">Changemaker Lite</span>
</a>
</div>
<div class="cm-header-nav__links">
<div class="cm-header-nav__links-inner">
<a href="#" data-path="/" class="cm-header-nav__link" data-nav-id="home" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">home</span><span class="cm-header-nav__label">Home</span></a>
<a href="#" data-path="/campaigns" class="cm-header-nav__link" data-nav-id="campaigns"><span class="material-icons-outlined">send</span><span class="cm-header-nav__label">Campaigns</span></a>
<a href="#" data-path="/shifts" class="cm-header-nav__link" data-nav-id="shifts"><span class="material-icons-outlined">schedule</span><span class="cm-header-nav__label">Shifts</span></a>
<a href="#" data-path="/events" class="cm-header-nav__link" data-nav-id="events" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">event</span><span class="cm-header-nav__label">Events</span></a>
<a href="#" data-path="/gallery" class="cm-header-nav__link" data-nav-id="gallery"><span class="material-icons-outlined">play_circle</span><span class="cm-header-nav__label">Gallery</span></a>
<a href="#" data-path="/shop" class="cm-header-nav__link" data-nav-id="shop"><span class="material-icons-outlined">shopping_bag</span><span class="cm-header-nav__label">Shop</span></a>
<a href="#" data-path="/donate" class="cm-header-nav__link" data-nav-id="donate"><span class="material-icons-outlined">favorite_border</span><span class="cm-header-nav__label">Donate</span></a>
<a href="/" class="cm-header-nav__link" data-nav-id="landing"><span class="material-icons-outlined">language</span><span class="cm-header-nav__label">Website</span></a>
<a href="/docs/" class="cm-header-nav__link" data-nav-id="docs"><span class="material-icons-outlined">menu_book</span><span class="cm-header-nav__label">Docs</span></a>
<a href="#" data-path="/app" class="cm-header-nav__link">
<span class="material-icons-outlined">dashboard</span>
<span class="cm-header-nav__label">Admin</span>
</a>
</div>
<button class="cm-header-nav__hamburger" aria-label="Open navigation menu">
<span class="material-icons-outlined">menu</span>
</button>
</div>
</nav>
<div class="cm-header-nav__mobile-drawer" id="cm-mobile-drawer">
<div class="cm-header-nav__mobile-header">
<span class="cm-header-nav__brand-text">Changemaker Lite</span>
<button class="cm-header-nav__mobile-close" aria-label="Close navigation menu">
<span class="material-icons-outlined">close</span>
</button>
</div>
<div class="cm-header-nav__mobile-links">
<a href="#" data-path="/" class="cm-header-nav__mobile-link" data-nav-id="home" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">home</span><span>Home</span></a>
<a href="#" data-path="/campaigns" class="cm-header-nav__mobile-link" data-nav-id="campaigns"><span class="material-icons-outlined">send</span><span>Campaigns</span></a>
<a href="#" data-path="/shifts" class="cm-header-nav__mobile-link" data-nav-id="shifts"><span class="material-icons-outlined">schedule</span><span>Shifts</span></a>
<a href="#" data-path="/events" class="cm-header-nav__mobile-link" data-nav-id="events" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">event</span><span>Events</span></a>
<a href="#" data-path="/gallery" class="cm-header-nav__mobile-link" data-nav-id="gallery"><span class="material-icons-outlined">play_circle</span><span>Gallery</span></a>
<a href="#" data-path="/shop" class="cm-header-nav__mobile-link" data-nav-id="shop"><span class="material-icons-outlined">shopping_bag</span><span>Shop</span></a>
<a href="#" data-path="/donate" class="cm-header-nav__mobile-link" data-nav-id="donate"><span class="material-icons-outlined">favorite_border</span><span>Donate</span></a>
<a href="/" class="cm-header-nav__mobile-link" data-nav-id="landing"><span class="material-icons-outlined">language</span><span>Website</span></a>
<a href="/docs/" class="cm-header-nav__mobile-link" data-nav-id="docs"><span class="material-icons-outlined">menu_book</span><span>Docs</span></a>
<a href="#" data-path="/app" class="cm-header-nav__mobile-link">
<span class="material-icons-outlined">dashboard</span>
<span>Admin</span>
</a>
</div>
</div>
<div class="cm-header-nav__mobile-overlay" id="cm-mobile-overlay"></div>
<script>
(function() {
var h = location.hostname;
var base;
if (h === 'localhost' || h === '127.0.0.1') {
base = location.protocol + '//localhost:' + (3002 || 3000);
} else {
var parts = h.split('.');
if (parts.length >= 3) { parts[0] = 'app'; }
else { parts.unshift('app'); }
base = location.protocol + '//' + parts.join('.');
}
var links = document.querySelectorAll('[data-path]');
for (var i = 0; i < links.length; i++) {
links[i].setAttribute('href', base + links[i].getAttribute('data-path'));
}
// Highlight active nav link based on current path
var path = location.pathname;
var activeLink = null;
if (path.indexOf('/docs') === 0) activeLink = 'docs';
document.querySelectorAll('.cm-header-nav__link[data-nav-id], .cm-header-nav__mobile-link[data-nav-id]').forEach(function(el) {
if (el.getAttribute('data-nav-id') === activeLink) {
el.classList.add('cm-header-nav__link--active');
}
});
// Hamburger toggle
var hamburger = document.querySelector('.cm-header-nav__hamburger');
var drawer = document.getElementById('cm-mobile-drawer');
var overlay = document.getElementById('cm-mobile-overlay');
var closeBtn = document.querySelector('.cm-header-nav__mobile-close');
function openDrawer() { drawer.classList.add('open'); overlay.classList.add('open'); }
function closeDrawer() { drawer.classList.remove('open'); overlay.classList.remove('open'); }
if (hamburger) hamburger.addEventListener('click', openDrawer);
if (closeBtn) closeBtn.addEventListener('click', closeDrawer);
if (overlay) overlay.addEventListener('click', closeDrawer);
})();
</script>
<style>
.md-banner {
background: linear-gradient(158deg, rgb(0,31,156) 0%, rgb(0,68,204) 100%) !important;
color: #ffffff !important;
padding: 0 !important;
}
.md-banner__button {
display: none !important;
}
.cm-header-nav {
background: linear-gradient(158deg, rgb(0,31,156) 0%, rgb(0,68,204) 100%);
height: 56px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
z-index: 10;
box-sizing: border-box;
}
.cm-header-nav a {
color: rgba(255, 255, 255, 0.85) !important;
}
.cm-header-nav__brand-link {
display: flex;
align-items: center;
gap: 10px;
text-decoration: none !important;
color: #fff !important;
}
.cm-header-nav__brand-text {
font-size: 18px;
font-weight: 600;
color: #fff !important;
}
.cm-header-nav__links {
display: flex;
align-items: center;
}
.cm-header-nav__links-inner {
display: flex;
align-items: center;
gap: 16px;
}
.cm-header-nav__link {
color: rgba(255, 255, 255, 0.85) !important;
text-decoration: none !important;
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
transition: color 0.2s, border-color 0.2s;
white-space: nowrap;
padding-bottom: 2px;
border-bottom: 2px solid transparent;
}
.cm-header-nav__link:hover {
color: #fff !important;
text-decoration: none !important;
}
.cm-header-nav__link--active,
.cm-header-nav__link--active:hover {
color: #fff !important;
font-weight: 600;
border-bottom-color: #fff;
}
.cm-header-nav__link .material-icons-outlined {
font-size: 16px;
}
.cm-header-nav__hamburger {
display: none;
background: none;
border: none;
cursor: pointer;
padding: 4px 8px;
color: #fff;
}
.cm-header-nav__hamburger .material-icons-outlined {
font-size: 24px;
}
/* Mobile drawer */
.cm-header-nav__mobile-drawer {
position: fixed;
top: 0;
right: -280px;
width: 280px;
height: 100vh;
background: #0d1b2a;
z-index: 10001;
transition: right 0.3s ease;
display: flex;
flex-direction: column;
}
.cm-header-nav__mobile-drawer.open {
right: 0;
}
.cm-header-nav__mobile-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
border-bottom: 1px solid rgba(255,255,255,0.1);
background: #1b2838;
}
.cm-header-nav__mobile-close {
background: none;
border: none;
cursor: pointer;
color: rgba(255,255,255,0.85);
padding: 4px;
}
.cm-header-nav__mobile-links {
display: flex;
flex-direction: column;
gap: 4px;
padding: 16px 0;
}
.cm-header-nav__mobile-link {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
color: rgba(255,255,255,0.85) !important;
text-decoration: none !important;
font-size: 15px;
border-radius: 4px;
}
.cm-header-nav__mobile-link:hover {
background: rgba(255,255,255,0.1);
color: #fff !important;
text-decoration: none !important;
}
.cm-header-nav__mobile-link--active {
color: #fff !important;
font-weight: 600;
background: rgba(255,255,255,0.1);
}
.cm-header-nav__mobile-link .material-icons-outlined {
font-size: 18px;
}
.cm-header-nav__mobile-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 10000;
}
.cm-header-nav__mobile-overlay.open {
display: block;
}
@media (max-width: 768px) {
.cm-header-nav { padding: 0 16px; }
.cm-header-nav__links-inner { display: none; }
.cm-header-nav__hamburger { display: block; }
}
</style>
</div>
<script>var el=document.querySelector("[data-md-component=announce]");if(el){var content=el.querySelector(".md-typeset");__md_hash(content.innerHTML)===__md_get("__announce")&&(el.hidden=!0)}</script>
</aside>
</div>
<header class="md-header md-header--shadow md-header--lifted" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../../.." title="Changemaker Lite" class="md-header__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
<img src="../../../assets/logo.png" alt="logo">
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Changemaker Lite
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Environment Variables
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m17.75 4.09-2.53 1.94.91 3.06-2.63-1.81-2.63 1.81.91-3.06-2.53-1.94L12.44 4l1.06-3 1.06 3zm3.5 6.91-1.64 1.25.59 1.98-1.7-1.17-1.7 1.17.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14.4-.4.82-.76 1.27-1.08.75-.53 1.93.36 1.85 1.19-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82-2.81 3.14-2.7 7.96.31 10.98 3.02 3.01 7.84 3.12 10.98.31"/></svg>
</label>
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0-7 2.39 3.42C13.65 5.15 12.84 5 12 5s-1.65.15-2.39.42zM3.34 7l4.16-.35A7.2 7.2 0 0 0 5.94 8.5c-.44.74-.69 1.5-.83 2.29zm.02 10 1.76-3.77a7.131 7.131 0 0 0 2.38 4.14zM20.65 7l-1.77 3.79a7.02 7.02 0 0 0-2.38-4.15zm-.01 10-4.14.36c.59-.51 1.12-1.14 1.54-1.86.42-.73.69-1.5.83-2.29zM12 22l-2.41-3.44c.74.27 1.55.44 2.41.44.82 0 1.63-.17 2.37-.44z"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
changemaker.lite
</div>
</a>
</div>
</nav>
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../../.." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="../../" class="md-tabs__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 21.5c-1.35-.85-3.8-1.5-5.5-1.5-1.65 0-3.35.3-4.75 1.05-.1.05-.15.05-.25.05-.25 0-.5-.25-.5-.5V6c.6-.45 1.25-.75 2-1 1.11-.35 2.33-.5 3.5-.5 1.95 0 4.05.4 5.5 1.5 1.45-1.1 3.55-1.5 5.5-1.5 1.17 0 2.39.15 3.5.5.75.25 1.4.55 2 1v14.6c0 .25-.25.5-.5.5-.1 0-.15 0-.25-.05-1.4-.75-3.1-1.05-4.75-1.05-1.7 0-4.15.65-5.5 1.5M12 8v11.5c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5V7c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5m1 3.5c1.11-.68 2.6-1 4.5-1 .91 0 1.76.09 2.5.28V9.23c-.87-.15-1.71-.23-2.5-.23q-2.655 0-4.5.84zm4.5.17c-1.71 0-3.21.26-4.5.79v1.69c1.11-.65 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24v-1.5c-.87-.16-1.71-.23-2.5-.23m2.5 2.9c-.87-.16-1.71-.24-2.5-.24-1.83 0-3.33.27-4.5.8v1.69c1.11-.66 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24z"/></svg>
Docs
</a>
</li>
<li class="md-tabs__item">
<a href="../../../blog/" class="md-tabs__link">
Blog
</a>
</li>
</ul>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../../.." title="Changemaker Lite" class="md-nav__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
<img src="../../../assets/logo.png" alt="logo">
</a>
Changemaker Lite
</label>
<div class="md-nav__source">
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
changemaker.lite
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" checked>
<div class="md-nav__link md-nav__container">
<a href="../../" class="md-nav__link ">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 21.5c-1.35-.85-3.8-1.5-5.5-1.5-1.65 0-3.35.3-4.75 1.05-.1.05-.15.05-.25.05-.25 0-.5-.25-.5-.5V6c.6-.45 1.25-.75 2-1 1.11-.35 2.33-.5 3.5-.5 1.95 0 4.05.4 5.5 1.5 1.45-1.1 3.55-1.5 5.5-1.5 1.17 0 2.39.15 3.5.5.75.25 1.4.55 2 1v14.6c0 .25-.25.5-.5.5-.1 0-.15 0-.25-.05-1.4-.75-3.1-1.05-4.75-1.05-1.7 0-4.15.65-5.5 1.5M12 8v11.5c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5V7c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5m1 3.5c1.11-.68 2.6-1 4.5-1 .91 0 1.76.09 2.5.28V9.23c-.87-.15-1.71-.23-2.5-.23q-2.655 0-4.5.84zm4.5.17c-1.71 0-3.21.26-4.5.79v1.69c1.11-.65 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24v-1.5c-.87-.16-1.71-.23-2.5-.23m2.5 2.9c-.87-.16-1.71-.24-2.5-.24-1.83 0-3.33.27-4.5.8v1.69c1.11-.66 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24z"/></svg>
<span class="md-ellipsis">
Docs
</span>
</a>
<label class="md-nav__link " for="__nav_2" id="__nav_2_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
Docs
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2_2" checked>
<div class="md-nav__link md-nav__container">
<a href="../" class="md-nav__link ">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m13.13 22.19-1.63-3.83c1.57-.58 3.04-1.36 4.4-2.27zM5.64 12.5l-3.83-1.63 6.1-2.77C7 9.46 6.22 10.93 5.64 12.5M21.61 2.39S16.66.269 11 5.93c-2.19 2.19-3.5 4.6-4.35 6.71-.28.75-.09 1.57.46 2.13l2.13 2.12c.55.56 1.37.74 2.12.46A19.1 19.1 0 0 0 18.07 13c5.66-5.66 3.54-10.61 3.54-10.61m-7.07 7.07c-.78-.78-.78-2.05 0-2.83s2.05-.78 2.83 0c.77.78.78 2.05 0 2.83s-2.05.78-2.83 0m-5.66 7.07-1.41-1.41zM6.24 22l3.64-3.64c-.34-.09-.67-.24-.97-.45L4.83 22zM2 22h1.41l4.77-4.76-1.42-1.41L2 20.59zm0-2.83 4.09-4.08c-.21-.3-.36-.62-.45-.97L2 17.76z"/></svg>
<span class="md-ellipsis">
Getting Started
</span>
</a>
<label class="md-nav__link " for="__nav_2_2" id="__nav_2_2_label" tabindex="0">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_2_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2_2">
<span class="md-nav__icon md-icon"></span>
Getting Started
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../installation/" class="md-nav__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 20h14v-2H5m14-9h-4V3H9v6H5l7 7z"/></svg>
<span class="md-ellipsis">
Installation
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 2c-1.11 0-2 .89-2 2v16a2 2 0 0 0 2 2h6.68a7 7 0 0 1-.68-3 7 7 0 0 1 7-7 7 7 0 0 1 1 .08V8l-6-6zm7 1.5L18.5 9H13zM18 14a.26.26 0 0 0-.26.21l-.19 1.32c-.3.13-.59.29-.85.47l-1.24-.5c-.11 0-.24 0-.31.13l-1 1.73c-.06.11-.04.24.06.32l1.06.82a4.2 4.2 0 0 0 0 1l-1.06.82a.26.26 0 0 0-.06.32l1 1.73c.06.13.19.13.31.13l1.24-.5c.26.18.54.35.85.47l.19 1.32c.02.12.12.21.26.21h2c.11 0 .22-.09.24-.21l.19-1.32c.3-.13.57-.29.84-.47l1.23.5c.13 0 .26 0 .33-.13l1-1.73a.26.26 0 0 0-.06-.32l-1.07-.82c.02-.17.04-.33.04-.5s-.01-.33-.04-.5l1.06-.82a.26.26 0 0 0 .06-.32l-1-1.73c-.06-.13-.19-.13-.32-.13l-1.23.5c-.27-.18-.54-.35-.85-.47l-.19-1.32A.236.236 0 0 0 20 14zm1 3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5c-.84 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5"/></svg>
<span class="md-ellipsis">
Environment Variables
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 2c-1.11 0-2 .89-2 2v16a2 2 0 0 0 2 2h6.68a7 7 0 0 1-.68-3 7 7 0 0 1 7-7 7 7 0 0 1 1 .08V8l-6-6zm7 1.5L18.5 9H13zM18 14a.26.26 0 0 0-.26.21l-.19 1.32c-.3.13-.59.29-.85.47l-1.24-.5c-.11 0-.24 0-.31.13l-1 1.73c-.06.11-.04.24.06.32l1.06.82a4.2 4.2 0 0 0 0 1l-1.06.82a.26.26 0 0 0-.06.32l1 1.73c.06.13.19.13.31.13l1.24-.5c.26.18.54.35.85.47l.19 1.32c.02.12.12.21.26.21h2c.11 0 .22-.09.24-.21l.19-1.32c.3-.13.57-.29.84-.47l1.23.5c.13 0 .26 0 .33-.13l1-1.73a.26.26 0 0 0-.06-.32l-1.07-.82c.02-.17.04-.33.04-.5s-.01-.33-.04-.5l1.06-.82a.26.26 0 0 0 .06-.32l-1-1.73c-.06-.13-.19-.13-.32-.13l-1.23.5c-.27-.18-.54-.35-.85-.47l-.19-1.32A.236.236 0 0 0 20 14zm1 3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5c-.84 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5"/></svg>
<span class="md-ellipsis">
Environment Variables
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="On this page">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
On this page
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#quick-reference" class="md-nav__link">
<span class="md-ellipsis">
Quick Reference
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#general" class="md-nav__link">
<span class="md-ellipsis">
General
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#postgresql-main-database" class="md-nav__link">
<span class="md-ellipsis">
PostgreSQL (Main Database)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#jwt-authentication" class="md-nav__link">
<span class="md-ellipsis">
JWT Authentication
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#encryption-key" class="md-nav__link">
<span class="md-ellipsis">
Encryption Key
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#initial-admin-account" class="md-nav__link">
<span class="md-ellipsis">
Initial Admin Account
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#api-server" class="md-nav__link">
<span class="md-ellipsis">
API Server
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#admin-gui" class="md-nav__link">
<span class="md-ellipsis">
Admin GUI
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nginx-reverse-proxy" class="md-nav__link">
<span class="md-ellipsis">
Nginx Reverse Proxy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#redis" class="md-nav__link">
<span class="md-ellipsis">
Redis
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#payments-stripe" class="md-nav__link">
<span class="md-ellipsis">
Payments (Stripe)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#email-smtp" class="md-nav__link">
<span class="md-ellipsis">
Email / SMTP
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#listmonk-newsletters" class="md-nav__link">
<span class="md-ellipsis">
Listmonk (Newsletters)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#represent-api-canadian-electoral-data" class="md-nav__link">
<span class="md-ellipsis">
Represent API (Canadian Electoral Data)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nocodb-data-browser" class="md-nav__link">
<span class="md-ellipsis">
NocoDB (Data Browser)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#media-manager" class="md-nav__link">
<span class="md-ellipsis">
Media Manager
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#gitea-git-hosting" class="md-nav__link">
<span class="md-ellipsis">
Gitea (Git Hosting)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#n8n-workflow-automation" class="md-nav__link">
<span class="md-ellipsis">
n8n (Workflow Automation)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mkdocs-documentation" class="md-nav__link">
<span class="md-ellipsis">
MkDocs (Documentation)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#code-server-web-ide" class="md-nav__link">
<span class="md-ellipsis">
Code Server (Web IDE)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#homepage-service-dashboard" class="md-nav__link">
<span class="md-ellipsis">
Homepage (Service Dashboard)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mini-qr-qr-code-generator" class="md-nav__link">
<span class="md-ellipsis">
Mini QR (QR Code Generator)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#excalidraw-whiteboard" class="md-nav__link">
<span class="md-ellipsis">
Excalidraw (Whiteboard)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#vaultwarden-password-manager" class="md-nav__link">
<span class="md-ellipsis">
Vaultwarden (Password Manager)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#rocketchat-team-chat" class="md-nav__link">
<span class="md-ellipsis">
Rocket.Chat (Team Chat)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#gancio-event-management" class="md-nav__link">
<span class="md-ellipsis">
Gancio (Event Management)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#jitsi-meet-video-conferencing" class="md-nav__link">
<span class="md-ellipsis">
Jitsi Meet (Video Conferencing)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#sms-campaigns-termux-android-bridge" class="md-nav__link">
<span class="md-ellipsis">
SMS Campaigns (Termux Android Bridge)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mailhog-development-email" class="md-nav__link">
<span class="md-ellipsis">
MailHog (Development Email)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nar-national-address-register" class="md-nav__link">
<span class="md-ellipsis">
NAR (National Address Register)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#geocoding" class="md-nav__link">
<span class="md-ellipsis">
Geocoding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#overpass-area-import" class="md-nav__link">
<span class="md-ellipsis">
Overpass / Area Import
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pangolin-tunnel" class="md-nav__link">
<span class="md-ellipsis">
Pangolin Tunnel
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#monitoring" class="md-nav__link">
<span class="md-ellipsis">
Monitoring
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#bunker-ops-fleet-management" class="md-nav__link">
<span class="md-ellipsis">
Bunker Ops (Fleet Management)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#generating-secrets" class="md-nav__link">
<span class="md-ellipsis">
Generating Secrets
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#minimal-vs-full-deployment" class="md-nav__link">
<span class="md-ellipsis">
Minimal vs Full Deployment
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../first-steps/" class="md-nav__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10.74 11.72c.47 1.23.42 2.51-.99 3.02-2.9 1.07-3.55-1.74-3.59-1.88zm-5.03-.81 4.32-1.07c-.19-1.05.1-2.1.1-3.34 0-1.68-1.33-4.97-3.45-4.44-2.42.6-2.77 3.29-2.68 4.59.12 1.3 1.64 4.08 1.71 4.26m12.14 8.94c-.03.15-.69 2.95-3.59 1.89-1.4-.52-1.46-1.8-.99-3.03zm2.15-6.2c.1-1.3-.24-4-2.67-4.6-2.11-.55-3.44 2.76-3.44 4.45 0 1.23.28 2.28.11 3.33l4.3 1.07c.08-.18 1.59-2.96 1.7-4.25"/></svg>
<span class="md-ellipsis">
First Steps
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../features/" class="md-nav__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m18.09 11.77 1.47 6.33L14 14.74 8.44 18.1l1.46-6.33L5 7.5l6.47-.54L14 1l2.53 5.96L23 7.5zM2 12.43c.19 0 .38-.06.55-.17l3.2-2.11-1.57-1.36-2.73 1.8c-.461.3-.589.91-.29 1.41.2.27.52.43.84.43m-.84 9.12c.2.29.52.45.84.45.19 0 .38-.05.55-.16l4.11-2.71.34-1.37.31-1.45-5.86 3.85c-.461.31-.589.93-.29 1.39m.29-6.17a1 1 0 0 0-.29 1.38c.2.3.52.45.84.45.19 0 .38-.05.55-.16l5.42-3.55.27-1.19-.92-.81z"/></svg>
<span class="md-ellipsis">
Features at a Glance
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../admin/" class="md-nav__link">
<span class="md-ellipsis">
Admin Guide
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../user-guide/" class="md-nav__link">
<span class="md-ellipsis">
User Guide
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../volunteer/" class="md-nav__link">
<span class="md-ellipsis">
Volunteer Guide
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../deployment/" class="md-nav__link">
<span class="md-ellipsis">
Deployment
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../architecture/" class="md-nav__link">
<span class="md-ellipsis">
Architecture
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../services/" class="md-nav__link">
<span class="md-ellipsis">
Services
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../api/" class="md-nav__link">
<span class="md-ellipsis">
API Reference
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../troubleshooting/" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item">
<a href="../../phil/" class="md-nav__link">
<span class="md-ellipsis">
Philosophy
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../blog/" class="md-nav__link">
<span class="md-ellipsis">
Blog
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="On this page">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
On this page
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#quick-reference" class="md-nav__link">
<span class="md-ellipsis">
Quick Reference
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#general" class="md-nav__link">
<span class="md-ellipsis">
General
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#postgresql-main-database" class="md-nav__link">
<span class="md-ellipsis">
PostgreSQL (Main Database)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#jwt-authentication" class="md-nav__link">
<span class="md-ellipsis">
JWT Authentication
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#encryption-key" class="md-nav__link">
<span class="md-ellipsis">
Encryption Key
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#initial-admin-account" class="md-nav__link">
<span class="md-ellipsis">
Initial Admin Account
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#api-server" class="md-nav__link">
<span class="md-ellipsis">
API Server
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#admin-gui" class="md-nav__link">
<span class="md-ellipsis">
Admin GUI
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nginx-reverse-proxy" class="md-nav__link">
<span class="md-ellipsis">
Nginx Reverse Proxy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#redis" class="md-nav__link">
<span class="md-ellipsis">
Redis
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#payments-stripe" class="md-nav__link">
<span class="md-ellipsis">
Payments (Stripe)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#email-smtp" class="md-nav__link">
<span class="md-ellipsis">
Email / SMTP
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#listmonk-newsletters" class="md-nav__link">
<span class="md-ellipsis">
Listmonk (Newsletters)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#represent-api-canadian-electoral-data" class="md-nav__link">
<span class="md-ellipsis">
Represent API (Canadian Electoral Data)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nocodb-data-browser" class="md-nav__link">
<span class="md-ellipsis">
NocoDB (Data Browser)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#media-manager" class="md-nav__link">
<span class="md-ellipsis">
Media Manager
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#gitea-git-hosting" class="md-nav__link">
<span class="md-ellipsis">
Gitea (Git Hosting)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#n8n-workflow-automation" class="md-nav__link">
<span class="md-ellipsis">
n8n (Workflow Automation)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mkdocs-documentation" class="md-nav__link">
<span class="md-ellipsis">
MkDocs (Documentation)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#code-server-web-ide" class="md-nav__link">
<span class="md-ellipsis">
Code Server (Web IDE)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#homepage-service-dashboard" class="md-nav__link">
<span class="md-ellipsis">
Homepage (Service Dashboard)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mini-qr-qr-code-generator" class="md-nav__link">
<span class="md-ellipsis">
Mini QR (QR Code Generator)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#excalidraw-whiteboard" class="md-nav__link">
<span class="md-ellipsis">
Excalidraw (Whiteboard)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#vaultwarden-password-manager" class="md-nav__link">
<span class="md-ellipsis">
Vaultwarden (Password Manager)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#rocketchat-team-chat" class="md-nav__link">
<span class="md-ellipsis">
Rocket.Chat (Team Chat)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#gancio-event-management" class="md-nav__link">
<span class="md-ellipsis">
Gancio (Event Management)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#jitsi-meet-video-conferencing" class="md-nav__link">
<span class="md-ellipsis">
Jitsi Meet (Video Conferencing)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#sms-campaigns-termux-android-bridge" class="md-nav__link">
<span class="md-ellipsis">
SMS Campaigns (Termux Android Bridge)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mailhog-development-email" class="md-nav__link">
<span class="md-ellipsis">
MailHog (Development Email)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nar-national-address-register" class="md-nav__link">
<span class="md-ellipsis">
NAR (National Address Register)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#geocoding" class="md-nav__link">
<span class="md-ellipsis">
Geocoding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#overpass-area-import" class="md-nav__link">
<span class="md-ellipsis">
Overpass / Area Import
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pangolin-tunnel" class="md-nav__link">
<span class="md-ellipsis">
Pangolin Tunnel
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#monitoring" class="md-nav__link">
<span class="md-ellipsis">
Monitoring
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#bunker-ops-fleet-management" class="md-nav__link">
<span class="md-ellipsis">
Bunker Ops (Fleet Management)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#generating-secrets" class="md-nav__link">
<span class="md-ellipsis">
Generating Secrets
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#minimal-vs-full-deployment" class="md-nav__link">
<span class="md-ellipsis">
Minimal vs Full Deployment
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<nav class="md-path" aria-label="Navigation" >
<ol class="md-path__list">
<li class="md-path__item">
<a href="../../.." class="md-path__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-path__item">
<a href="../../" class="md-path__link">
<span class="md-ellipsis">
Docs
</span>
</a>
</li>
<li class="md-path__item">
<a href="../" class="md-path__link">
<span class="md-ellipsis">
Getting Started
</span>
</a>
</li>
</ol>
</nav>
<article class="md-content__inner md-typeset">
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/docs/getting-started/environment-variables.md" title="Edit this page" class="md-content__button md-icon" rel="edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 20H6V4h7v5h5v3.1l2-2V8l-6-6H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h4zm10.2-7c.1 0 .3.1.4.2l1.3 1.3c.2.2.2.6 0 .8l-1 1-2.1-2.1 1-1c.1-.1.2-.2.4-.2m0 3.9L14.1 23H12v-2.1l6.1-6.1z"/></svg>
</a>
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/docs/getting-started/environment-variables.md" title="View source of this page" class="md-content__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 18c.56 0 1 .44 1 1s-.44 1-1 1-1-.44-1-1 .44-1 1-1m0-3c-2.73 0-5.06 1.66-6 4 .94 2.34 3.27 4 6 4s5.06-1.66 6-4c-.94-2.34-3.27-4-6-4m0 6.5a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5 2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5M9.27 20H6V4h7v5h5v4.07c.7.08 1.36.25 2 .49V8l-6-6H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h4.5a8.2 8.2 0 0 1-1.23-2"/></svg>
</a>
<h1 id="environment-variables">Environment Variables<a class="headerlink" href="#environment-variables" title="Permanent link">&para;</a></h1>
<p>Changemaker Lite uses a single <code>.env</code> file at the project root to configure all services. Copy the example file to get started:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a>cp<span class="w"> </span>.env.example<span class="w"> </span>.env
</span></code></pre></div>
<div class="admonition danger">
<p class="admonition-title">Security Essentials</p>
<ul>
<li><strong>Change every</strong> <code>REQUIRED_STRONG_PASSWORD_CHANGE_THIS</code> value before starting services</li>
<li><strong>Generate secrets</strong> with <code>openssl rand -hex 32</code> (or <code>-hex 16</code> where noted)</li>
<li><strong>Never</strong> commit <code>.env</code> to version control</li>
<li>Use unique values for each secret &mdash; do not reuse JWT secrets as encryption keys</li>
</ul>
</div>
<hr />
<h2 id="quick-reference">Quick Reference<a class="headerlink" href="#quick-reference" title="Permanent link">&para;</a></h2>
<p>Variables are grouped by service. Each table marks whether a variable is <strong>required</strong> for a basic deployment or <strong>optional</strong> (has a sensible default or only needed for specific features).</p>
<table>
<thead>
<tr>
<th>Symbol</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span></td>
<td>Must be set before first run</td>
</tr>
<tr>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span></td>
<td>Has a working default; change for production</td>
</tr>
<tr>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span></td>
<td>Feature flag &mdash; opt-in</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="general">General<a class="headerlink" href="#general" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>NODE_ENV</code></td>
<td><code>development</code></td>
<td>Set to <code>production</code> for production deployments. Controls logging, error detail, and security checks.</td>
</tr>
<tr>
<td><code>DOMAIN</code></td>
<td><code>cmlite.org</code></td>
<td>Root domain. Used for nginx subdomain routing (<code>app.DOMAIN</code>, <code>api.DOMAIN</code>, etc.). The root domain serves the MkDocs documentation site; all application routes live under <code>app.DOMAIN</code>.</td>
</tr>
<tr>
<td><code>USER_ID</code></td>
<td><code>1000</code></td>
<td>UID for container file ownership. Match your host user's UID (<code>id -u</code>).</td>
</tr>
<tr>
<td><code>GROUP_ID</code></td>
<td><code>1000</code></td>
<td>GID for container file ownership. Match your host user's GID (<code>id -g</code>).</td>
</tr>
<tr>
<td><code>DOCKER_GROUP_ID</code></td>
<td><code>984</code></td>
<td>GID of the <code>docker</code> group on the host. Needed for containers that access the Docker socket. Find with <code>getent group docker</code>.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="postgresql-main-database">PostgreSQL (Main Database) <span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span><a class="headerlink" href="#postgresql-main-database" title="Permanent link">&para;</a></h2>
<p>The primary database for both the Express API and the Fastify Media API (shared).</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>V2_POSTGRES_USER</code></td>
<td><code>changemaker</code></td>
<td>Database username.</td>
</tr>
<tr>
<td><code>V2_POSTGRES_PASSWORD</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> <strong>Must change.</strong> Database password.</td>
</tr>
<tr>
<td><code>V2_POSTGRES_DB</code></td>
<td><code>changemaker_v2</code></td>
<td>Database name.</td>
</tr>
<tr>
<td><code>V2_POSTGRES_PORT</code></td>
<td><code>5433</code></td>
<td>Host port mapping. The container listens on <code>5432</code> internally.</td>
</tr>
</tbody>
</table>
<div class="admonition tip">
<p class="admonition-title">Connection string</p>
<p>The <code>DATABASE_URL</code> is constructed automatically inside Docker. If running locally, set:
<div class="language-text highlight"><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a>DATABASE_URL=postgresql://changemaker:YOUR_PASSWORD@localhost:5433/changemaker_v2
</span></code></pre></div></p>
</div>
<hr />
<h2 id="jwt-authentication">JWT Authentication <span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span><a class="headerlink" href="#jwt-authentication" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>JWT_ACCESS_SECRET</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> Secret for signing access tokens. Generate with <code>openssl rand -hex 32</code>.</td>
</tr>
<tr>
<td><code>JWT_REFRESH_SECRET</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> Secret for signing refresh tokens. <strong>Must differ</strong> from the access secret.</td>
</tr>
<tr>
<td><code>JWT_ACCESS_EXPIRY</code></td>
<td><code>15m</code></td>
<td>Access token lifetime. Short-lived by design.</td>
</tr>
<tr>
<td><code>JWT_REFRESH_EXPIRY</code></td>
<td><code>7d</code></td>
<td>Refresh token lifetime. Tokens are rotated atomically on each refresh.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="encryption-key">Encryption Key <span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span><a class="headerlink" href="#encryption-key" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ENCRYPTION_KEY</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> AES key for encrypting secrets stored in the database (SMTP passwords, API keys, etc.). Generate with <code>openssl rand -hex 32</code>. <strong>Must not</strong> reuse a JWT secret. Required in production (<code>NODE_ENV=production</code>).</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="initial-admin-account">Initial Admin Account <span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span><a class="headerlink" href="#initial-admin-account" title="Permanent link">&para;</a></h2>
<p>These credentials create the first super-admin user during database seeding (<code>npx prisma db seed</code>).</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>INITIAL_ADMIN_EMAIL</code></td>
<td><code>admin@cmlite.org</code></td>
<td>Email address for the initial admin.</td>
</tr>
<tr>
<td><code>INITIAL_ADMIN_PASSWORD</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> <strong>Must change.</strong> Must be 12+ characters with uppercase, lowercase, and a digit. Change this password after first login.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="api-server">API Server<a class="headerlink" href="#api-server" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>API_PORT</code></td>
<td><code>4000</code></td>
<td>Host port for the Express API.</td>
</tr>
<tr>
<td><code>API_URL</code></td>
<td><code>http://localhost:4000</code></td>
<td>Public URL of the API. Used for generating links in emails and QR codes.</td>
</tr>
<tr>
<td><code>CORS_ORIGINS</code></td>
<td><code>http://localhost:3000,http://localhost</code></td>
<td>Comma-separated list of allowed CORS origins. <strong>Add your production domain</strong> (e.g., <code>https://app.yourdomain.org</code>) for production.</td>
</tr>
</tbody>
</table>
<div class="admonition warning">
<p class="admonition-title">Production CORS</p>
<p>If you deploy behind a tunnel (Pangolin, Cloudflare) and API requests fail with CORS errors, add your production <code>app.</code> subdomain here:
<div class="language-text highlight"><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a>CORS_ORIGINS=https://app.betteredmonton.org,http://localhost:3000,http://localhost
</span></code></pre></div>
Then restart the API: <code>docker compose restart api</code></p>
</div>
<hr />
<h2 id="admin-gui">Admin GUI<a class="headerlink" href="#admin-gui" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ADMIN_PORT</code></td>
<td><code>3000</code></td>
<td>Host port for the React admin dashboard.</td>
</tr>
<tr>
<td><code>ADMIN_URL</code></td>
<td><code>http://localhost:3000</code></td>
<td>Public URL of the admin GUI.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="nginx-reverse-proxy">Nginx Reverse Proxy<a class="headerlink" href="#nginx-reverse-proxy" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>NGINX_HTTP_PORT</code></td>
<td><code>80</code></td>
<td>HTTP port. All subdomains route through nginx.</td>
</tr>
<tr>
<td><code>NGINX_HTTPS_PORT</code></td>
<td><code>443</code></td>
<td>HTTPS port. SSL is typically handled by the tunnel provider (Pangolin/Cloudflare).</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="redis">Redis <span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span><a class="headerlink" href="#redis" title="Permanent link">&para;</a></h2>
<p>Shared by rate limiting, BullMQ job queues, geocoding cache, and session data.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>REDIS_PASSWORD</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> <strong>Must change.</strong> Redis requires authentication.</td>
</tr>
<tr>
<td><code>REDIS_URL</code></td>
<td><code>redis://:${REDIS_PASSWORD}@redis-changemaker:6379</code></td>
<td>Full connection URL. Uses the password variable automatically.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="payments-stripe">Payments (Stripe) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span><a class="headerlink" href="#payments-stripe" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ENABLE_PAYMENTS</code></td>
<td><code>false</code></td>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span> Set to <code>true</code> to enable the payments feature (memberships, products, donations). Stripe API keys are stored encrypted in the database via the admin settings page.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="email-smtp">Email / SMTP <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span><a class="headerlink" href="#email-smtp" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>SMTP_HOST</code></td>
<td><code>mailhog-changemaker</code></td>
<td>SMTP server. Default points to the MailHog dev container.</td>
</tr>
<tr>
<td><code>SMTP_PORT</code></td>
<td><code>1025</code></td>
<td>SMTP port. <code>1025</code> for MailHog, <code>587</code> for most production SMTP.</td>
</tr>
<tr>
<td><code>SMTP_USER</code></td>
<td><em>(empty)</em></td>
<td>SMTP username. Not needed for MailHog.</td>
</tr>
<tr>
<td><code>SMTP_PASS</code></td>
<td><em>(empty)</em></td>
<td>SMTP password.</td>
</tr>
<tr>
<td><code>SMTP_FROM</code></td>
<td><code>noreply@cmlite.org</code></td>
<td>"From" address on outgoing emails.</td>
</tr>
<tr>
<td><code>SMTP_FROM_NAME</code></td>
<td><code>Changemaker Lite</code></td>
<td>Display name for the "From" header.</td>
</tr>
<tr>
<td><code>EMAIL_TEST_MODE</code></td>
<td><code>true</code></td>
<td>When <code>true</code>, all emails go to MailHog instead of real SMTP. <strong>Set to <code>false</code> in production.</strong></td>
</tr>
<tr>
<td><code>TEST_EMAIL_RECIPIENT</code></td>
<td><code>admin@cmlite.org</code></td>
<td>Catch-all recipient when test mode is on.</td>
</tr>
</tbody>
</table>
<div class="admonition info">
<p class="admonition-title">Development email</p>
<p>With <code>EMAIL_TEST_MODE=true</code>, all outgoing email is captured in MailHog at <code>http://localhost:8025</code>. No real emails are sent.</p>
</div>
<hr />
<h2 id="listmonk-newsletters">Listmonk (Newsletters) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span><a class="headerlink" href="#listmonk-newsletters" title="Permanent link">&para;</a></h2>
<p>Listmonk handles newsletter/marketing campaigns. Sync with the main platform is opt-in.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>LISTMONK_PORT</code></td>
<td><code>9001</code></td>
<td>Listmonk web UI port.</td>
</tr>
<tr>
<td><code>LISTMONK_DB_PORT</code></td>
<td><code>5434</code></td>
<td>Listmonk's own PostgreSQL port (separate from the main DB). Uses 5434 to avoid conflict with the main PostgreSQL (5432 internal / 5433 host).</td>
</tr>
<tr>
<td><code>LISTMONK_DB_USER</code></td>
<td><code>listmonk</code></td>
<td>Listmonk database user.</td>
</tr>
<tr>
<td><code>LISTMONK_DB_PASSWORD</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> Listmonk database password.</td>
</tr>
<tr>
<td><code>LISTMONK_DB_NAME</code></td>
<td><code>listmonk</code></td>
<td>Listmonk database name.</td>
</tr>
<tr>
<td><code>LISTMONK_WEB_ADMIN_USER</code></td>
<td><code>admin</code></td>
<td>Login for the Listmonk web dashboard.</td>
</tr>
<tr>
<td><code>LISTMONK_WEB_ADMIN_PASSWORD</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> Password for the Listmonk web dashboard.</td>
</tr>
<tr>
<td><code>LISTMONK_API_USER</code></td>
<td><code>v2-api</code></td>
<td>API user for programmatic access (auto-created by init container).</td>
</tr>
<tr>
<td><code>LISTMONK_API_TOKEN</code></td>
<td>&mdash;</td>
<td>Token for API user. Generate with <code>openssl rand -hex 16</code>.</td>
</tr>
<tr>
<td><code>LISTMONK_ADMIN_USER</code></td>
<td><code>v2-api</code></td>
<td>Same as <code>LISTMONK_API_USER</code> (used by the sync service).</td>
</tr>
<tr>
<td><code>LISTMONK_ADMIN_PASSWORD</code></td>
<td>&mdash;</td>
<td>Same as <code>LISTMONK_API_TOKEN</code>.</td>
</tr>
<tr>
<td><code>LISTMONK_SYNC_ENABLED</code></td>
<td><code>false</code></td>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span> Set to <code>true</code> to sync participants/locations/users to Listmonk lists.</td>
</tr>
<tr>
<td><code>LISTMONK_WEBHOOK_SECRET</code></td>
<td><em>(empty)</em></td>
<td>Shared secret for Listmonk webhook callbacks.</td>
</tr>
<tr>
<td><code>LISTMONK_PROXY_PORT</code></td>
<td><code>9002</code></td>
<td>Nginx proxy port for Listmonk.</td>
</tr>
</tbody>
</table>
<details class="example">
<summary>Listmonk SMTP settings</summary>
<p>Listmonk has its own SMTP configuration, separate from the main platform's:</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>LISTMONK_SMTP_HOST</code></td>
<td><code>mailhog-changemaker</code></td>
<td>SMTP host for Listmonk.</td>
</tr>
<tr>
<td><code>LISTMONK_SMTP_PORT</code></td>
<td><code>1025</code></td>
<td>SMTP port.</td>
</tr>
<tr>
<td><code>LISTMONK_SMTP_USER</code></td>
<td><em>(empty)</em></td>
<td>SMTP username.</td>
</tr>
<tr>
<td><code>LISTMONK_SMTP_PASSWORD</code></td>
<td><em>(empty)</em></td>
<td>SMTP password.</td>
</tr>
<tr>
<td><code>LISTMONK_SMTP_TLS_TYPE</code></td>
<td><code>none</code></td>
<td>TLS mode: <code>none</code>, <code>STARTTLS</code>, or <code>TLS</code>.</td>
</tr>
<tr>
<td><code>LISTMONK_SMTP_FROM</code></td>
<td><code>Changemaker Lite &lt;noreply@cmlite.org&gt;</code></td>
<td>From address for newsletters.</td>
</tr>
</tbody>
</table>
</details>
<hr />
<h2 id="represent-api-canadian-electoral-data">Represent API (Canadian Electoral Data)<a class="headerlink" href="#represent-api-canadian-electoral-data" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>REPRESENT_API_URL</code></td>
<td><code>https://represent.opennorth.ca</code></td>
<td>OpenNorth Represent API endpoint. Used for postal code &rarr; representative lookups. No API key required.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="nocodb-data-browser">NocoDB (Data Browser) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span><a class="headerlink" href="#nocodb-data-browser" title="Permanent link">&para;</a></h2>
<p>Read-only database browser. Useful for inspecting data without SQL.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>NOCODB_V2_PORT</code> / <code>NOCODB_PORT</code></td>
<td><code>8091</code></td>
<td>Host port for the NocoDB web UI.</td>
</tr>
<tr>
<td><code>NOCODB_URL</code></td>
<td><code>http://changemaker-v2-nocodb:8080</code></td>
<td>Internal Docker URL.</td>
</tr>
<tr>
<td><code>NC_ADMIN_EMAIL</code></td>
<td><code>admin@cmlite.org</code></td>
<td>NocoDB admin email.</td>
</tr>
<tr>
<td><code>NC_ADMIN_PASSWORD</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> NocoDB admin password.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="media-manager">Media Manager <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span><a class="headerlink" href="#media-manager" title="Permanent link">&para;</a></h2>
<p>Video library with upload, analytics, scheduling, and a public gallery.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ENABLE_MEDIA_FEATURES</code></td>
<td><code>false</code></td>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span> Set to <code>true</code> to enable the media system.</td>
</tr>
<tr>
<td><code>MEDIA_API_PORT</code></td>
<td><code>4100</code></td>
<td>Fastify media API port.</td>
</tr>
<tr>
<td><code>MEDIA_API_PUBLIC_URL</code></td>
<td><code>http://media-api:4100</code></td>
<td>Internal URL for the media API container.</td>
</tr>
<tr>
<td><code>MEDIA_ROOT</code></td>
<td><code>/media/library</code></td>
<td>Path to the video library inside the container.</td>
</tr>
<tr>
<td><code>MEDIA_UPLOADS</code></td>
<td><code>/media/uploads</code></td>
<td>Path for upload processing.</td>
</tr>
<tr>
<td><code>MAX_UPLOAD_SIZE_GB</code></td>
<td><code>10</code></td>
<td>Maximum single-file upload size in gigabytes.</td>
</tr>
<tr>
<td><code>PUBLIC_MEDIA_PORT</code></td>
<td><code>3100</code></td>
<td>Public media gallery server port.</td>
</tr>
<tr>
<td><code>VIDEO_PLAYER_DEBUG</code></td>
<td><code>false</code></td>
<td>Enable verbose video player logging.</td>
</tr>
</tbody>
</table>
<details class="example">
<summary>Analytics &amp; scheduling settings</summary>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>VIDEO_ANALYTICS_RETENTION_DAYS</code></td>
<td><code>90</code></td>
<td>Days to retain analytics data. GDPR-compliant with IP hashing.</td>
</tr>
<tr>
<td><code>VIDEO_ANALYTICS_IP_HASHING_ENABLED</code></td>
<td><code>true</code></td>
<td>Hash viewer IPs for privacy.</td>
</tr>
<tr>
<td><code>VIDEO_SCHEDULE_DEFAULT_TIMEZONE</code></td>
<td><code>UTC</code></td>
<td>Default timezone for scheduled publishing.</td>
</tr>
<tr>
<td><code>VIDEO_SCHEDULE_NOTIFICATION_ENABLED</code></td>
<td><code>true</code></td>
<td>Notify on scheduled publish/unpublish.</td>
</tr>
<tr>
<td><code>VIDEO_PREVIEW_LINK_EXPIRY_HOURS</code></td>
<td><code>24</code></td>
<td>Preview link JWT expiry (hours).</td>
</tr>
</tbody>
</table>
</details>
<hr />
<h2 id="gitea-git-hosting">Gitea (Git Hosting) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span><a class="headerlink" href="#gitea-git-hosting" title="Permanent link">&para;</a></h2>
<p>Self-hosted Git repository. Optional service.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GITEA_URL</code></td>
<td><code>http://gitea-changemaker:3000</code></td>
<td>Internal container URL for Gitea.</td>
</tr>
<tr>
<td><code>GITEA_PORT</code> / <code>GITEA_WEB_PORT</code></td>
<td><code>3030</code></td>
<td>Gitea web UI port.</td>
</tr>
<tr>
<td><code>GITEA_SSH_PORT</code></td>
<td><code>2222</code></td>
<td>Gitea SSH port for git operations.</td>
</tr>
<tr>
<td><code>GITEA_DB_TYPE</code></td>
<td><code>mysql</code></td>
<td>Database type (Gitea uses its own MySQL).</td>
</tr>
<tr>
<td><code>GITEA_DB_HOST</code></td>
<td><code>gitea-db:3306</code></td>
<td>Internal database host.</td>
</tr>
<tr>
<td><code>GITEA_DB_NAME</code></td>
<td><code>gitea</code></td>
<td>Database name.</td>
</tr>
<tr>
<td><code>GITEA_DB_USER</code></td>
<td><code>gitea</code></td>
<td>Database user.</td>
</tr>
<tr>
<td><code>GITEA_DB_PASSWD</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> Gitea database password.</td>
</tr>
<tr>
<td><code>GITEA_DB_ROOT_PASSWORD</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> MySQL root password for Gitea.</td>
</tr>
<tr>
<td><code>GITEA_ROOT_URL</code></td>
<td><code>https://git.cmlite.org</code></td>
<td>Public-facing URL for Gitea.</td>
</tr>
<tr>
<td><code>GITEA_DOMAIN</code></td>
<td><code>git.cmlite.org</code></td>
<td>Domain used in git clone URLs.</td>
</tr>
</tbody>
</table>
<details class="example">
<summary>Gitea Docs Comments</summary>
<p>Enable comments on MkDocs documentation pages, backed by Gitea Issues.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GITEA_COMMENTS_ENABLED</code></td>
<td><code>false</code></td>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span> Enable comments on MkDocs pages.</td>
</tr>
<tr>
<td><code>GITEA_API_TOKEN</code></td>
<td><em>(empty)</em></td>
<td>Personal access token with repo write scope. Create in Gitea &rarr; Settings &rarr; Applications.</td>
</tr>
<tr>
<td><code>GITEA_COMMENTS_REPO_OWNER</code></td>
<td><em>(empty)</em></td>
<td>Gitea username that owns the docs-comments repo.</td>
</tr>
<tr>
<td><code>GITEA_COMMENTS_REPO_NAME</code></td>
<td><code>docs-comments</code></td>
<td>Repository name (auto-created via admin setup).</td>
</tr>
<tr>
<td><code>GITEA_OAUTH_CLIENT_ID</code></td>
<td><em>(empty)</em></td>
<td>OAuth2 application client ID (create in Gitea &rarr; Settings &rarr; Applications &rarr; OAuth2).</td>
</tr>
<tr>
<td><code>GITEA_OAUTH_CLIENT_SECRET</code></td>
<td><em>(empty)</em></td>
<td>OAuth2 application client secret.</td>
</tr>
</tbody>
</table>
</details>
<hr />
<h2 id="n8n-workflow-automation">n8n (Workflow Automation) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span><a class="headerlink" href="#n8n-workflow-automation" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>N8N_PORT</code></td>
<td><code>5678</code></td>
<td>n8n web UI port.</td>
</tr>
<tr>
<td><code>N8N_HOST</code></td>
<td><code>n8n.cmlite.org</code></td>
<td>Public hostname for n8n.</td>
</tr>
<tr>
<td><code>N8N_ENCRYPTION_KEY</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> Encryption key for n8n credentials storage.</td>
</tr>
<tr>
<td><code>N8N_USER_EMAIL</code></td>
<td><code>admin@example.com</code></td>
<td>Initial n8n admin email.</td>
</tr>
<tr>
<td><code>N8N_USER_PASSWORD</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> Initial n8n admin password.</td>
</tr>
<tr>
<td><code>GENERIC_TIMEZONE</code></td>
<td><code>UTC</code></td>
<td>Timezone for n8n cron triggers.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="mkdocs-documentation">MkDocs (Documentation)<a class="headerlink" href="#mkdocs-documentation" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>MKDOCS_PORT</code></td>
<td><code>4003</code></td>
<td>MkDocs dev server port (live preview).</td>
</tr>
<tr>
<td><code>MKDOCS_SITE_SERVER_PORT</code></td>
<td><code>4004</code></td>
<td>MkDocs static site server port.</td>
</tr>
<tr>
<td><code>BASE_DOMAIN</code></td>
<td><code>https://cmlite.org</code></td>
<td>Base URL for generated documentation links.</td>
</tr>
<tr>
<td><code>MKDOCS_PREVIEW_URL</code></td>
<td><code>http://mkdocs:8000</code></td>
<td>Internal container URL.</td>
</tr>
<tr>
<td><code>MKDOCS_DOCS_PATH</code></td>
<td><code>/mkdocs/docs</code></td>
<td>Documentation source directory inside the container.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="code-server-web-ide">Code Server (Web IDE)<a class="headerlink" href="#code-server-web-ide" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>CODE_SERVER_PORT</code></td>
<td><code>8888</code></td>
<td>Code Server web UI port.</td>
</tr>
<tr>
<td><code>CODE_SERVER_URL</code></td>
<td><code>http://code-server:8080</code></td>
<td>Internal container URL.</td>
</tr>
<tr>
<td><code>USER_NAME</code></td>
<td><code>coder</code></td>
<td>User account inside the Code Server container.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="homepage-service-dashboard">Homepage (Service Dashboard)<a class="headerlink" href="#homepage-service-dashboard" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>HOMEPAGE_PORT</code></td>
<td><code>3010</code></td>
<td>Homepage web UI port.</td>
</tr>
<tr>
<td><code>HOMEPAGE_EMBED_PORT</code></td>
<td><code>8887</code></td>
<td>Port for iframe embedding in admin.</td>
</tr>
<tr>
<td><code>HOMEPAGE_VAR_BASE_URL</code></td>
<td><code>http://localhost</code></td>
<td>Base URL used in Homepage service links.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="mini-qr-qr-code-generator">Mini QR (QR Code Generator)<a class="headerlink" href="#mini-qr-qr-code-generator" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>MINI_QR_PORT</code></td>
<td><code>8089</code></td>
<td>Mini QR direct access port.</td>
</tr>
<tr>
<td><code>MINI_QR_URL</code></td>
<td><code>http://mini-qr:8080</code></td>
<td>Internal container URL.</td>
</tr>
<tr>
<td><code>MINI_QR_EMBED_PORT</code></td>
<td><code>8885</code></td>
<td>Port for iframe embedding (walk sheets, cut exports).</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="excalidraw-whiteboard">Excalidraw (Whiteboard)<a class="headerlink" href="#excalidraw-whiteboard" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>EXCALIDRAW_PORT</code></td>
<td><code>8090</code></td>
<td>Excalidraw web UI port.</td>
</tr>
<tr>
<td><code>EXCALIDRAW_URL</code></td>
<td><code>http://excalidraw-changemaker:80</code></td>
<td>Internal container URL.</td>
</tr>
<tr>
<td><code>EXCALIDRAW_EMBED_PORT</code></td>
<td><code>8886</code></td>
<td>Port for iframe embedding.</td>
</tr>
<tr>
<td><code>EXCALIDRAW_WS_URL</code></td>
<td><code>wss://draw.cmlite.org</code></td>
<td>WebSocket URL for real-time collaboration.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="vaultwarden-password-manager">Vaultwarden (Password Manager) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span><a class="headerlink" href="#vaultwarden-password-manager" title="Permanent link">&para;</a></h2>
<p>Self-hosted Bitwarden-compatible password manager. Optional service.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>VAULTWARDEN_PORT</code></td>
<td><code>8445</code></td>
<td>Vaultwarden web UI port.</td>
</tr>
<tr>
<td><code>VAULTWARDEN_URL</code></td>
<td><code>http://vaultwarden-changemaker:80</code></td>
<td>Internal container URL.</td>
</tr>
<tr>
<td><code>VAULTWARDEN_EMBED_PORT</code></td>
<td><code>8890</code></td>
<td>Port for iframe embedding in admin.</td>
</tr>
<tr>
<td><code>VAULTWARDEN_ADMIN_TOKEN</code></td>
<td><em>(empty)</em></td>
<td>Admin panel token (access at <code>/admin</code>). Generate with <code>openssl rand -hex 32</code>.</td>
</tr>
<tr>
<td><code>VAULTWARDEN_DOMAIN</code></td>
<td><code>https://vault.cmlite.org</code></td>
<td>Public-facing URL. <strong>Must use HTTPS</strong> &mdash; Bitwarden web vault enforces HTTPS for account creation. Set to your Pangolin tunnel URL.</td>
</tr>
<tr>
<td><code>VAULTWARDEN_SIGNUPS_ALLOWED</code></td>
<td><code>false</code></td>
<td>Allow new user self-registration. Keep <code>false</code> and use admin panel invites.</td>
</tr>
<tr>
<td><code>VAULTWARDEN_WEBSOCKET_ENABLED</code></td>
<td><code>true</code></td>
<td>Enable WebSocket notifications for real-time sync.</td>
</tr>
<tr>
<td><code>VAULTWARDEN_SMTP_SECURITY</code></td>
<td><code>off</code></td>
<td>SMTP security mode: <code>off</code> for MailHog, <code>starttls</code> or <code>force_tls</code> for production. Uses the main <code>SMTP_*</code> variables for host/credentials.</td>
</tr>
</tbody>
</table>
<div class="admonition info">
<p class="admonition-title">Initial setup</p>
<p>The <code>vaultwarden-init</code> container automatically invites the <code>INITIAL_ADMIN_EMAIL</code> user when starting. Check MailHog (or your SMTP) for the invitation email.</p>
</div>
<hr />
<h2 id="rocketchat-team-chat">Rocket.Chat (Team Chat) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span><a class="headerlink" href="#rocketchat-team-chat" title="Permanent link">&para;</a></h2>
<p>Self-hosted team chat for volunteer coordination. Requires MongoDB (auto-configured).</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ENABLE_CHAT</code></td>
<td><code>false</code></td>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span> Set to <code>true</code> to enable the Rocket.Chat integration. The initial default; once saved in admin Settings, the DB value is authoritative.</td>
</tr>
<tr>
<td><code>ROCKETCHAT_ADMIN_USER</code></td>
<td><code>rcadmin</code></td>
<td>Rocket.Chat admin username.</td>
</tr>
<tr>
<td><code>ROCKETCHAT_ADMIN_PASSWORD</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> Rocket.Chat admin password.</td>
</tr>
<tr>
<td><code>ROCKETCHAT_URL</code></td>
<td><code>http://rocketchat-changemaker:3000</code></td>
<td>Internal container URL.</td>
</tr>
<tr>
<td><code>ROCKETCHAT_EMBED_PORT</code></td>
<td><code>8891</code></td>
<td>Port for iframe embedding in admin.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="gancio-event-management">Gancio (Event Management) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span><a class="headerlink" href="#gancio-event-management" title="Permanent link">&para;</a></h2>
<p>Self-hosted event management platform. Uses the shared PostgreSQL database (auto-created by <code>init-gancio-db.sh</code>).</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GANCIO_PORT</code></td>
<td><code>8092</code></td>
<td>Gancio web UI port.</td>
</tr>
<tr>
<td><code>GANCIO_URL</code></td>
<td><code>http://gancio-changemaker:13120</code></td>
<td>Internal container URL.</td>
</tr>
<tr>
<td><code>GANCIO_EMBED_PORT</code></td>
<td><code>8892</code></td>
<td>Port for iframe embedding in admin.</td>
</tr>
<tr>
<td><code>GANCIO_BASE_URL</code></td>
<td><code>https://events.cmlite.org</code></td>
<td>Public-facing URL for Gancio. Used in event links.</td>
</tr>
<tr>
<td><code>GANCIO_ADMIN_USER</code></td>
<td><code>admin</code></td>
<td>Gancio admin username for shift-to-event sync (OAuth login).</td>
</tr>
<tr>
<td><code>GANCIO_ADMIN_PASSWORD</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> Gancio admin password.</td>
</tr>
<tr>
<td><code>GANCIO_SYNC_ENABLED</code></td>
<td><code>false</code></td>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span> Set to <code>true</code> to enable automatic shift &rarr; Gancio event synchronization.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="jitsi-meet-video-conferencing">Jitsi Meet (Video Conferencing) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span><a class="headerlink" href="#jitsi-meet-video-conferencing" title="Permanent link">&para;</a></h2>
<p>Self-hosted video conferencing with JWT authentication. Integrates with Rocket.Chat for in-channel video calls.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ENABLE_MEET</code></td>
<td><code>false</code></td>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span> Set to <code>true</code> to enable the Jitsi Meet integration. The initial default; once saved in admin Settings, the DB value is authoritative.</td>
</tr>
<tr>
<td><code>JITSI_APP_ID</code></td>
<td><code>changemaker</code></td>
<td>JWT application ID. Must match across Jitsi Prosody, Rocket.Chat app settings, and <code>JWT_ACCEPTED_ISSUERS</code>/<code>JWT_ACCEPTED_AUDIENCES</code>.</td>
</tr>
<tr>
<td><code>JITSI_APP_SECRET</code></td>
<td>&mdash;</td>
<td><span class="twemoji text-red"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg></span> JWT secret for signing Jitsi tokens. Generate with <code>openssl rand -hex 32</code>. Shared between Jitsi Prosody, Rocket.Chat, and the API.</td>
</tr>
<tr>
<td><code>JITSI_JICOFO_AUTH_PASSWORD</code></td>
<td>&mdash;</td>
<td>Internal XMPP password for Jicofo (conference focus). Generate with <code>openssl rand -hex 16</code>.</td>
</tr>
<tr>
<td><code>JITSI_JVB_AUTH_PASSWORD</code></td>
<td>&mdash;</td>
<td>Internal XMPP password for JVB (video bridge). Generate with <code>openssl rand -hex 16</code>.</td>
</tr>
<tr>
<td><code>JITSI_EMBED_PORT</code></td>
<td><code>8893</code></td>
<td>Port for iframe embedding in admin.</td>
</tr>
<tr>
<td><code>JITSI_URL</code></td>
<td><code>http://jitsi-web-changemaker:80</code></td>
<td>Internal container URL.</td>
</tr>
<tr>
<td><code>JVB_ADVERTISE_IP</code></td>
<td><em>(empty)</em></td>
<td>Server's public IP address. <strong>Required in production</strong> for NAT traversal so remote participants can connect.</td>
</tr>
<tr>
<td><code>JVB_PORT</code></td>
<td><code>10000</code></td>
<td>UDP port for media traffic. Must be open in your firewall.</td>
</tr>
</tbody>
</table>
<div class="admonition warning">
<p class="admonition-title">Production requirements</p>
<ul>
<li><code>JVB_ADVERTISE_IP</code> must be set to your server's public IP for calls to work outside the local network.</li>
<li>Port <code>10000/udp</code> must be open in your firewall for media traffic.</li>
<li>Calls must go through the production domain (not localhost) for SSL/JWT to work.</li>
</ul>
</div>
<hr />
<h2 id="sms-campaigns-termux-android-bridge">SMS Campaigns (Termux Android Bridge) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span><a class="headerlink" href="#sms-campaigns-termux-android-bridge" title="Permanent link">&para;</a></h2>
<p>Send SMS messages via an Android phone running the Termux API server. The phone acts as an SMS gateway.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ENABLE_SMS</code></td>
<td><code>false</code></td>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span> Set to <code>true</code> to enable SMS campaigns. The initial default; once saved in admin Settings, the DB value is authoritative.</td>
</tr>
<tr>
<td><code>TERMUX_API_URL</code></td>
<td><code>http://10.0.0.193:5001</code></td>
<td>URL of the Termux API server running on the Android phone.</td>
</tr>
<tr>
<td><code>TERMUX_API_KEY</code></td>
<td><em>(empty)</em></td>
<td>API key for authenticating with the Termux server (HMAC auth via <code>X-API-Key</code> header).</td>
</tr>
<tr>
<td><code>SMS_DELAY_BETWEEN_MS</code></td>
<td><code>3000</code></td>
<td>Delay between sending individual SMS messages (ms). Prevents carrier throttling.</td>
</tr>
<tr>
<td><code>SMS_MAX_RETRIES</code></td>
<td><code>3</code></td>
<td>Maximum retry attempts for failed SMS sends.</td>
</tr>
<tr>
<td><code>SMS_RESPONSE_SYNC_INTERVAL_MS</code></td>
<td><code>30000</code></td>
<td>How often to poll the phone's inbox for responses (ms).</td>
</tr>
<tr>
<td><code>SMS_DEVICE_MONITOR_INTERVAL_MS</code></td>
<td><code>30000</code></td>
<td>How often to check device health &mdash; battery, connectivity (ms).</td>
</tr>
</tbody>
</table>
<div class="admonition tip">
<p class="admonition-title">GUI configuration</p>
<p>The Termux API URL and API key can also be configured from <strong>Admin &rarr; Settings &rarr; SMS</strong>. Database values override these env vars when set.</p>
</div>
<hr />
<h2 id="mailhog-development-email">MailHog (Development Email)<a class="headerlink" href="#mailhog-development-email" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>MAILHOG_SMTP_PORT</code></td>
<td><code>1025</code></td>
<td>SMTP port for capturing emails.</td>
</tr>
<tr>
<td><code>MAILHOG_WEB_PORT</code></td>
<td><code>8025</code></td>
<td>Web UI to view captured emails.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="nar-national-address-register">NAR (National Address Register) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span><a class="headerlink" href="#nar-national-address-register" title="Permanent link">&para;</a></h2>
<p>Canadian address data import for geographic canvassing.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>NAR_DATA_DIR</code></td>
<td><code>/data</code></td>
<td>Path to extracted NAR data inside the container. Expects <code>YYYYMM/Addresses/</code> and <code>YYYYMM/Locations/</code> subdirectories. Mount via <code>./data:/data:ro</code> in Docker Compose.</td>
</tr>
</tbody>
</table>
<p>Download NAR data from <a href="https://www150.statcan.gc.ca/n1/pub/46-26-0002/462600022022001-eng.htm">Statistics Canada</a>.</p>
<hr />
<h2 id="geocoding">Geocoding <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span><a class="headerlink" href="#geocoding" title="Permanent link">&para;</a></h2>
<p>Multi-provider geocoding for address resolution. Works out of the box with free providers; optional paid providers improve accuracy.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>MAPBOX_API_KEY</code></td>
<td><em>(empty)</em></td>
<td>Mapbox API key for improved geocoding accuracy. Free tier: 100k requests/month. <a href="https://www.mapbox.com/pricing">Sign up</a>.</td>
</tr>
<tr>
<td><code>GEOCODING_RATE_LIMIT_MS</code></td>
<td><code>1100</code></td>
<td>Delay between requests to free providers (ms). Respects rate limits.</td>
</tr>
<tr>
<td><code>GEOCODING_CACHE_ENABLED</code></td>
<td><code>true</code></td>
<td>Enable Redis-backed geocoding cache.</td>
</tr>
<tr>
<td><code>GEOCODING_CACHE_TTL_HOURS</code></td>
<td><code>24</code></td>
<td>Cache lifetime in hours.</td>
</tr>
<tr>
<td><code>GOOGLE_MAPS_API_KEY</code></td>
<td><em>(empty)</em></td>
<td>Google Maps API key. Most accurate but $0.005/request after free tier.</td>
</tr>
<tr>
<td><code>GOOGLE_MAPS_ENABLED</code></td>
<td><code>false</code></td>
<td>Enable Google Maps as a geocoding provider.</td>
</tr>
<tr>
<td><code>GEOCODING_PARALLEL_ENABLED</code></td>
<td><code>true</code></td>
<td>Enable parallel geocoding for bulk imports (~10x speedup).</td>
</tr>
<tr>
<td><code>GEOCODING_BATCH_SIZE</code></td>
<td><code>10</code></td>
<td>Number of concurrent geocoding requests during bulk operations.</td>
</tr>
<tr>
<td><code>BULK_GEOCODE_ENABLED</code></td>
<td><code>true</code></td>
<td>Enable bulk re-geocoding from the admin UI.</td>
</tr>
<tr>
<td><code>BULK_GEOCODE_MAX_BATCH</code></td>
<td><code>5000</code></td>
<td>Maximum locations per bulk geocoding run.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="overpass-area-import">Overpass / Area Import <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span><a class="headerlink" href="#overpass-area-import" title="Permanent link">&para;</a></h2>
<p>OpenStreetMap data import for map enrichment.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>OVERPASS_API_URL</code></td>
<td><code>https://overpass-api.de/api/interpreter</code></td>
<td>Overpass API endpoint. Use a private instance for heavy usage.</td>
</tr>
<tr>
<td><code>OVERPASS_MIN_DELAY_MS</code></td>
<td><code>30000</code></td>
<td>Minimum delay between requests (ms). The public API requires 30 seconds.</td>
</tr>
<tr>
<td><code>AREA_IMPORT_MAX_GRID_POINTS</code></td>
<td><code>500</code></td>
<td>Maximum reverse-geocode grid points per area import.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="pangolin-tunnel">Pangolin Tunnel <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span><a class="headerlink" href="#pangolin-tunnel" title="Permanent link">&para;</a></h2>
<p>Expose services to the internet without port forwarding, using a self-hosted <a href="https://github.com/fosrl/pangolin">Pangolin</a> instance.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>PANGOLIN_API_URL</code></td>
<td><code>https://api.bnkserve.org/v1</code></td>
<td>Pangolin server API endpoint.</td>
</tr>
<tr>
<td><code>PANGOLIN_API_KEY</code></td>
<td><em>(empty)</em></td>
<td>API key for Pangolin management.</td>
</tr>
<tr>
<td><code>PANGOLIN_ORG_ID</code></td>
<td><em>(empty)</em></td>
<td>Organization ID in Pangolin.</td>
</tr>
<tr>
<td><code>PANGOLIN_SITE_ID</code></td>
<td><em>(empty)</em></td>
<td>Site ID (populated after setup via admin GUI).</td>
</tr>
<tr>
<td><code>PANGOLIN_ENDPOINT</code></td>
<td><code>https://pangolin.bnkserve.org</code></td>
<td>Pangolin tunnel endpoint.</td>
</tr>
<tr>
<td><code>PANGOLIN_NEWT_ID</code></td>
<td><em>(empty)</em></td>
<td>Newt client ID (populated after setup).</td>
</tr>
<tr>
<td><code>PANGOLIN_NEWT_SECRET</code></td>
<td><em>(empty)</em></td>
<td>Newt client secret (populated after setup).</td>
</tr>
</tbody>
</table>
<div class="admonition tip">
<p class="admonition-title">Setup flow</p>
<p>Configure the tunnel from <strong>Admin &rarr; Settings &rarr; Pangolin</strong>. The setup wizard walks you through creating a site, copying credentials, and connecting the Newt container. See <a href="../../deployment/">Deployment</a> for the full guide.</p>
</div>
<hr />
<h2 id="monitoring">Monitoring <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span><a class="headerlink" href="#monitoring" title="Permanent link">&para;</a></h2>
<p>These services are behind the <code>monitoring</code> Docker Compose profile. Start them with:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>--profile<span class="w"> </span>monitoring<span class="w"> </span>up<span class="w"> </span>-d
</span></code></pre></div>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>PROMETHEUS_PORT</code></td>
<td><code>9090</code></td>
<td>Prometheus web UI / query port.</td>
</tr>
<tr>
<td><code>GRAFANA_PORT</code></td>
<td><code>3005</code></td>
<td>Grafana dashboard port.</td>
</tr>
<tr>
<td><code>GRAFANA_ADMIN_PASSWORD</code></td>
<td><code>admin</code></td>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span> Change in production.</td>
</tr>
<tr>
<td><code>GRAFANA_ROOT_URL</code></td>
<td><code>http://localhost:3005</code></td>
<td>Public URL for Grafana (used in links).</td>
</tr>
<tr>
<td><code>CADVISOR_PORT</code></td>
<td><code>8086</code></td>
<td>cAdvisor container metrics port.</td>
</tr>
<tr>
<td><code>NODE_EXPORTER_PORT</code></td>
<td><code>9100</code></td>
<td>Prometheus node exporter port.</td>
</tr>
<tr>
<td><code>REDIS_EXPORTER_PORT</code></td>
<td><code>9121</code></td>
<td>Redis metrics exporter port.</td>
</tr>
<tr>
<td><code>ALERTMANAGER_PORT</code></td>
<td><code>9093</code></td>
<td>Alertmanager web UI port.</td>
</tr>
<tr>
<td><code>GOTIFY_PORT</code></td>
<td><code>8889</code></td>
<td>Gotify push notification port.</td>
</tr>
<tr>
<td><code>GOTIFY_ADMIN_USER</code></td>
<td><code>admin</code></td>
<td>Gotify admin username.</td>
</tr>
<tr>
<td><code>GOTIFY_ADMIN_PASSWORD</code></td>
<td><code>admin</code></td>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6zM16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"/></svg></span> Change in production.</td>
</tr>
<tr>
<td><code>GRAFANA_EMBED_PORT</code></td>
<td><code>8894</code></td>
<td>Port for iframe embedding Grafana in admin.</td>
</tr>
<tr>
<td><code>ALERTMANAGER_EMBED_PORT</code></td>
<td><code>8895</code></td>
<td>Port for iframe embedding Alertmanager in admin.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="bunker-ops-fleet-management">Bunker Ops (Fleet Management) <span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span><a class="headerlink" href="#bunker-ops-fleet-management" title="Permanent link">&para;</a></h2>
<p>Remote metrics push for managing multiple Changemaker Lite instances from a central monitoring server.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>INSTANCE_LABEL</code></td>
<td><em>(empty)</em></td>
<td>Unique label for this instance (used as a Prometheus metric label). Falls back to <code>DOMAIN</code> if empty.</td>
</tr>
<tr>
<td><code>BUNKER_OPS_ENABLED</code></td>
<td><code>false</code></td>
<td><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 22a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3zm-1-3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57l-2.29-3.96L14 17l-5.07-5.07-3.75 6.5c-.11.16-.18.36-.18.57m8-9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg></span> Enable remote metrics push to a central VictoriaMetrics server.</td>
</tr>
<tr>
<td><code>BUNKER_OPS_REMOTE_WRITE_URL</code></td>
<td><em>(empty)</em></td>
<td>VictoriaMetrics <code>remote_write</code> endpoint (e.g., <code>https://ops.example.com/api/v1/write</code>).</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="generating-secrets">Generating Secrets<a class="headerlink" href="#generating-secrets" title="Permanent link">&para;</a></h2>
<p>Use these commands to generate all required secrets at once:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1" href="#__codelineno-4-1"></a><span class="c1"># JWT secrets (two separate values)</span>
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2" href="#__codelineno-4-2"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;JWT_ACCESS_SECRET=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-3"><a id="__codelineno-4-3" name="__codelineno-4-3" href="#__codelineno-4-3"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;JWT_REFRESH_SECRET=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-4"><a id="__codelineno-4-4" name="__codelineno-4-4" href="#__codelineno-4-4"></a>
</span><span id="__span-4-5"><a id="__codelineno-4-5" name="__codelineno-4-5" href="#__codelineno-4-5"></a><span class="c1"># Encryption key (must differ from JWT secrets)</span>
</span><span id="__span-4-6"><a id="__codelineno-4-6" name="__codelineno-4-6" href="#__codelineno-4-6"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;ENCRYPTION_KEY=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-7"><a id="__codelineno-4-7" name="__codelineno-4-7" href="#__codelineno-4-7"></a>
</span><span id="__span-4-8"><a id="__codelineno-4-8" name="__codelineno-4-8" href="#__codelineno-4-8"></a><span class="c1"># Database and Redis passwords</span>
</span><span id="__span-4-9"><a id="__codelineno-4-9" name="__codelineno-4-9" href="#__codelineno-4-9"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;V2_POSTGRES_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">24</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-10"><a id="__codelineno-4-10" name="__codelineno-4-10" href="#__codelineno-4-10"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;REDIS_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">24</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-11"><a id="__codelineno-4-11" name="__codelineno-4-11" href="#__codelineno-4-11"></a>
</span><span id="__span-4-12"><a id="__codelineno-4-12" name="__codelineno-4-12" href="#__codelineno-4-12"></a><span class="c1"># Listmonk</span>
</span><span id="__span-4-13"><a id="__codelineno-4-13" name="__codelineno-4-13" href="#__codelineno-4-13"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;LISTMONK_DB_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">24</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-14"><a id="__codelineno-4-14" name="__codelineno-4-14" href="#__codelineno-4-14"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;LISTMONK_WEB_ADMIN_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">16</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-15"><a id="__codelineno-4-15" name="__codelineno-4-15" href="#__codelineno-4-15"></a><span class="nv">LISTMONK_TOKEN</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">16</span><span class="k">)</span>
</span><span id="__span-4-16"><a id="__codelineno-4-16" name="__codelineno-4-16" href="#__codelineno-4-16"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;LISTMONK_API_TOKEN=</span><span class="nv">$LISTMONK_TOKEN</span><span class="s2">&quot;</span>
</span><span id="__span-4-17"><a id="__codelineno-4-17" name="__codelineno-4-17" href="#__codelineno-4-17"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;LISTMONK_ADMIN_PASSWORD=</span><span class="nv">$LISTMONK_TOKEN</span><span class="s2">&quot;</span>
</span><span id="__span-4-18"><a id="__codelineno-4-18" name="__codelineno-4-18" href="#__codelineno-4-18"></a>
</span><span id="__span-4-19"><a id="__codelineno-4-19" name="__codelineno-4-19" href="#__codelineno-4-19"></a><span class="c1"># Supporting services</span>
</span><span id="__span-4-20"><a id="__codelineno-4-20" name="__codelineno-4-20" href="#__codelineno-4-20"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;GITEA_DB_PASSWD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">24</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-21"><a id="__codelineno-4-21" name="__codelineno-4-21" href="#__codelineno-4-21"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;GITEA_DB_ROOT_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">24</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-22"><a id="__codelineno-4-22" name="__codelineno-4-22" href="#__codelineno-4-22"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;N8N_ENCRYPTION_KEY=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-23"><a id="__codelineno-4-23" name="__codelineno-4-23" href="#__codelineno-4-23"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;N8N_USER_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">16</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-24"><a id="__codelineno-4-24" name="__codelineno-4-24" href="#__codelineno-4-24"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;NC_ADMIN_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">16</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-25"><a id="__codelineno-4-25" name="__codelineno-4-25" href="#__codelineno-4-25"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;INITIAL_ADMIN_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-base64<span class="w"> </span><span class="m">18</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-26"><a id="__codelineno-4-26" name="__codelineno-4-26" href="#__codelineno-4-26"></a>
</span><span id="__span-4-27"><a id="__codelineno-4-27" name="__codelineno-4-27" href="#__codelineno-4-27"></a><span class="c1"># Vaultwarden</span>
</span><span id="__span-4-28"><a id="__codelineno-4-28" name="__codelineno-4-28" href="#__codelineno-4-28"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;VAULTWARDEN_ADMIN_TOKEN=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-29"><a id="__codelineno-4-29" name="__codelineno-4-29" href="#__codelineno-4-29"></a>
</span><span id="__span-4-30"><a id="__codelineno-4-30" name="__codelineno-4-30" href="#__codelineno-4-30"></a><span class="c1"># Rocket.Chat</span>
</span><span id="__span-4-31"><a id="__codelineno-4-31" name="__codelineno-4-31" href="#__codelineno-4-31"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;ROCKETCHAT_ADMIN_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">16</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-32"><a id="__codelineno-4-32" name="__codelineno-4-32" href="#__codelineno-4-32"></a>
</span><span id="__span-4-33"><a id="__codelineno-4-33" name="__codelineno-4-33" href="#__codelineno-4-33"></a><span class="c1"># Gancio</span>
</span><span id="__span-4-34"><a id="__codelineno-4-34" name="__codelineno-4-34" href="#__codelineno-4-34"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;GANCIO_ADMIN_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">16</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-35"><a id="__codelineno-4-35" name="__codelineno-4-35" href="#__codelineno-4-35"></a>
</span><span id="__span-4-36"><a id="__codelineno-4-36" name="__codelineno-4-36" href="#__codelineno-4-36"></a><span class="c1"># Jitsi Meet</span>
</span><span id="__span-4-37"><a id="__codelineno-4-37" name="__codelineno-4-37" href="#__codelineno-4-37"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;JITSI_APP_SECRET=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-38"><a id="__codelineno-4-38" name="__codelineno-4-38" href="#__codelineno-4-38"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;JITSI_JICOFO_AUTH_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">16</span><span class="k">)</span><span class="s2">&quot;</span>
</span><span id="__span-4-39"><a id="__codelineno-4-39" name="__codelineno-4-39" href="#__codelineno-4-39"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;JITSI_JVB_AUTH_PASSWORD=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">16</span><span class="k">)</span><span class="s2">&quot;</span>
</span></code></pre></div>
<div class="admonition tip">
<p class="admonition-title">Tip</p>
<p>Copy the output and paste the values into your <code>.env</code> file. The <code>INITIAL_ADMIN_PASSWORD</code> uses base64 encoding to ensure it contains uppercase, lowercase, and digits (meeting the password policy).</p>
</div>
<hr />
<h2 id="minimal-vs-full-deployment">Minimal vs Full Deployment<a class="headerlink" href="#minimal-vs-full-deployment" title="Permanent link">&para;</a></h2>
<div class="tabbed-set tabbed-alternate" data-tabs="1:2"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Minimal (Core Only)</label><label for="__tabbed_1_2">Full Stack</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
<p>For a basic deployment with campaigns, map, and admin:</p>
<div class="language-bash highlight"><span class="filename">Required variables</span><pre><span></span><code><span id="__span-5-1"><a id="__codelineno-5-1" name="__codelineno-5-1" href="#__codelineno-5-1"></a><span class="nv">V2_POSTGRES_PASSWORD</span><span class="o">=</span>...
</span><span id="__span-5-2"><a id="__codelineno-5-2" name="__codelineno-5-2" href="#__codelineno-5-2"></a><span class="nv">REDIS_PASSWORD</span><span class="o">=</span>...
</span><span id="__span-5-3"><a id="__codelineno-5-3" name="__codelineno-5-3" href="#__codelineno-5-3"></a><span class="nv">JWT_ACCESS_SECRET</span><span class="o">=</span>...
</span><span id="__span-5-4"><a id="__codelineno-5-4" name="__codelineno-5-4" href="#__codelineno-5-4"></a><span class="nv">JWT_REFRESH_SECRET</span><span class="o">=</span>...
</span><span id="__span-5-5"><a id="__codelineno-5-5" name="__codelineno-5-5" href="#__codelineno-5-5"></a><span class="nv">ENCRYPTION_KEY</span><span class="o">=</span>...
</span><span id="__span-5-6"><a id="__codelineno-5-6" name="__codelineno-5-6" href="#__codelineno-5-6"></a><span class="nv">INITIAL_ADMIN_PASSWORD</span><span class="o">=</span>...
</span></code></pre></div>
<div class="language-bash highlight"><span class="filename">Start services</span><pre><span></span><code><span id="__span-6-1"><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>up<span class="w"> </span>-d<span class="w"> </span>v2-postgres<span class="w"> </span>redis<span class="w"> </span>api<span class="w"> </span>admin
</span></code></pre></div>
</div>
<div class="tabbed-block">
<p>For the complete platform including media, newsletters, monitoring, and all services:</p>
<div class="language-bash highlight"><span class="filename">Additional variables needed</span><pre><span></span><code><span id="__span-7-1"><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a><span class="c1"># Everything above, plus:</span>
</span><span id="__span-7-2"><a id="__codelineno-7-2" name="__codelineno-7-2" href="#__codelineno-7-2"></a><span class="nv">ENABLE_MEDIA_FEATURES</span><span class="o">=</span><span class="nb">true</span>
</span><span id="__span-7-3"><a id="__codelineno-7-3" name="__codelineno-7-3" href="#__codelineno-7-3"></a><span class="nv">ENABLE_PAYMENTS</span><span class="o">=</span><span class="nb">true</span>
</span><span id="__span-7-4"><a id="__codelineno-7-4" name="__codelineno-7-4" href="#__codelineno-7-4"></a><span class="nv">ENABLE_CHAT</span><span class="o">=</span><span class="nb">true</span>
</span><span id="__span-7-5"><a id="__codelineno-7-5" name="__codelineno-7-5" href="#__codelineno-7-5"></a><span class="nv">ENABLE_MEET</span><span class="o">=</span><span class="nb">true</span>
</span><span id="__span-7-6"><a id="__codelineno-7-6" name="__codelineno-7-6" href="#__codelineno-7-6"></a><span class="nv">ENABLE_SMS</span><span class="o">=</span><span class="nb">true</span>
</span><span id="__span-7-7"><a id="__codelineno-7-7" name="__codelineno-7-7" href="#__codelineno-7-7"></a><span class="nv">LISTMONK_SYNC_ENABLED</span><span class="o">=</span><span class="nb">true</span>
</span><span id="__span-7-8"><a id="__codelineno-7-8" name="__codelineno-7-8" href="#__codelineno-7-8"></a><span class="nv">GANCIO_SYNC_ENABLED</span><span class="o">=</span><span class="nb">true</span>
</span><span id="__span-7-9"><a id="__codelineno-7-9" name="__codelineno-7-9" href="#__codelineno-7-9"></a><span class="nv">LISTMONK_DB_PASSWORD</span><span class="o">=</span>...
</span><span id="__span-7-10"><a id="__codelineno-7-10" name="__codelineno-7-10" href="#__codelineno-7-10"></a><span class="nv">LISTMONK_WEB_ADMIN_PASSWORD</span><span class="o">=</span>...
</span><span id="__span-7-11"><a id="__codelineno-7-11" name="__codelineno-7-11" href="#__codelineno-7-11"></a><span class="nv">LISTMONK_API_TOKEN</span><span class="o">=</span>...
</span><span id="__span-7-12"><a id="__codelineno-7-12" name="__codelineno-7-12" href="#__codelineno-7-12"></a><span class="nv">NC_ADMIN_PASSWORD</span><span class="o">=</span>...
</span><span id="__span-7-13"><a id="__codelineno-7-13" name="__codelineno-7-13" href="#__codelineno-7-13"></a><span class="nv">GITEA_DB_PASSWD</span><span class="o">=</span>...
</span><span id="__span-7-14"><a id="__codelineno-7-14" name="__codelineno-7-14" href="#__codelineno-7-14"></a><span class="nv">GITEA_DB_ROOT_PASSWORD</span><span class="o">=</span>...
</span><span id="__span-7-15"><a id="__codelineno-7-15" name="__codelineno-7-15" href="#__codelineno-7-15"></a><span class="nv">N8N_ENCRYPTION_KEY</span><span class="o">=</span>...
</span><span id="__span-7-16"><a id="__codelineno-7-16" name="__codelineno-7-16" href="#__codelineno-7-16"></a><span class="nv">N8N_USER_PASSWORD</span><span class="o">=</span>...
</span><span id="__span-7-17"><a id="__codelineno-7-17" name="__codelineno-7-17" href="#__codelineno-7-17"></a><span class="nv">VAULTWARDEN_ADMIN_TOKEN</span><span class="o">=</span>...
</span><span id="__span-7-18"><a id="__codelineno-7-18" name="__codelineno-7-18" href="#__codelineno-7-18"></a><span class="nv">ROCKETCHAT_ADMIN_PASSWORD</span><span class="o">=</span>...
</span><span id="__span-7-19"><a id="__codelineno-7-19" name="__codelineno-7-19" href="#__codelineno-7-19"></a><span class="nv">GANCIO_ADMIN_PASSWORD</span><span class="o">=</span>...
</span><span id="__span-7-20"><a id="__codelineno-7-20" name="__codelineno-7-20" href="#__codelineno-7-20"></a><span class="nv">JITSI_APP_SECRET</span><span class="o">=</span>...
</span><span id="__span-7-21"><a id="__codelineno-7-21" name="__codelineno-7-21" href="#__codelineno-7-21"></a><span class="nv">JITSI_JICOFO_AUTH_PASSWORD</span><span class="o">=</span>...
</span><span id="__span-7-22"><a id="__codelineno-7-22" name="__codelineno-7-22" href="#__codelineno-7-22"></a><span class="nv">JITSI_JVB_AUTH_PASSWORD</span><span class="o">=</span>...
</span><span id="__span-7-23"><a id="__codelineno-7-23" name="__codelineno-7-23" href="#__codelineno-7-23"></a><span class="nv">JVB_ADVERTISE_IP</span><span class="o">=</span>your.public.ip.here
</span><span id="__span-7-24"><a id="__codelineno-7-24" name="__codelineno-7-24" href="#__codelineno-7-24"></a><span class="nv">EMAIL_TEST_MODE</span><span class="o">=</span><span class="nb">false</span>
</span><span id="__span-7-25"><a id="__codelineno-7-25" name="__codelineno-7-25" href="#__codelineno-7-25"></a><span class="nv">SMTP_HOST</span><span class="o">=</span>smtp.your-provider.com
</span><span id="__span-7-26"><a id="__codelineno-7-26" name="__codelineno-7-26" href="#__codelineno-7-26"></a><span class="nv">SMTP_PORT</span><span class="o">=</span><span class="m">587</span>
</span><span id="__span-7-27"><a id="__codelineno-7-27" name="__codelineno-7-27" href="#__codelineno-7-27"></a><span class="nv">SMTP_USER</span><span class="o">=</span>you@example.com
</span><span id="__span-7-28"><a id="__codelineno-7-28" name="__codelineno-7-28" href="#__codelineno-7-28"></a><span class="nv">SMTP_PASS</span><span class="o">=</span>your-smtp-password
</span></code></pre></div>
<div class="language-bash highlight"><span class="filename">Start services</span><pre><span></span><code><span id="__span-8-1"><a id="__codelineno-8-1" name="__codelineno-8-1" href="#__codelineno-8-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>up<span class="w"> </span>-d
</span><span id="__span-8-2"><a id="__codelineno-8-2" name="__codelineno-8-2" href="#__codelineno-8-2"></a>docker<span class="w"> </span>compose<span class="w"> </span>--profile<span class="w"> </span>monitoring<span class="w"> </span>up<span class="w"> </span>-d
</span></code></pre></div>
</div>
</div>
</div>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<nav class="md-footer__inner md-grid" aria-label="Footer" >
<a href="../installation/" class="md-footer__link md-footer__link--prev" aria-label="Previous: Installation">
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</div>
<div class="md-footer__title">
<span class="md-footer__direction">
Previous
</span>
<div class="md-ellipsis">
Installation
</div>
</div>
</a>
<a href="../first-steps/" class="md-footer__link md-footer__link--next" aria-label="Next: First Steps">
<div class="md-footer__title">
<span class="md-footer__direction">
Next
</span>
<div class="md-ellipsis">
First Steps
</div>
</div>
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg>
</div>
</a>
</nav>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
<div class="md-copyright__highlight">
Copyright &copy; 2024 The Bunker Operations <a href="#__consent">Change cookie settings</a>
</div>
</div>
<div class="md-social">
<a href="https://gitea.bnkops.com/admin" target="_blank" rel="noopener" title="Gitea Repository" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9M252.8 8C114.1 8 8 113.3 8 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C436.2 457.8 504 362.9 504 252 504 113.3 391.5 8 252.8 8M105.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2"/></svg>
</a>
<a href="https://listmonk.bnkops.com/subscription/form" target="_blank" rel="noopener" title="Newsletter" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M536.4-26.3c9.8-3.5 20.6-1 28 6.3s9.8 18.2 6.3 28l-178 496.9c-5 13.9-18.1 23.1-32.8 23.1-14.2 0-27-8.6-32.3-21.7l-64.2-158c-4.5-11-2.5-23.6 5.2-32.6l94.5-112.4c5.1-6.1 4.7-15-.9-20.6s-14.6-6-20.6-.9l-112.4 94.3c-9.1 7.6-21.6 9.6-32.6 5.2L38.1 216.8c-13.1-5.3-21.7-18.1-21.7-32.3 0-14.7 9.2-27.8 23.1-32.8z"/></svg>
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "../../..", "features": ["announce.dismiss", "content.action.edit", "content.action.view", "content.code.annotate", "content.code.copy", "content.tooltips", "navigation.footer", "navigation.indexes", "navigation.path", "navigation.prune", "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../../assets/javascripts/bundle.79ae519e.min.js"></script>
<script src="../../../javascripts/home.js"></script>
<script src="../../../javascripts/github-widget.js"></script>
<script src="../../../javascripts/gitea-widget.js"></script>
<script src="../../../assets/js/env-config.js"></script>
<script src="../../../assets/js/video-player.js"></script>
<script src="../../../assets/js/image-gallery.js"></script>
<script src="../../../assets/js/gancio-events.js"></script>
<script src="../../../assets/js/payment-widgets.js"></script>
<script src="../../../assets/js/scheduling-poll.js"></script>
<script src="../../../javascripts/ad-widgets.js"></script>
<script src="../../../javascripts/docs-comments.js"></script>
</body>
</html>