Triangle CMS (Delta) is the Drexel Triangle's solution for a minimal WordPress replacement. Its core is a headless CMS (Content Management System) that serves and edits content from a local database. WordPress content can be migrated into Delta via the wordpress-etl tool. Delta also offers a minimal frontend similar to WordPress, and because the core CMS is headless, users are welcome to build their own. Delta also includes built-in logging, accessible through a Grafana dashboard, to enable easy monitoring of API calls and internal errors.
Triangle CMS is split into:
server/: Go backendfrontend/: React frontendobservability/: Loki + Promtail + Grafana config for logging purposesscripts/: setup scripts
API specification and response/data model documentation live in the project wiki:
- https://github.com/DrexelTriangle/triangle-cms/wiki
- Endpoints: https://github.com/DrexelTriangle/triangle-cms/wiki/Endpoints
- Response shapes/models: https://github.com/DrexelTriangle/triangle-cms/wiki/Response-Shapes
- Docker and Docker Compose
- Go 1.24+
- Node.js 20+ and npm
- Local clone of
wordpress-etl: https://github.com/DrexelTriangle/wordpress-etl
- Run the WordPress ETL pipeline locally in
wordpress-etlto generate source SQL files. - From
triangle-cmsrepo root, generate CMS SQL files:
./scripts/generate_wordpress_sql.shBy default, the script auto-detects ETL SQL if the wordpress-etl repository is in the same directory or the parent directory of triangle-cms:
../wordpress-etl/logs/sql./wordpress-etl/logs/sql
- Start only MariaDB in Docker (recommended for local backend development):
docker compose up -d mariadbThis starts:
- MariaDB (
mariadb) onlocalhost:${MARIADB_PORT_FORWARD:-3306}(container port3306), populated from the ETL pipeline output
If you want the full Docker stack (CMS + observability), run:
./scripts/setup-containers.shThis starts:
- MariaDB (
mariadb) onlocalhost:${MARIADB_PORT_FORWARD:-3306}(container port3306), populated from the ETL pipeline output - CMS backend (
cms) onhttps://localhost:8080which exposes the API - Promtail (
promtail) which collects logs from Docker containers - Loki (
loki) on port3100, which indexes logs from Promtail - Grafana (
http://localhost:3000, defaultUser:admin, Password:admin) which allows you to explore and query logs from Loki
Important:
- Logging/observability (Promtail, Loki, Grafana) is only available when running the full Docker stack.
- If you run the backend locally via
go run(instead of thecmsDocker container), those Docker logging pipelines do not apply to your local process.
Reset all compose volumes and rebuild:
Warning
Running this command will erase all logs and database entries, equivalent to a fresh install
./scripts/setup-containers.sh --reset-dataUse this flow when you want a full local refresh of DB + API.
- (Optional, one-time) install local embeddings dependency:
python3 -m pip install --user sentence-transformers-
Run the WordPress ETL pipeline in your local
wordpress-etlrepo so fresh source data exists. -
Generate CMS SQL files:
Without embeddings:
./scripts/generate_wordpress_sql.shWith embeddings (default-off feature):
./scripts/generate_wordpress_sql.sh --generate-embeddings- Recreate containers and volumes so MariaDB re-runs init SQL (
01..05):
Warning
Running this command erases DB data and logs.
./scripts/setup-containers.sh --reset-data- Choose how to run backend:
- Docker backend already running from step 4:
https://localhost:8080 - Or run Go API locally:
docker compose stop cms
cd server
go run ./main.go- Verify endpoint behavior:
curl -s https://localhost:8080/v1/articles/christmasIf embeddings were generated and vector infra exists, related should contain up to 3 article overviews.
If embeddings were not generated, related safely falls back to [].
- Ensure MariaDB is running (via Docker or local instance).
- If using Docker MariaDB and running the backend via
go run, configureserver/.envwith the same values used by your composeMARIADB_DATABASE,MARIADB_USER, andMARIADB_PASSWORD:
DB_NAME=triangle
DB_USER=triangle_user
DB_PASSWORD=triangle_password
DB_HOST=127.0.0.1
DB_PORT=3306If using a separate local MariaDB instance, configure server/.env for that instance instead.
- If the Docker
cmsservice is running, stop it first to avoid port conflict on:8080:
docker compose stop cms- Run backend:
cd server
go run ./main.goThe backend serves HTTPS on https://localhost:8080 using (the certs are just there to keep Postman happy):
server/certs/localhost.crtserver/certs/localhost.key
When you change Go backend code under server/ and want those changes reflected in the Docker cms container, rebuild and restart that service image:
docker compose up -d --build cmsIf your change is to MariaDB bootstrap SQL files in server/internal/database/wordpress_etl/, those are only applied on fresh DB initialization. Recreate volumes for those to take effect:
Warning
Running this command will erase all logs and database entries, equivalent to a fresh install
./scripts/setup-containers.sh --reset-datacd frontend
npm install
npm run devVite dev server starts on http://localhost:5173.
SQL files for bootstrap imports live in:
server/internal/database/wordpress_etl/
Before running this step, you must have already run the ETL pipeline in:
Generate CMS SQL files from the ETL pipeline output:
./scripts/generate_wordpress_sql.sh [source_sql_dir] [output_dir]Optional embeddings generation (off by default):
./scripts/generate_wordpress_sql.sh --generate-embeddingsDefaults:
- source: auto-detected (
../wordpress-etl,../wordpress-etl/logs/sql,./wordpress-etl,./wordpress-etl/logs/sql) - output: default (
server/internal/database/wordpress_etl) - embeddings: disabled unless
--generate-embeddingsis passed
Overrides:
- Pass explicit args:
./scripts/generate_wordpress_sql.sh ../wordpress-etl/logs/sql server/internal/database/wordpress_etl - Or use env vars:
WP_ETL_SQL_DIR=../wordpress-etl ./scripts/generate_wordpress_sql.shWP_ETL_OUT_DIR=server/internal/database/wordpress_etl ./scripts/generate_wordpress_sql.shWP_ETL_SQL_DIR=../wordpress-etl WP_ETL_OUT_DIR=server/internal/database/wordpress_etl ./scripts/generate_wordpress_sql.shWP_EMBED_MODEL=sentence-transformers/paraphrase-MiniLM-L3-v2 ./scripts/generate_wordpress_sql.sh --generate-embeddingsWP_EMBED_BATCH_SIZE=64 ./scripts/generate_wordpress_sql.sh --generate-embeddings
Generated files:
01-authors.sql02-articles.sql03-articles-authors.sql04-seo.sql05-article-embeddings.sql(only populated when embeddings are enabled)06-taxonomy.sql(canonical section/subsection titles)
These are mounted into MariaDB init at container startup through docker-compose.yml.
Backend tests:
cd server
go test ./...Test coverage:
Run to see the percentage of code each test covers
cd server
go test -coverprofile=cover.out ./...This also generates the cover.out file, showing exactly which lines are run during tests.