If an agent drives your board through the local API all day — building boards, importing media, rendering video projects — you do not need the desktop window open at all. The API can run on its own, start automatically, and restart itself if it ever stops. This page explains how the app is put together, how to run the API as a background service on macOS, Windows and Linux, and how to stop it again.
This is an advanced setup. If you use the app normally — opening boards, editing in the window — you do not need any of it.
How the app is put together
GreenLight DashBoard is two programs, not one. Launching it starts the desktop shell, which then starts a second, separate program — the backend:
| Process | What it is | What it does |
|---|---|---|
| GreenLight DashBoard | The desktop shell | The window, the menus, the dock/taskbar icon, the built-in terminal |
moodboard-backend | A self-contained binary | Listens on 127.0.0.1:18000, answers every /api/… call, stores your boards, runs renders — and serves the interface itself |
That last point is the one worth remembering: the window is a browser view pointed at http://localhost:18000. The interface you look at is served by the backend, not by the desktop shell.
Two things follow from this.
- An open window is not proof that the API is alive. If the backend stops, the window keeps showing the last thing it painted while every API call fails. The app looks perfectly healthy and answers nothing.
- The backend can run without the desktop app. The app is a window around it, and that window is optional. Everything below is built on this.
One owner at a time
Never run the service while the desktop app is running. Both want the same port, the same database and the same board folders — and a board folder that is open twice goes read-only. Quit the desktop app before starting the service.
You do not lose the interface by doing this. With the service running, open http://localhost:18000 in any browser and you get the full app.
Where the pieces live
You need four locations: the backend binary, the built interface, the bundled agent skills, and your data folder. They sit in fixed places inside the installation.
macOS
RESOURCES="/Applications/GreenLight DashBoard.app/Contents/Resources"
BACKEND="$RESOURCES/backend/moodboard-backend/moodboard-backend"
DATA="$HOME/Library/Application Support/greenlight-dashboard"
Windows
The installer lets you choose the folder, so check where yours went. The per-user default is %LOCALAPPDATA%\Programs\GreenLight DashBoard; a machine-wide install lands in C:\Program Files\GreenLight DashBoard.
$Resources = "$env:LOCALAPPDATA\Programs\GreenLight DashBoard\resources"
$Backend = "$Resources\backend\moodboard-backend\moodboard-backend.exe"
$Data = "$env:APPDATA\greenlight-dashboard"
Linux (.deb)
RESOURCES="/opt/GreenLight DashBoard/resources"
BACKEND="$RESOURCES/backend/moodboard-backend/moodboard-backend"
DATA="$HOME/.config/greenlight-dashboard"
Linux (AppImage)
An AppImage keeps its files inside the image and only mounts them while the app runs, so the backend cannot be started from it directly. Extract it once and use the extracted copy:
./GreenLight-DashBoard-*.AppImage --appimage-extract
RESOURCES="$PWD/squashfs-root/resources"
BACKEND="$RESOURCES/backend/moodboard-backend/moodboard-backend"
Re-extract after every app update, or install the .deb instead, where the files sit on disk permanently.
If you moved your media folder (Options ▸ Show/Change Media Folder), use that path rather than the default when you set MEDIA_DIR below — otherwise the service starts against an empty media root and your boards look like they lost their pictures.
The environment it needs
The backend takes the port as its single command-line argument, and everything else through environment variables. These are exactly what the desktop app passes:
| Variable | Value | What it is |
|---|---|---|
MEDIA_DIR | <data>/media | Board folders, media files, renders |
DB_PATH | <data>/moodboard.db | The database |
LIBRARY_DIR | <data>/libraries | Sound, asset and music packs |
LOG_DIR | <data>/logs | Backend logs |
FRONTEND_DIR | <resources>/frontend-dist | The interface it serves |
GREENLIGHT_SKILLS_DIR | <resources>/skills | Bundled agent skills |
BOARD_AGENT_SKILL_PATH | <resources>/skills/greenlight-dash/SKILL.md | The board skill agents load |
GREENLIGHT_RENDER_BASE_URL | http://localhost:<port> | Where render jobs load their render page from |
MOCKUP_RENDER_BASE_URL | http://localhost:<port> | Same, for mockup and video renders |
ALLOWED_ORIGINS | http://localhost:<port> | Browser origin allowed to call the API |
The three URL variables must point at the port the service itself is listening on — renders load their render page over HTTP from the same backend.
Try it by hand first
Quit the desktop app, then run the backend directly. macOS and Linux:
RESOURCES="/Applications/GreenLight DashBoard.app/Contents/Resources" # Linux: /opt/GreenLight DashBoard/resources
DATA="$HOME/Library/Application Support/greenlight-dashboard" # Linux: $HOME/.config/greenlight-dashboard
PORT=18000
export MEDIA_DIR="$DATA/media"
export DB_PATH="$DATA/moodboard.db"
export LIBRARY_DIR="$DATA/libraries"
export LOG_DIR="$DATA/logs"
export FRONTEND_DIR="$RESOURCES/frontend-dist"
export GREENLIGHT_SKILLS_DIR="$RESOURCES/skills"
export BOARD_AGENT_SKILL_PATH="$RESOURCES/skills/greenlight-dash/SKILL.md"
export GREENLIGHT_RENDER_BASE_URL="http://localhost:$PORT"
export MOCKUP_RENDER_BASE_URL="http://localhost:$PORT"
export ALLOWED_ORIGINS="http://localhost:$PORT"
"$RESOURCES/backend/moodboard-backend/moodboard-backend" "$PORT"
Windows PowerShell:
$Resources = "$env:LOCALAPPDATA\Programs\GreenLight DashBoard\resources"
$Data = "$env:APPDATA\greenlight-dashboard"
$Port = 18000
$env:MEDIA_DIR = "$Data\media"
$env:DB_PATH = "$Data\moodboard.db"
$env:LIBRARY_DIR = "$Data\libraries"
$env:LOG_DIR = "$Data\logs"
$env:FRONTEND_DIR = "$Resources\frontend-dist"
$env:GREENLIGHT_SKILLS_DIR = "$Resources\skills"
$env:BOARD_AGENT_SKILL_PATH = "$Resources\skills\greenlight-dash\SKILL.md"
$env:GREENLIGHT_RENDER_BASE_URL = "http://localhost:$Port"
$env:MOCKUP_RENDER_BASE_URL = "http://localhost:$Port"
$env:ALLOWED_ORIGINS = "http://localhost:$Port"
& "$Resources\backend\moodboard-backend\moodboard-backend.exe" $Port
It should be answering within a second or two. Check it from another terminal:
curl http://127.0.0.1:18000/api/moodboards
Then open http://localhost:18000 in a browser — the whole interface is there. Press Ctrl+C in the terminal to stop it again.
Running it as a service
Running it by hand works, but nothing brings it back if it stops. Each system has a built-in supervisor that starts the backend at login and restarts it if it ever exits.
macOS — launchd
Save this as ~/Library/LaunchAgents/pro.greenlightdash.backend.plist, with your own paths filled in:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>pro.greenlightdash.backend</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/GreenLight DashBoard.app/Contents/Resources/backend/moodboard-backend/moodboard-backend</string>
<string>18000</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>MEDIA_DIR</key><string>/Users/you/Library/Application Support/greenlight-dashboard/media</string>
<key>DB_PATH</key><string>/Users/you/Library/Application Support/greenlight-dashboard/moodboard.db</string>
<key>LIBRARY_DIR</key><string>/Users/you/Library/Application Support/greenlight-dashboard/libraries</string>
<key>LOG_DIR</key><string>/Users/you/Library/Application Support/greenlight-dashboard/logs</string>
<key>FRONTEND_DIR</key><string>/Applications/GreenLight DashBoard.app/Contents/Resources/frontend-dist</string>
<key>GREENLIGHT_SKILLS_DIR</key><string>/Applications/GreenLight DashBoard.app/Contents/Resources/skills</string>
<key>BOARD_AGENT_SKILL_PATH</key><string>/Applications/GreenLight DashBoard.app/Contents/Resources/skills/greenlight-dash/SKILL.md</string>
<key>GREENLIGHT_RENDER_BASE_URL</key><string>http://localhost:18000</string>
<key>MOCKUP_RENDER_BASE_URL</key><string>http://localhost:18000</string>
<key>ALLOWED_ORIGINS</key><string>http://localhost:18000</string>
<key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>ThrottleInterval</key><integer>10</integer>
<key>ProcessType</key><string>Interactive</string>
<key>StandardOutPath</key><string>/Users/you/Library/Application Support/greenlight-dashboard/logs/backend-service.log</string>
<key>StandardErrorPath</key><string>/Users/you/Library/Application Support/greenlight-dashboard/logs/backend-service.log</string>
</dict>
</plist>
Then:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/pro.greenlightdash.backend.plist # start
launchctl bootout gui/$(id -u)/pro.greenlightdash.backend # stop
launchctl kickstart -k gui/$(id -u)/pro.greenlightdash.backend # restart
launchctl print gui/$(id -u)/pro.greenlightdash.backend | grep -E 'state|pid' # status
ProcessType Interactive keeps macOS from throttling it as a background job, which matters while renders run. KeepAlive is what restarts it after a crash — which also means a plain kill gets undone about ten seconds later. Use bootout to stop it for real.
Linux — systemd user service
Save as ~/.config/systemd/user/greenlight-backend.service:
[Unit]
Description=GreenLight DashBoard backend
After=network.target
[Service]
Type=simple
ExecStart="/opt/GreenLight DashBoard/resources/backend/moodboard-backend/moodboard-backend" 18000
Environment=MEDIA_DIR=%h/.config/greenlight-dashboard/media
Environment=DB_PATH=%h/.config/greenlight-dashboard/moodboard.db
Environment=LIBRARY_DIR=%h/.config/greenlight-dashboard/libraries
Environment=LOG_DIR=%h/.config/greenlight-dashboard/logs
Environment="FRONTEND_DIR=/opt/GreenLight DashBoard/resources/frontend-dist"
Environment="GREENLIGHT_SKILLS_DIR=/opt/GreenLight DashBoard/resources/skills"
Environment="BOARD_AGENT_SKILL_PATH=/opt/GreenLight DashBoard/resources/skills/greenlight-dash/SKILL.md"
Environment=GREENLIGHT_RENDER_BASE_URL=http://localhost:18000
Environment=MOCKUP_RENDER_BASE_URL=http://localhost:18000
Environment=ALLOWED_ORIGINS=http://localhost:18000
Restart=always
RestartSec=10
KillSignal=SIGTERM
TimeoutStopSec=30
[Install]
WantedBy=default.target
Then:
systemctl --user daemon-reload
systemctl --user enable --now greenlight-backend # start now + at login
systemctl --user stop greenlight-backend # stop
systemctl --user restart greenlight-backend # restart
systemctl --user status greenlight-backend # status
journalctl --user -u greenlight-backend -f # follow the log
loginctl enable-linger $USER # keep it running when you are logged out
Paths with spaces must be quoted as a whole in Environment= lines, exactly as shown. If you run from an extracted AppImage, point ExecStart and the three resource paths at your squashfs-root/resources instead.
Windows — Task Scheduler
Windows has no per-user service manager, so use a small wrapper script plus a scheduled task. Save the wrapper as C:\GreenLight\start-backend.ps1:
$Resources = "$env:LOCALAPPDATA\Programs\GreenLight DashBoard\resources"
$Data = "$env:APPDATA\greenlight-dashboard"
$Port = 18000
$env:MEDIA_DIR = "$Data\media"
$env:DB_PATH = "$Data\moodboard.db"
$env:LIBRARY_DIR = "$Data\libraries"
$env:LOG_DIR = "$Data\logs"
$env:FRONTEND_DIR = "$Resources\frontend-dist"
$env:GREENLIGHT_SKILLS_DIR = "$Resources\skills"
$env:BOARD_AGENT_SKILL_PATH = "$Resources\skills\greenlight-dash\SKILL.md"
$env:GREENLIGHT_RENDER_BASE_URL = "http://localhost:$Port"
$env:MOCKUP_RENDER_BASE_URL = "http://localhost:$Port"
$env:ALLOWED_ORIGINS = "http://localhost:$Port"
# restart the backend whenever it exits
while ($true) {
& "$Resources\backend\moodboard-backend\moodboard-backend.exe" $Port
Start-Sleep -Seconds 10
}
Register it to run at logon:
schtasks /Create /TN "GreenLight Backend" /SC ONLOGON /RL HIGHEST ^
/TR "powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File C:\GreenLight\start-backend.ps1"
schtasks /Run /TN "GreenLight Backend" :: start now
schtasks /End /TN "GreenLight Backend" :: stop the wrapper
schtasks /Query /TN "GreenLight Backend" :: status
Ending the task stops the wrapper loop but can leave the backend itself running, so follow it with Stop-Process -Name moodboard-backend in PowerShell. If you prefer a real Windows service, a service wrapper such as NSSM can run the same executable with the same environment.
Stopping it properly
On a clean stop the backend flushes every pending board file to disk and releases its board-folder locks. A forced kill skips both: you can lose the last few seconds of changes, and a stale lock file is left behind. Always try the graceful stop first.
| Situation | Do this |
|---|---|
| Running in a terminal | Ctrl+C |
| macOS service | launchctl bootout gui/$(id -u)/pro.greenlightdash.backend |
| Linux service | systemctl --user stop greenlight-backend |
| Windows task | schtasks /End /TN "GreenLight Backend" then Stop-Process -Name moodboard-backend |
| Started by hand, macOS/Linux | kill <pid> — never kill -9 unless it refuses to exit |
| Started by hand, Windows | taskkill /PID <pid>, adding /F only if that does not work |
Renders run a headless browser as a child of the backend. A graceful stop lets it close; a forced kill during a render can leave an orphaned browser process behind.
Checking that it is actually running
The important habit: check the listener, not the app. A visible window proves nothing about the API.
# macOS / Linux
lsof -nP -iTCP:18000 -sTCP:LISTEN
ss -ltnp | grep 18000
:: Windows
netstat -ano | findstr :18000
Or just ask the API:
curl http://127.0.0.1:18000/api/moodboards
If that returns a list, everything downstream — agents, renders, the interface in your browser — will work. If it fails while the app window is open, the backend has stopped and only a restart brings it back.
Renders need a browser
Video, mockup and template renders are performed in a headless Chromium that the backend launches. The app ships the automation library but not the browser itself, so the machine needs one of Google Chrome, Microsoft Edge or Chromium available — on a bare Linux box, sudo apt install chromium or sudo snap install chromium. If your browser lives somewhere unusual, point PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH at it. This requirement is the same for the desktop app; running headlessly does not add it.
Pointing your agent at it
Inside the desktop app the port is assigned at launch (18000 when it is free) and injected into every terminal the app opens as GREENLIGHT_API_BASE_URL, which is what agents are told to read. A service you run yourself has no such luxury and no such problem: you chose the port, it never changes, and an agent can rely on it. Set the same variable for your agent so the bundled skill keeps working unmodified:
export GREENLIGHT_API_BASE_URL=http://localhost:18000
See Using the Board with Hermes, OpenClaw and External Agent systems for what to hand an agent once the API is reachable.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
| The service will not start, or exits immediately | Something already holds the port — usually the desktop app. Quit it, or run the service on a different port (remember to change the three URL variables too). |
Reads work, but every write returns 423 | Two backends have the same board folder open, so it went read-only. Make sure only one is running; the lock lives at <board folder>/.database/lock.json. |
| The agent gets “connection refused” while the app window is open | The backend has stopped. Check the listener as above and restart the service; the window will keep looking normal regardless. |
| Boards open, but every card has lost its media | MEDIA_DIR points somewhere other than your real media folder. Check Options ▸ Show/Change Media Folder in the app. |
| Renders fail with “could not launch a browser” | No Chrome, Edge or Chromium on the machine — install one, or set PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH. |
Backend logs are written to your LOG_DIR, and the service files above add a backend-service.log next to them with everything the process printed.
Leave a Reply