Add video card insert feature + MkDocs video hydration + fixes
- New video card block for GrapesJS landing pages, email templates, MkDocs export, and documentation editor Insert dropdown - Shared HTML generators in admin/src/utils/videoCardHtml.ts - MkDocs video-player.js hydrates .video-card-block elements: thumbnail fix via MEDIA_API_URL, click-to-play inline, Gallery link - Media API CORS: auto-add MkDocs + docs subdomain origins - env_config_hook.py: smart Docker hostname detection, ADMIN_PORT resolution, pass env vars to MkDocs container - Gallery URL uses /gallery?expanded=ID format - VideoPickerModal: fix double /api prefix and Docker hostname thumbs - Seed: default-video-card PageBlock - Remove V1 legacy code (influence/, map/) Bunker Admin
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 68 KiB |
BIN
mkdocs/.cache/plugin/social/assets/images/social/docs/index.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
@@ -8,7 +8,7 @@
|
||||
"assets/images/social/blog/2025/08/01/3.png": "332b80224e75bda48c92439ad6354e7ffcab52e1",
|
||||
"assets/images/social/blog/2025/09/24/4.png": "840234a707ac182ce6b89203c658b312d03df58e",
|
||||
"assets/images/social/blog/archive/2025.png": "08cbed159d450158ab4d79807f37adcda08bce39",
|
||||
"assets/images/social/blog/index.png": "7a5388a2a7e752cce942be0455d9fbf7fa816d12",
|
||||
"assets/images/social/blog/index.png": "b6fe388cb026572dfa0f42b9a6833e9fa9f95b9e",
|
||||
"assets/images/social/build/index.png": "30e75040da55694ca6485d51a35fb4d19a26b408",
|
||||
"assets/images/social/build/influence.png": "2961db1abb5f36d53086bf2bde9dfe19f1487809",
|
||||
"assets/images/social/build/map.png": "ef5bf7b7e7c3e0e527d66b3d3292918de78b7198",
|
||||
@@ -19,6 +19,17 @@
|
||||
"assets/images/social/config/index.png": "1903ccb7f8af2454c97b8a70059119c1527826fb",
|
||||
"assets/images/social/config/map.png": "322276b5f574c461e2cafc4c5c8e8735bed2d25e",
|
||||
"assets/images/social/config/mkdocs.png": "b23831844fca01c49624fa378a20453daae66604",
|
||||
"assets/images/social/docs/admin/index.png": "fca983b7d9dc1cbac915d8369f2d6354e3902fa5",
|
||||
"assets/images/social/docs/api/index.png": "6d589b655cd1681d187337b6c46275d9c4758361",
|
||||
"assets/images/social/docs/architecture/index.png": "480bf36086518125519527af26a31c3be8b1d04c",
|
||||
"assets/images/social/docs/deployment/index.png": "2ef61cacc08a8a0f6bc94308936ec7731fe5938a",
|
||||
"assets/images/social/docs/features/index.png": "f092b70692e778c4f570fb7942113ceb36f5110c",
|
||||
"assets/images/social/docs/getting-started/environment-variables.png": "a2ac6ca4cb56f9697fc7fd25f9cca6f1aa83a58b",
|
||||
"assets/images/social/docs/getting-started/index.png": "c38af587f205180bab6232e517438de6db89fc88",
|
||||
"assets/images/social/docs/index.png": "473afed8e6ed44768b1a64ad90c4a8595667a8f3",
|
||||
"assets/images/social/docs/services/index.png": "9fcf00324266a9f7b58c7b277da2127e8882aa47",
|
||||
"assets/images/social/docs/troubleshooting/index.png": "b0ffadb8b01b261dfe7dea1b55fe02a3d639b7e7",
|
||||
"assets/images/social/docs/volunteer/index.png": "a38ac1baf53fc19a77832926d14d56b92bbc5662",
|
||||
"assets/images/social/how%20to/canvass.png": "83854d6765f418b3cd83651cd8828034d5a5027d",
|
||||
"assets/images/social/index.png": "e78b3d8cfb2c7529a587def60aaa7e158e0fb176",
|
||||
"assets/images/social/lander.png": "6ca837e423f4f2e6f786243bddefc3e55ced0818",
|
||||
@@ -40,7 +51,7 @@
|
||||
"assets/images/social/services/postgresql.png": "831fb68dd3e01d9a017e59b100aaa8a455c8c112",
|
||||
"assets/images/social/services/static-server.png": "f36d527c80adba4bcb7778784683f429acb4ce74",
|
||||
"assets/images/social/test-2.png": "a6ae43d52d7c58fc106a562777e03b7da2263f83",
|
||||
"assets/images/social/test.png": "18d63169d742e6321dc4bb2988b8c5de61e79a28",
|
||||
"assets/images/social/test.png": "a6ae43d52d7c58fc106a562777e03b7da2263f83",
|
||||
"assets/images/social/v1/adv/ansible.png": "cb542ad9a3cc9a869258b3b1353966e1b9616a2b",
|
||||
"assets/images/social/v1/adv/index.png": "faa3ec092003114c031995ba6258c4d43f4262a4",
|
||||
"assets/images/social/v1/adv/vscode-ssh.png": "7c88c30c6bfb74736a308407d1a3b7cf3381d42b",
|
||||
|
||||
@@ -1,431 +0,0 @@
|
||||
# Changemaker Lite V2 - Admin Documentation Validation Report
|
||||
|
||||
**Report Date:** 2026-02-12
|
||||
**Documentation Path:** `/home/bunker-admin/changemaker.lite/mkdocs/docs/v2/frontend/pages/admin/`
|
||||
**Validator:** Claude Sonnet 4.5
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
### Strengths
|
||||
|
||||
- ✅ **29 out of 30** expected admin pages documented
|
||||
- ✅ **28,457 total lines** of comprehensive documentation
|
||||
- ✅ **97% structural compliance** (28/29 files have all required sections)
|
||||
- ✅ **Excellent code example coverage** (average 59 code blocks per file)
|
||||
- ✅ **Consistent formatting** and structure across all files
|
||||
- ✅ **Strong API integration documentation** (average 6 endpoints per file)
|
||||
|
||||
### Areas for Improvement
|
||||
|
||||
- ⚠️ **1 missing file:** page-editor-page.md
|
||||
- ⚠️ **14 files** have broken cross-reference links
|
||||
- ⚠️ **6 service pages** lack response examples for API endpoints
|
||||
- ⚠️ **1 file** (email-queue-page.md) has untagged code blocks
|
||||
- ⚠️ **1 file** (dashboard-page.md) missing Troubleshooting section
|
||||
|
||||
### Critical Issues
|
||||
|
||||
- ❌ **None** (all critical sections present in 96.5% of files)
|
||||
|
||||
---
|
||||
|
||||
## 1. File Existence Analysis
|
||||
|
||||
**Total Files Expected:** 30
|
||||
**Total Files Found:** 29
|
||||
**Missing Files:** 1
|
||||
|
||||
### Documented Files (29)
|
||||
|
||||
- users-page.md
|
||||
- dashboard-page.md
|
||||
- settings-page.md
|
||||
- campaigns-page.md
|
||||
- responses-page.md
|
||||
- representatives-page.md
|
||||
- email-queue-page.md
|
||||
- locations-page.md
|
||||
- cuts-page.md
|
||||
- map-settings-page.md
|
||||
- shifts-page.md
|
||||
- canvass-dashboard-page.md
|
||||
- landing-pages-page.md
|
||||
- email-templates-page.md
|
||||
- email-template-editor-page.md
|
||||
- listmonk-page.md
|
||||
- pangolin-page.md
|
||||
- docs-page.md
|
||||
- observability-page.md
|
||||
- data-quality-dashboard-page.md
|
||||
- mkdocs-settings-page.md
|
||||
- mini-qr-page.md
|
||||
- walk-sheet-page.md
|
||||
- cut-export-page.md
|
||||
- mailhog-page.md
|
||||
- code-editor-page.md
|
||||
- nocodb-page.md
|
||||
- n8n-page.md
|
||||
- gitea-page.md
|
||||
|
||||
### Missing Files (1)
|
||||
|
||||
- ❌ **page-editor-page.md**
|
||||
- Referenced by: landing-pages-page.md, email-template-editor-page.md
|
||||
- Source file exists: `admin/src/pages/PageEditorPage.tsx`
|
||||
- Impact: **Medium** (breaks 2 internal links, important editor page)
|
||||
|
||||
---
|
||||
|
||||
## 2. Structural Validation
|
||||
|
||||
**Files with Complete Structure:** 28/29 (96.5%)
|
||||
**Files with Missing Sections:** 1/29 (3.5%)
|
||||
|
||||
### Required Sections Checklist
|
||||
|
||||
- ✅ ## Overview
|
||||
- ✅ ## Features
|
||||
- ✅ ## User Workflow
|
||||
- ✅ ## Component Structure/Breakdown
|
||||
- ✅ ## API Integration
|
||||
- ✅ ## Troubleshooting
|
||||
- ✅ ## Related Documentation
|
||||
|
||||
### Structural Issues
|
||||
|
||||
- ⚠️ **dashboard-page.md:** Missing ## Troubleshooting section (minor impact)
|
||||
|
||||
---
|
||||
|
||||
## 3. Content Quality Metrics
|
||||
|
||||
### Documentation Volume
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Total Lines | 28,457 |
|
||||
| Average Lines per File | 981 |
|
||||
| Largest File | mkdocs-settings-page.md (1,442 lines) |
|
||||
| Smallest File | gitea-page.md (120 lines) |
|
||||
|
||||
### Code Examples
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Total Code Blocks | 1,730 |
|
||||
| Average per File | 59 blocks |
|
||||
| Files with 100+ blocks | 3 files |
|
||||
|
||||
**Files with 100+ blocks:**
|
||||
- landing-pages-page.md (110)
|
||||
- listmonk-page.md (112)
|
||||
- canvass-dashboard-page.md (102)
|
||||
|
||||
### API Endpoint Coverage
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Total Endpoints Documented | ~185 |
|
||||
| Average per File | 6 endpoints |
|
||||
| Best Coverage | locations-page.md (21 endpoints) |
|
||||
|
||||
### Role-Based Access Documentation
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Files mentioning RBAC | 17/29 (58.6%) |
|
||||
| Files mentioning specific roles | 14/29 (48.3%) |
|
||||
|
||||
### Route Path Documentation
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Files with route paths | 28/29 (96.5%) |
|
||||
| Average route paths per file | 2.4 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Code Block Quality
|
||||
|
||||
**Well-Formatted Files:** 28/29
|
||||
|
||||
### Issues Found
|
||||
|
||||
- ⚠️ **email-queue-page.md:** 5 code blocks without language tags
|
||||
- Should specify: `typescript`, `json`, `bash`, etc.
|
||||
|
||||
**Recommendation:** Add language identifiers to improve syntax highlighting and readability
|
||||
|
||||
---
|
||||
|
||||
## 5. API Documentation Quality
|
||||
|
||||
**Files with Comprehensive API Docs:** 23/29 (79.3%)
|
||||
(Including method, path, request/response examples)
|
||||
|
||||
### Service Pages Lacking Response Examples (6)
|
||||
|
||||
These files document API endpoints but don't show response formats:
|
||||
|
||||
- code-editor-page.md
|
||||
- email-queue-page.md
|
||||
- gitea-page.md
|
||||
- mailhog-page.md
|
||||
- n8n-page.md
|
||||
- nocodb-page.md
|
||||
|
||||
**Impact:** Low-Medium
|
||||
Service pages primarily document iframe integration, not direct API usage. However, response examples would improve completeness.
|
||||
|
||||
---
|
||||
|
||||
## 6. Cross-Reference Integrity
|
||||
|
||||
**Files with Broken Links:** 14/29 (48.3%)
|
||||
|
||||
### Categories of Broken Links
|
||||
|
||||
1. Backend module references (`api-reference/*`, `backend/modules/*`)
|
||||
2. Feature documentation (`features/*`)
|
||||
3. User guide references (`user-guides/*`)
|
||||
4. Deployment guides (`deployment/*`)
|
||||
5. Troubleshooting guides (`troubleshooting/*`)
|
||||
|
||||
### Most Impacted Files
|
||||
|
||||
| File | Broken Links |
|
||||
|------|--------------|
|
||||
| mkdocs-settings-page.md | 16 |
|
||||
| observability-page.md | 16 |
|
||||
| cut-export-page.md | 12 |
|
||||
| mini-qr-page.md | 10 |
|
||||
|
||||
### Root Cause
|
||||
|
||||
Links reference documentation that doesn't exist yet in these directories:
|
||||
|
||||
- `v2/backend/`
|
||||
- `v2/features/`
|
||||
- `v2/api-reference/`
|
||||
- `v2/user-guides/`
|
||||
- `v2/deployment/`
|
||||
- `v2/troubleshooting/`
|
||||
|
||||
### Recommendation
|
||||
|
||||
Either:
|
||||
- a) Create the referenced documentation files
|
||||
- b) Update links to point to existing documentation
|
||||
- c) Remove forward references and mark as "Coming Soon"
|
||||
|
||||
---
|
||||
|
||||
## 7. File Size Distribution
|
||||
|
||||
### Large Files (1000+ lines): 11 files
|
||||
|
||||
- mkdocs-settings-page.md (1,442 lines)
|
||||
- landing-pages-page.md (1,262 lines)
|
||||
- representatives-page.md (1,218 lines)
|
||||
- cuts-page.md (1,191 lines)
|
||||
- map-settings-page.md (1,093 lines)
|
||||
- canvass-dashboard-page.md (1,085 lines)
|
||||
- listmonk-page.md (1,076 lines)
|
||||
- locations-page.md (1,033 lines)
|
||||
- email-queue-page.md (749 lines)
|
||||
- email-templates-page.md (639 lines)
|
||||
- email-template-editor-page.md (626 lines)
|
||||
|
||||
### Medium Files (400-999 lines): 12 files
|
||||
|
||||
### Small Files (<400 lines): 6 files
|
||||
|
||||
- gitea-page.md (120 lines)
|
||||
- nocodb-page.md (190 lines)
|
||||
- dashboard-page.md (194 lines)
|
||||
- n8n-page.md (253 lines)
|
||||
- code-editor-page.md (261 lines)
|
||||
- settings-page.md (403 lines)
|
||||
|
||||
**Assessment:** Good balance between comprehensive coverage and maintainability. Smaller service pages are appropriately brief.
|
||||
|
||||
---
|
||||
|
||||
## 8. Consistency Analysis
|
||||
|
||||
### Consistent Patterns ✅
|
||||
|
||||
- All files follow H2 (`##`) section structure
|
||||
- All files include TypeScript code examples
|
||||
- All files document admin routes at `/app/*`
|
||||
- All files include related documentation links
|
||||
- All files use consistent terminology
|
||||
|
||||
### Component Documentation Style ✅
|
||||
|
||||
- Props tables with descriptions
|
||||
- State management patterns
|
||||
- Event handlers documented
|
||||
- API integration patterns clear
|
||||
|
||||
### API Documentation Style ✅
|
||||
|
||||
- HTTP method + endpoint path
|
||||
- Request parameters documented
|
||||
- Most include response examples
|
||||
- Error cases covered
|
||||
|
||||
---
|
||||
|
||||
## 9. Additional Insights from Deep Validation
|
||||
|
||||
### All 29 Files Have Proper H1 Titles ✅
|
||||
|
||||
- Every file starts with a descriptive H1 heading
|
||||
- Titles follow consistent naming convention (e.g., "CampaignsPage", "UsersPage")
|
||||
|
||||
### Excellent Content Depth ✅
|
||||
|
||||
| Element | Count |
|
||||
|---------|-------|
|
||||
| State Management references (useState/useEffect/Zustand) | 380+ |
|
||||
| Error Handling patterns | 87+ |
|
||||
| JSON response examples | 126+ |
|
||||
| TypeScript import statements | Shown in code examples |
|
||||
|
||||
### Comprehensive Workflow Documentation ✅
|
||||
|
||||
Sample file workflow step counts:
|
||||
|
||||
| File | Numbered Steps |
|
||||
|------|----------------|
|
||||
| locations-page.md | 145 |
|
||||
| landing-pages-page.md | 108 |
|
||||
| campaigns-page.md | 59 |
|
||||
| canvass-dashboard-page.md | 47 |
|
||||
| users-page.md | 33 |
|
||||
|
||||
### Rich Data Visualization ✅
|
||||
|
||||
Extensive use of tables for structured data:
|
||||
|
||||
| File | Table Rows |
|
||||
|------|------------|
|
||||
| locations-page.md | 22 |
|
||||
| users-page.md | 14 |
|
||||
| landing-pages-page.md | 9 |
|
||||
| canvass-dashboard-page.md | 8 |
|
||||
|
||||
### Clear Access Control Documentation ✅
|
||||
|
||||
All major files document:
|
||||
- Required authentication
|
||||
- Specific role requirements (SUPER_ADMIN, MAP_ADMIN, INFLUENCE_ADMIN)
|
||||
- Route paths (`/app/*`)
|
||||
|
||||
---
|
||||
|
||||
## 10. Recommendations
|
||||
|
||||
### Priority 1 (HIGH)
|
||||
|
||||
- [ ] **Create page-editor-page.md documentation**
|
||||
- File exists: `admin/src/pages/PageEditorPage.tsx`
|
||||
- Referenced by 2 other pages
|
||||
- Important for landing page workflow
|
||||
|
||||
### Priority 2 (MEDIUM)
|
||||
|
||||
- [ ] **Fix cross-reference links** (14 files affected)
|
||||
- Options: Create referenced docs, update links, or comment out forward references
|
||||
|
||||
- [ ] **Add response examples to 6 service pages**
|
||||
- Improves API documentation completeness
|
||||
- Helps developers understand integration
|
||||
|
||||
### Priority 3 (LOW)
|
||||
|
||||
- [ ] **Add Troubleshooting section to dashboard-page.md**
|
||||
- For consistency with other pages
|
||||
|
||||
- [ ] **Add language tags to 5 code blocks in email-queue-page.md**
|
||||
- Improves syntax highlighting
|
||||
|
||||
- [ ] **Consider splitting very large files (1000+ lines)**
|
||||
- mkdocs-settings-page.md could be split by feature
|
||||
- canvass-dashboard-page.md could separate components
|
||||
|
||||
---
|
||||
|
||||
## 11. Overall Quality Assessment
|
||||
|
||||
### Documentation Maturity: ★★★★☆ (4/5 stars)
|
||||
|
||||
### Strengths
|
||||
|
||||
- ✅ Comprehensive coverage (96.7% file completion)
|
||||
- ✅ Excellent structural consistency (96.5% compliance)
|
||||
- ✅ Rich code examples (1,730+ code blocks)
|
||||
- ✅ Strong API documentation (185+ endpoints)
|
||||
- ✅ Clear user workflows
|
||||
- ✅ Good component breakdowns
|
||||
- ✅ Extensive content (28,457 lines)
|
||||
|
||||
### Areas for Improvement
|
||||
|
||||
- ⚠️ 48% of files have broken cross-references
|
||||
- ⚠️ Missing 1 critical page (page-editor)
|
||||
- ⚠️ Some service pages lack response examples
|
||||
- ⚠️ Minor code block formatting issues
|
||||
|
||||
### Impact on Users
|
||||
|
||||
- Documentation is **highly usable** in current state
|
||||
- Internal navigation could be improved
|
||||
- Cross-references need resolution for production-ready docs
|
||||
|
||||
---
|
||||
|
||||
## Final Recommendation
|
||||
|
||||
The admin page documentation is **PRODUCTION-READY** with one critical gap:
|
||||
|
||||
### 🔴 BLOCKER
|
||||
|
||||
- Create **page-editor-page.md** (referenced by 2 files, important workflow)
|
||||
|
||||
### 🟡 POLISH ITEMS (Non-blocking)
|
||||
|
||||
- Resolve 14 files with forward-reference links
|
||||
- Add response examples to 6 service pages
|
||||
- Add Troubleshooting section to dashboard-page.md
|
||||
- Tag 5 code blocks in email-queue-page.md
|
||||
|
||||
### 📊 Metrics Summary
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Files Complete | 29/30 (96.7%) |
|
||||
| Total Lines | 28,457 |
|
||||
| Code Examples | 1,730+ |
|
||||
| API Endpoints | 185+ |
|
||||
| Structural Compliance | 96.5% |
|
||||
| Proper Titles | 100% |
|
||||
| Route Documentation | 100% |
|
||||
|
||||
### ⭐ Quality Rating
|
||||
|
||||
**Current:** 4/5 stars (EXCELLENT)
|
||||
**Once page-editor-page.md is created:** 4.5/5 stars (OUTSTANDING)
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
This documentation represents an **excellent foundation** for the Changemaker Lite V2 admin interface. The comprehensive coverage, consistent structure, and rich code examples make this documentation immediately usable for developers.
|
||||
|
||||
The primary recommendation is to create the missing `page-editor-page.md` file to complete the documentation set. The broken cross-references are forward-looking links that can be resolved as the remaining documentation sections are created (backend, features, API reference, user guides, deployment, troubleshooting).
|
||||
|
||||
**Overall Verdict:** Ready for initial use with minor improvements needed for production-grade completeness.
|
||||
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 372 KiB |
@@ -102,14 +102,20 @@
|
||||
/* Responsive video containers */
|
||||
@media (max-width: 768px) {
|
||||
.video-block,
|
||||
.advanced-video-container {
|
||||
.advanced-video-container,
|
||||
.video-card-block {
|
||||
margin: 1.5rem auto;
|
||||
}
|
||||
|
||||
.video-block video,
|
||||
.advanced-video-container video {
|
||||
.advanced-video-container video,
|
||||
.video-card-player video {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.video-card-block {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Video placeholder (shown during GrapesJS export before hydration) */
|
||||
@@ -138,9 +144,45 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Video card block (YouTube-style preview card) */
|
||||
.video-card-block {
|
||||
margin: 2rem auto;
|
||||
max-width: 480px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.video-card-block:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.video-card-block a {
|
||||
text-decoration: none !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
/* Video card gallery button */
|
||||
.video-card-gallery-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
/* Video card inline player (after clicking thumbnail) */
|
||||
.video-card-player video {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.video-card-player video:focus {
|
||||
outline: 2px solid var(--md-primary-fg-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
|
||||
/* Ensure videos respect content width in Material theme */
|
||||
.md-content .video-block,
|
||||
.md-content .advanced-video-container {
|
||||
.md-content .advanced-video-container,
|
||||
.md-content .video-card-block {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 222 KiB |
@@ -2,7 +2,7 @@
|
||||
// Generated by mkdocs/docs/hooks/env_config_hook.py
|
||||
(function() {
|
||||
window.MEDIA_API_URL = 'http://localhost:4100';
|
||||
window.PUBLIC_URL = 'http://localhost:3000';
|
||||
window.PUBLIC_URL = 'http://localhost:3002';
|
||||
window.VIDEO_PLAYER_DEBUG = false;
|
||||
|
||||
console.log('[Video Config] Loaded from environment:', {
|
||||
|
||||
@@ -189,14 +189,202 @@
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format view count (e.g. 1200 → "1.2K views")
|
||||
*/
|
||||
function formatViewCount(count) {
|
||||
if (!count || count <= 0) return '0 views';
|
||||
if (count === 1) return '1 view';
|
||||
if (count < 1000) return count + ' views';
|
||||
if (count < 1000000) return (count / 1000).toFixed(1).replace(/\.0$/, '') + 'K views';
|
||||
return (count / 1000000).toFixed(1).replace(/\.0$/, '') + 'M views';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gallery URL for a video.
|
||||
*/
|
||||
function getGalleryUrl(videoId) {
|
||||
return PUBLIC_URL + '/gallery?expanded=' + videoId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate a video card block:
|
||||
* 1. Fix thumbnail URL using MEDIA_API_URL
|
||||
* 2. Fetch live metadata (views, title)
|
||||
* 3. Clicking thumbnail plays video inline
|
||||
* 4. Replace "Watch →" with Gallery link
|
||||
*/
|
||||
async function hydrateVideoCard(cardElement) {
|
||||
var videoId = parseInt(cardElement.dataset.videoId, 10);
|
||||
if (!videoId || isNaN(videoId)) return;
|
||||
|
||||
var title = cardElement.dataset.videoTitle || 'Video';
|
||||
|
||||
// 1. Fix thumbnail URL — replace placeholder/broken src with MEDIA_API_URL
|
||||
var img = cardElement.querySelector('img');
|
||||
if (img) {
|
||||
// Cache-buster avoids stale CORS-error cached responses
|
||||
var correctThumbUrl = MEDIA_API_URL + '/api/videos/' + videoId + '/thumbnail?v=' + Date.now();
|
||||
// Always replace src — data URI placeholder, Docker hostname, or stale URL
|
||||
img.src = correctThumbUrl;
|
||||
// Fallback if thumbnail fails to load — show a styled placeholder instead of broken image
|
||||
img.onerror = function() {
|
||||
img.style.display = 'none';
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Fetch live metadata to update views/title
|
||||
try {
|
||||
var metadata = await fetchVideoMetadata(videoId);
|
||||
if (metadata) {
|
||||
var viewsEl = cardElement.querySelector('[data-role="views"]');
|
||||
if (viewsEl && metadata.viewCount !== undefined) {
|
||||
viewsEl.textContent = formatViewCount(metadata.viewCount);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Non-critical — card works fine without live metadata
|
||||
}
|
||||
|
||||
// 3. Prevent the <a> tag from navigating
|
||||
var link = cardElement.querySelector('a');
|
||||
if (link) {
|
||||
link.addEventListener('click', function(e) { e.preventDefault(); });
|
||||
}
|
||||
|
||||
// 4. Fix the play button overlay SVG + set up click-to-play
|
||||
var thumbContainer = cardElement.querySelector('a > div:first-child');
|
||||
if (thumbContainer) {
|
||||
// Replace old tiny play icon with proper triangle
|
||||
var playOverlay = thumbContainer.querySelector('div:last-child');
|
||||
if (playOverlay && playOverlay.querySelector('svg')) {
|
||||
playOverlay.innerHTML = '<svg width="28" height="28" viewBox="0 0 24 24" fill="#fff" style="margin-left:3px;"><polygon points="5,3 19,12 5,21"/></svg>';
|
||||
}
|
||||
|
||||
thumbContainer.style.cursor = 'pointer';
|
||||
thumbContainer.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Don't re-create if already playing
|
||||
if (cardElement.querySelector('video')) return;
|
||||
|
||||
var streamUrl = MEDIA_API_URL + '/api/videos/' + videoId + '/stream';
|
||||
var thumbUrl = MEDIA_API_URL + '/api/videos/' + videoId + '/thumbnail';
|
||||
|
||||
// Build inline player — locked to same 16:9 aspect ratio as the card
|
||||
var playerContainer = document.createElement('div');
|
||||
playerContainer.className = 'video-card-player';
|
||||
playerContainer.style.cssText = 'max-width:480px;margin:0 auto;border-radius:12px;overflow:hidden;background:#0d1b2a;box-shadow:0 4px 12px rgba(0,0,0,0.3);';
|
||||
|
||||
// 16:9 aspect ratio wrapper prevents container resize when video loads
|
||||
var videoWrapper = document.createElement('div');
|
||||
videoWrapper.style.cssText = 'position:relative;padding-bottom:56.25%;background:#000;';
|
||||
|
||||
var video = document.createElement('video');
|
||||
video.src = streamUrl;
|
||||
video.poster = thumbUrl;
|
||||
video.controls = true;
|
||||
video.autoplay = true;
|
||||
video.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;display:block;border-radius:12px 12px 0 0;';
|
||||
video.setAttribute('title', title);
|
||||
|
||||
videoWrapper.appendChild(video);
|
||||
|
||||
var infoBar = document.createElement('div');
|
||||
infoBar.style.cssText = 'padding:10px 16px;background:#1b2838;display:flex;align-items:center;gap:8px;';
|
||||
|
||||
var titleSpan = document.createElement('span');
|
||||
titleSpan.style.cssText = 'color:#fff;font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;';
|
||||
titleSpan.textContent = title;
|
||||
|
||||
var galleryLink = document.createElement('a');
|
||||
galleryLink.href = getGalleryUrl(videoId);
|
||||
galleryLink.target = '_blank';
|
||||
galleryLink.innerHTML = '▶ Gallery';
|
||||
galleryLink.className = 'video-card-gallery-btn';
|
||||
galleryLink.style.cssText = 'background:#9d4edd;border:none;color:#fff;font-size:12px;font-weight:600;padding:5px 12px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background 0.15s;text-decoration:none;display:inline-block;';
|
||||
galleryLink.title = 'Open in gallery';
|
||||
galleryLink.onmouseenter = function() { galleryLink.style.background = '#b06ce6'; };
|
||||
galleryLink.onmouseleave = function() { galleryLink.style.background = '#9d4edd'; };
|
||||
galleryLink.addEventListener('click', function(ev) {
|
||||
ev.stopPropagation();
|
||||
});
|
||||
|
||||
var closeBtn = document.createElement('button');
|
||||
closeBtn.textContent = '\u2715';
|
||||
closeBtn.style.cssText = 'background:none;border:none;color:#8899aa;font-size:16px;cursor:pointer;padding:4px;flex-shrink:0;transition:color 0.15s;';
|
||||
closeBtn.title = 'Close player';
|
||||
closeBtn.onmouseenter = function() { closeBtn.style.color = '#fff'; };
|
||||
closeBtn.onmouseleave = function() { closeBtn.style.color = '#8899aa'; };
|
||||
|
||||
infoBar.appendChild(titleSpan);
|
||||
infoBar.appendChild(galleryLink);
|
||||
infoBar.appendChild(closeBtn);
|
||||
playerContainer.appendChild(videoWrapper);
|
||||
playerContainer.appendChild(infoBar);
|
||||
|
||||
// Save original card content and replace
|
||||
var originalContent = cardElement.innerHTML;
|
||||
cardElement.innerHTML = '';
|
||||
cardElement.appendChild(playerContainer);
|
||||
|
||||
// Close button restores original card
|
||||
closeBtn.addEventListener('click', function(ev) {
|
||||
ev.stopPropagation();
|
||||
video.pause();
|
||||
video.src = '';
|
||||
cardElement.innerHTML = originalContent;
|
||||
hydrateVideoCard(cardElement);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Replace "Watch →" text with a Gallery button in the card info bar
|
||||
var infoBar = cardElement.querySelector('a > div:last-child');
|
||||
if (infoBar) {
|
||||
var flexRow = infoBar.querySelector('div:last-child');
|
||||
if (flexRow) {
|
||||
var spans = flexRow.querySelectorAll('span');
|
||||
var watchSpan = null;
|
||||
for (var i = 0; i < spans.length; i++) {
|
||||
if (spans[i].textContent.indexOf('Watch') !== -1) {
|
||||
watchSpan = spans[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (watchSpan) {
|
||||
var galleryLink = document.createElement('a');
|
||||
galleryLink.href = getGalleryUrl(videoId);
|
||||
galleryLink.target = '_blank';
|
||||
galleryLink.innerHTML = '▶ Gallery';
|
||||
galleryLink.className = 'video-card-gallery-btn';
|
||||
galleryLink.style.cssText = 'background:#9d4edd;border:none;color:#fff;font-size:12px;font-weight:600;padding:5px 14px;border-radius:4px;cursor:pointer;transition:background 0.15s;text-decoration:none;display:inline-block;';
|
||||
galleryLink.title = 'Open in gallery';
|
||||
galleryLink.onmouseenter = function() { galleryLink.style.background = '#b06ce6'; };
|
||||
galleryLink.onmouseleave = function() { galleryLink.style.background = '#9d4edd'; };
|
||||
galleryLink.addEventListener('click', function(ev) {
|
||||
ev.stopPropagation(); // Don't trigger card click-to-play
|
||||
});
|
||||
watchSpan.replaceWith(galleryLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as hydrated
|
||||
cardElement.dataset.hydrated = 'true';
|
||||
console.log('Hydrated video card for video ' + videoId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all video blocks on page load
|
||||
*/
|
||||
function initVideoBlocks() {
|
||||
const blocks = document.querySelectorAll('.video-block');
|
||||
console.log(`Found ${blocks.length} video block(s) to hydrate`);
|
||||
var blocks = document.querySelectorAll('.video-block');
|
||||
console.log('Found ' + blocks.length + ' video block(s) to hydrate');
|
||||
|
||||
blocks.forEach(block => {
|
||||
blocks.forEach(function(block) {
|
||||
// Skip if already rendered (has video element)
|
||||
if (block.querySelector('video')) {
|
||||
console.log('Video block already rendered, skipping');
|
||||
@@ -207,18 +395,40 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all video card blocks on page load
|
||||
*/
|
||||
function initVideoCards() {
|
||||
var cards = document.querySelectorAll('.video-card-block');
|
||||
console.log('Found ' + cards.length + ' video card(s) to hydrate');
|
||||
|
||||
cards.forEach(function(card) {
|
||||
// Skip if already hydrated
|
||||
if (card.dataset.hydrated === 'true') return;
|
||||
hydrateVideoCard(card);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all video elements (blocks + cards)
|
||||
*/
|
||||
function initAll() {
|
||||
initVideoBlocks();
|
||||
initVideoCards();
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initVideoBlocks);
|
||||
document.addEventListener('DOMContentLoaded', initAll);
|
||||
} else {
|
||||
initVideoBlocks();
|
||||
initAll();
|
||||
}
|
||||
|
||||
// Re-initialize on MkDocs navigation (for SPA-style navigation)
|
||||
if (typeof window.document$ !== 'undefined') {
|
||||
window.document$.subscribe(() => {
|
||||
console.log('MkDocs navigation detected, re-initializing video blocks');
|
||||
setTimeout(initVideoBlocks, 100);
|
||||
window.document$.subscribe(function() {
|
||||
console.log('MkDocs navigation detected, re-initializing video elements');
|
||||
setTimeout(initAll, 100);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
|
Before Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 553 KiB |
|
Before Width: | Height: | Size: 6.4 MiB |
|
Before Width: | Height: | Size: 920 KiB |
@@ -7,10 +7,10 @@
|
||||
"stars_count": 0,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 23,
|
||||
"updated_at": "2026-02-11T10:19:49-07:00",
|
||||
"updated_at": "2026-02-17T10:36:57-07:00",
|
||||
"created_at": "2025-05-28T14:54:59-06:00",
|
||||
"clone_url": "https://gitea.bnkops.com/admin/changemaker.lite.git",
|
||||
"ssh_url": "git@gitea.bnkops.com:admin/changemaker.lite.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-02-11T10:19:49-07:00"
|
||||
"last_build_update": "2026-02-17T10:36:57-07:00"
|
||||
}
|
||||
@@ -3,14 +3,14 @@
|
||||
"name": "claude-code",
|
||||
"description": "Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.",
|
||||
"html_url": "https://github.com/anthropics/claude-code",
|
||||
"language": "PowerShell",
|
||||
"stars_count": 26166,
|
||||
"forks_count": 1437,
|
||||
"open_issues_count": 2513,
|
||||
"updated_at": "2025-07-27T00:10:38Z",
|
||||
"language": "Shell",
|
||||
"stars_count": 67351,
|
||||
"forks_count": 5255,
|
||||
"open_issues_count": 6188,
|
||||
"updated_at": "2026-02-17T21:23:40Z",
|
||||
"created_at": "2025-02-22T17:41:21Z",
|
||||
"clone_url": "https://github.com/anthropics/claude-code.git",
|
||||
"ssh_url": "git@github.com:anthropics/claude-code.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2025-07-25T21:06:46Z"
|
||||
"last_build_update": "2026-02-17T18:53:52Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "VS Code in the browser",
|
||||
"html_url": "https://github.com/coder/code-server",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 73102,
|
||||
"forks_count": 6130,
|
||||
"open_issues_count": 143,
|
||||
"updated_at": "2025-07-26T22:12:45Z",
|
||||
"stars_count": 76278,
|
||||
"forks_count": 6513,
|
||||
"open_issues_count": 176,
|
||||
"updated_at": "2026-02-17T20:24:30Z",
|
||||
"created_at": "2019-02-27T16:50:41Z",
|
||||
"clone_url": "https://github.com/coder/code-server.git",
|
||||
"ssh_url": "git@github.com:coder/code-server.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2025-07-24T23:16:29Z"
|
||||
"last_build_update": "2026-02-14T12:44:57Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.",
|
||||
"html_url": "https://github.com/gethomepage/homepage",
|
||||
"language": "JavaScript",
|
||||
"stars_count": 25016,
|
||||
"forks_count": 1560,
|
||||
"stars_count": 28451,
|
||||
"forks_count": 1790,
|
||||
"open_issues_count": 1,
|
||||
"updated_at": "2025-07-26T22:55:16Z",
|
||||
"updated_at": "2026-02-17T19:07:13Z",
|
||||
"created_at": "2022-08-24T07:29:42Z",
|
||||
"clone_url": "https://github.com/gethomepage/homepage.git",
|
||||
"ssh_url": "git@github.com:gethomepage/homepage.git",
|
||||
"default_branch": "dev",
|
||||
"last_build_update": "2025-07-27T00:42:35Z"
|
||||
"last_build_update": "2026-02-17T12:23:04Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD",
|
||||
"html_url": "https://github.com/go-gitea/gitea",
|
||||
"language": "Go",
|
||||
"stars_count": 49740,
|
||||
"forks_count": 5920,
|
||||
"open_issues_count": 2736,
|
||||
"updated_at": "2025-07-27T00:44:09Z",
|
||||
"stars_count": 53755,
|
||||
"forks_count": 6390,
|
||||
"open_issues_count": 2835,
|
||||
"updated_at": "2026-02-17T20:31:54Z",
|
||||
"created_at": "2016-11-01T02:13:26Z",
|
||||
"clone_url": "https://github.com/go-gitea/gitea.git",
|
||||
"ssh_url": "git@github.com:go-gitea/gitea.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2025-07-27T00:44:04Z"
|
||||
"last_build_update": "2026-02-17T20:32:46Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard. Single binary app.",
|
||||
"html_url": "https://github.com/knadh/listmonk",
|
||||
"language": "Go",
|
||||
"stars_count": 17468,
|
||||
"forks_count": 1686,
|
||||
"open_issues_count": 100,
|
||||
"updated_at": "2025-07-26T16:53:36Z",
|
||||
"stars_count": 19074,
|
||||
"forks_count": 1926,
|
||||
"open_issues_count": 113,
|
||||
"updated_at": "2026-02-17T16:51:23Z",
|
||||
"created_at": "2019-06-26T05:08:39Z",
|
||||
"clone_url": "https://github.com/knadh/listmonk.git",
|
||||
"ssh_url": "git@github.com:knadh/listmonk.git",
|
||||
"default_branch": "master",
|
||||
"last_build_update": "2025-07-22T12:07:13Z"
|
||||
"last_build_update": "2026-02-17T05:10:24Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Create & scan cute qr codes easily \ud83d\udc7e",
|
||||
"html_url": "https://github.com/lyqht/mini-qr",
|
||||
"language": "Vue",
|
||||
"stars_count": 1338,
|
||||
"forks_count": 177,
|
||||
"open_issues_count": 13,
|
||||
"updated_at": "2025-07-26T18:13:59Z",
|
||||
"stars_count": 1851,
|
||||
"forks_count": 236,
|
||||
"open_issues_count": 21,
|
||||
"updated_at": "2026-02-17T15:35:23Z",
|
||||
"created_at": "2023-04-21T14:20:14Z",
|
||||
"clone_url": "https://github.com/lyqht/mini-qr.git",
|
||||
"ssh_url": "git@github.com:lyqht/mini-qr.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2025-07-17T12:31:42Z"
|
||||
"last_build_update": "2025-10-31T15:20:31Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.",
|
||||
"html_url": "https://github.com/n8n-io/n8n",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 123887,
|
||||
"forks_count": 37509,
|
||||
"open_issues_count": 971,
|
||||
"updated_at": "2025-07-27T00:44:16Z",
|
||||
"stars_count": 174947,
|
||||
"forks_count": 54925,
|
||||
"open_issues_count": 1388,
|
||||
"updated_at": "2026-02-17T21:23:05Z",
|
||||
"created_at": "2019-06-22T09:24:21Z",
|
||||
"clone_url": "https://github.com/n8n-io/n8n.git",
|
||||
"ssh_url": "git@github.com:n8n-io/n8n.git",
|
||||
"default_branch": "master",
|
||||
"last_build_update": "2025-07-26T15:16:15Z"
|
||||
"last_build_update": "2026-02-17T21:12:15Z"
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"full_name": "nocodb/nocodb",
|
||||
"name": "nocodb",
|
||||
"description": "\ud83d\udd25 \ud83d\udd25 \ud83d\udd25 Open Source Airtable Alternative",
|
||||
"description": "\ud83d\udd25 \ud83d\udd25 \ud83d\udd25 A Free & Self-hostable Airtable Alternative",
|
||||
"html_url": "https://github.com/nocodb/nocodb",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 56041,
|
||||
"forks_count": 4048,
|
||||
"open_issues_count": 686,
|
||||
"updated_at": "2025-07-26T23:44:10Z",
|
||||
"stars_count": 62026,
|
||||
"forks_count": 4632,
|
||||
"open_issues_count": 592,
|
||||
"updated_at": "2026-02-17T20:03:56Z",
|
||||
"created_at": "2017-10-29T18:51:48Z",
|
||||
"clone_url": "https://github.com/nocodb/nocodb.git",
|
||||
"ssh_url": "git@github.com:nocodb/nocodb.git",
|
||||
"default_branch": "develop",
|
||||
"last_build_update": "2025-07-26T18:53:06Z"
|
||||
"last_build_update": "2026-02-17T18:50:24Z"
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"full_name": "ollama/ollama",
|
||||
"name": "ollama",
|
||||
"description": "Get up and running with Llama 3.3, DeepSeek-R1, Phi-4, Gemma 3, Mistral Small 3.1 and other large language models.",
|
||||
"description": "Get up and running with Kimi-K2.5, GLM-5, MiniMax, DeepSeek, gpt-oss, Qwen, Gemma and other models.",
|
||||
"html_url": "https://github.com/ollama/ollama",
|
||||
"language": "Go",
|
||||
"stars_count": 147622,
|
||||
"forks_count": 12527,
|
||||
"open_issues_count": 1947,
|
||||
"updated_at": "2025-07-27T00:34:59Z",
|
||||
"stars_count": 162779,
|
||||
"forks_count": 14601,
|
||||
"open_issues_count": 2431,
|
||||
"updated_at": "2026-02-17T20:40:13Z",
|
||||
"created_at": "2023-06-26T19:39:32Z",
|
||||
"clone_url": "https://github.com/ollama/ollama.git",
|
||||
"ssh_url": "git@github.com:ollama/ollama.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2025-07-25T23:58:11Z"
|
||||
"last_build_update": "2026-02-17T21:16:23Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Documentation that simply works",
|
||||
"html_url": "https://github.com/squidfunk/mkdocs-material",
|
||||
"language": "Python",
|
||||
"stars_count": 24011,
|
||||
"forks_count": 3825,
|
||||
"open_issues_count": 6,
|
||||
"updated_at": "2025-07-27T00:11:59Z",
|
||||
"stars_count": 26063,
|
||||
"forks_count": 4044,
|
||||
"open_issues_count": 2,
|
||||
"updated_at": "2026-02-17T20:20:41Z",
|
||||
"created_at": "2016-01-28T22:09:23Z",
|
||||
"clone_url": "https://github.com/squidfunk/mkdocs-material.git",
|
||||
"ssh_url": "git@github.com:squidfunk/mkdocs-material.git",
|
||||
"default_branch": "master",
|
||||
"last_build_update": "2025-07-26T15:53:16Z"
|
||||
"last_build_update": "2026-01-21T14:19:54Z"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 441 KiB |
@@ -0,0 +1,2 @@
|
||||
# Blog
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
date: 2025-07-03
|
||||
---
|
||||
|
||||
# Blog 1
|
||||
|
||||
Hello! Just putting something up here because, well, gosh darn, feels like the right thing to do.
|
||||
|
||||
Making swift progress. Can now write things fast as heck lad.
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
date: 2025-07-10
|
||||
---
|
||||
|
||||
Wow. Big build day. Added (admittedly still buggy) shifts support to the system. Power did it in a day.
|
||||
|
||||
Other updates recently include:
|
||||
|
||||
- Fully reworked backend `server.js` into modular components.
|
||||
- Bunch of mobile related fixes and improvements.
|
||||
- Bi-directional saving of configs fixed up
|
||||
- Some style upgrades
|
||||
|
||||
Need to make more content about how to use the system in general too.
|
||||
@@ -1,74 +0,0 @@
|
||||
---
|
||||
date: 2025-08-01
|
||||
---
|
||||
|
||||
Alrighty yall, it was a wild month of development, and we have a lot to cover! Here’s the latest on Changemaker Lite, including our new landing page, major updates to the map application, and a comprehensive overview of all changes made in the last month.
|
||||
|
||||
Campaigning is going! We have candidates working the system in the field, and we’re excited to see how it performs in real-world scenarios.
|
||||
|
||||
# Monthly Development Report – August 2025
|
||||
|
||||
## Git Change Summary (July–August 2025)
|
||||
|
||||
Below is a summary of all changes pushed to git in the last month:
|
||||
|
||||
- **Admin Panel & NocoDB Integration**: Major updates to the admin section, including a new NocoDB admin area, improved database search, and code cleanups.
|
||||
- **Website & UI Updates**: Numerous updates to the website, including language tweaks, mobile friendliness, and new frontend features.
|
||||
- **Shifts Management**: Comprehensive volunteer shift management system added, with calendar/grid views, admin controls, and real-time updates.
|
||||
- **Authentication & User Management**: Enhanced login system, password recovery via SMTP, user management panel for admins, and role-based access control.
|
||||
- **Map & Geocoding**: Improved map display, apartment views, geocoding integration, and address confirmation system.
|
||||
- **Unified Search System**: Powerful search bar (Ctrl+K) for docs and address search, with real-time results, caching, and QR code generation.
|
||||
- **Data Import & Conversion**: CSV data import with batch geocoding and visual progress, plus a new data converter tool.
|
||||
- **Email & Notifications**: SMTP integration for email notifications and password recovery.
|
||||
- **Performance & Bug Fixes**: Numerous bug fixes, code cleanups, and performance improvements across the stack.
|
||||
- **Docker & Deployment**: Docker containerization, improved build scripts, and easier multi-instance deployment.
|
||||
- **Documentation**: Expanded and updated documentation, including new manuals and guides.
|
||||
|
||||
For a detailed commit log, see `git-report.txt`.
|
||||
|
||||
---
|
||||
|
||||
## Overview of `lander.html`
|
||||
|
||||
The `lander.html` file is a modern, responsive landing page for Changemaker Lite, featuring:
|
||||
|
||||
- **Custom Theming**: Light/dark mode toggle with persistent user preference.
|
||||
- **Sticky Header & Navigation**: Fixed header with smooth scroll and navigation links.
|
||||
- **Hero Section**: Prominent introduction with call-to-action buttons.
|
||||
- **Search Integration**: Inline MkDocs search with real-time results and keyboard shortcuts.
|
||||
- **Feature Showcases**: Sections for problems, solutions, power tools, data ownership, pricing, integrations, testimonials, and live examples.
|
||||
- **Responsive Design**: Mobile-friendly layout with adaptive grids and cards.
|
||||
- **Animations**: Intersection observer for fade-in effects on cards and sections.
|
||||
- **Video & Media**: Embedded video showcase and rich media support.
|
||||
- **Footer**: Informative footer with links and contact info.
|
||||
|
||||
The page is styled with CSS variables for easy theming and includes scripts for search, theme switching, and smooth scrolling.
|
||||
|
||||
---
|
||||
|
||||
## New Features in Map (`README.md`)
|
||||
|
||||
The map application has received significant upgrades:
|
||||
|
||||
- **Interactive Map**: Real-time visualization with OpenStreetMap and Leaflet.js.
|
||||
- **Unified Search**: Docs and address search in one bar, with keyboard shortcuts and smart caching.
|
||||
- **Geolocation & Add Locations**: Real-time user geolocation and ability to add new locations directly from the map.
|
||||
- **Auto-Refresh**: Map data auto-refreshes every 30 seconds.
|
||||
- **Responsive & Mobile Ready**: Fully responsive design for all devices.
|
||||
- **Secure API Proxy**: Protects credentials and secures API access.
|
||||
- **Admin Panel**: System configuration, user management, and shift management for admins.
|
||||
- **Walk Sheet Generator**: For door-to-door canvassing, with customizable titles and QR code integration.
|
||||
- **Volunteer Shifts**: Calendar/grid views, signup/cancellation, admin shift creation, and real-time updates.
|
||||
- **Role-Based Access**: Admin vs. user permissions throughout the app.
|
||||
- **Email Notifications**: SMTP-based notifications and password recovery.
|
||||
- **CSV Import & Geocoding**: Batch import with geocoding and progress tracking.
|
||||
- **Dockerized Deployment**: Easy setup and scaling with Docker.
|
||||
- **Open Source**: 100% open source, no proprietary dependencies.
|
||||
|
||||
**API Endpoints**: Comprehensive REST API for locations, shifts, authentication, admin, and geocoding, all with rate limiting and security features.
|
||||
|
||||
**Database Schema**: Auto-created tables for locations, users, settings, shifts, and signups, with detailed field definitions.
|
||||
|
||||
---
|
||||
|
||||
For more details, see the full `README.md` and explore the live application.
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
date: 2025-09-24
|
||||
---
|
||||
|
||||
Okay! Wow! Its been nearly 2 months since I wrote a blog update for this system.
|
||||
|
||||
We have pushed out [influence](https://influence.bnkops.com) as a beta product, and will be pushing it out to get feedback from real users over the next month.
|
||||
|
||||
Our campaign software Map was also used by a real campaign for the first time, and we have some great feedback to incorporate into the system.
|
||||
|
||||
## What We've Built Since August
|
||||
|
||||
Here's a quick rundown of everything we've committed to the codebase over the past three months:
|
||||
|
||||
### Influence App - Major Launch
|
||||
- **Complete UI Overhaul**: Built an entirely new user interface and user system from the ground up
|
||||
- **Response Wall**: Developed a comprehensive response wall system where elected officials can respond to campaigns, including verified response system with QR codes and verify buttons
|
||||
- **Campaign Management**: Created new system for creating campaigns from the main site dashboard with campaign cover photos and phone numbers
|
||||
- **Social Features**: Added social share buttons and site info improvements
|
||||
- **Geocoding Enhancements**: Implemented automatic scanning of NocoDB locations to build geo-locations, plus premium Mapbox option for better street address matching
|
||||
- **User Management**: Built password updater for users/admins and improved overall user management
|
||||
- **Network Integration**: Integrated Influence into the Changemaker network
|
||||
- **Monitoring & Maintenance**: Added health check utility, logger, metrics, backup, and SMTP toggle scripts
|
||||
|
||||
### Map App - Production Ready
|
||||
- **Map Cuts Feature**: Built a comprehensive "cuts" system for dividing territories, including assignment workflows, print views, and spatial data handling
|
||||
- **Public Shifts**: Implemented new public shifts system for volunteer coordination
|
||||
- **Performance**: Optimized loading for maps with 1000+ locations and improved shift loading speeds
|
||||
- **Admin Improvements**: Major refactor of admin.js into readable, maintainable files, plus new NocoDB admin section with database search
|
||||
- **Temp Users**: Enhanced temporary user system with proper access controls and limited data sending
|
||||
- **Data Tools**: Added CSV import reporting and ListMonk synchronization
|
||||
- **UI/UX**: Standardized z-indexes, updated pop-ups, fixed menu bugs, and improved cut overlays
|
||||
- **CORS & Auth**: Fixed authentication, lockouts, and CORS for local dev access
|
||||
|
||||
### Infrastructure & DevOps
|
||||
- **Documentation**: Updated MkDocs documentation with search functionality
|
||||
- **Build System**: Improved build-nocodb script to migrate data and auto-input URLs to .env
|
||||
- **Docker**: Cleaned up docker-compose configuration and fixed container duplication issues
|
||||
- **Configuration**: Updated homepage configs, Cloudflare tunnel settings, and general system configs
|
||||
|
||||
The velocity has been incredible - we went from concept to production with Influence in just a few weeks, and Map has evolved into a robust campaigning tool that's battle-tested in real elections. Looking forward to incorporating user feedback and continuing to iterate!
|
||||
|
||||
22
mkdocs/docs/docs/admin/index.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Administration
|
||||
description: Admin guide for managing users, settings, and platform operations.
|
||||
icon: material/shield-account
|
||||
---
|
||||
|
||||
# Administration
|
||||
|
||||
This section covers day-to-day administration of the Changemaker Lite platform.
|
||||
|
||||
!!! warning "Under Construction"
|
||||
Detailed admin documentation is being written. Check back soon.
|
||||
|
||||
## Topics
|
||||
|
||||
- **User Management** — create, edit, and deactivate user accounts
|
||||
- **Roles & Permissions** — SUPER_ADMIN, INFLUENCE_ADMIN, MAP_ADMIN, USER, TEMP
|
||||
- **Site Settings** — configure site name, contact info, and feature flags
|
||||
- **Email Templates** — manage reusable email templates
|
||||
- **Newsletter Sync** — Listmonk integration for subscriber management
|
||||
- **Email Queue** — monitor and manage the BullMQ advocacy email queue
|
||||
- **Landing Pages** — create and publish pages with the GrapesJS editor
|
||||
42
mkdocs/docs/docs/api/index.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: API Reference
|
||||
description: REST API endpoints for authentication, campaigns, locations, media, and more.
|
||||
icon: material/api
|
||||
---
|
||||
|
||||
# API Reference
|
||||
|
||||
Changemaker Lite exposes two REST APIs — the main Express API (port 4000) and the Fastify Media API (port 4100).
|
||||
|
||||
!!! warning "Under Construction"
|
||||
The full API reference is being written. Check back soon.
|
||||
|
||||
## Main API (Express, port 4000)
|
||||
|
||||
### Authentication
|
||||
- `POST /api/auth/register` — create account
|
||||
- `POST /api/auth/login` — get access + refresh tokens
|
||||
- `POST /api/auth/refresh` — rotate refresh token
|
||||
- `POST /api/auth/logout` — invalidate refresh token
|
||||
- `GET /api/auth/me` — current user profile
|
||||
|
||||
### Campaigns
|
||||
- `GET /api/campaigns` — list campaigns (admin)
|
||||
- `POST /api/campaigns` — create campaign
|
||||
- `GET /api/public/campaigns` — list public campaigns
|
||||
- `GET /api/public/campaigns/:id` — campaign details
|
||||
|
||||
### Locations
|
||||
- `GET /api/locations` — list locations (admin)
|
||||
- `POST /api/locations` — create location
|
||||
- `GET /api/public/locations` — public map locations
|
||||
|
||||
### More Endpoints
|
||||
Users, shifts, cuts, responses, email queue, pages, settings, and more — full reference coming soon.
|
||||
|
||||
## Media API (Fastify, port 4100)
|
||||
|
||||
- `GET /api/videos` — list videos
|
||||
- `POST /api/upload` — upload video
|
||||
- `GET /api/public/videos` — public gallery
|
||||
- `POST /api/track/view` — record view event
|
||||
39
mkdocs/docs/docs/architecture/index.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Architecture
|
||||
description: System architecture, dual API design, database schema, and authentication flow.
|
||||
icon: material/sitemap
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
Changemaker Lite uses a dual-API architecture with a shared PostgreSQL database.
|
||||
|
||||
!!! warning "Under Construction"
|
||||
Detailed architecture documentation is being written. Check back soon.
|
||||
|
||||
## System Overview
|
||||
|
||||
```
|
||||
Browser ──► Nginx (reverse proxy) ──┬──► Express API (port 4000) ──► PostgreSQL
|
||||
├──► Fastify Media API (port 4100) ──┘
|
||||
├──► React Admin GUI (port 3000)
|
||||
└──► MkDocs / Other Services
|
||||
```
|
||||
|
||||
## Key Components
|
||||
|
||||
| Component | Technology | Role |
|
||||
|-----------|-----------|------|
|
||||
| Main API | Express.js + Prisma | Auth, campaigns, map, shifts, pages |
|
||||
| Media API | Fastify + Prisma | Video library, analytics, uploads |
|
||||
| Admin GUI | React + Ant Design | Single-page admin application |
|
||||
| Database | PostgreSQL 16 | Shared by both APIs (30+ tables) |
|
||||
| Cache | Redis | Rate limiting, job queues, geocoding |
|
||||
| Proxy | Nginx | Subdomain routing, security headers |
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
- JWT access tokens (15 min) + refresh tokens (7 days)
|
||||
- Refresh token rotation with atomic database transaction
|
||||
- Role-based access control (5 roles)
|
||||
- Rate limiting on auth endpoints (10/min per IP)
|
||||
579
mkdocs/docs/docs/deployment/index.md
Normal file
@@ -0,0 +1,579 @@
|
||||
---
|
||||
title: Deployment
|
||||
description: Deploy Changemaker Lite to production with Docker, SSL, backups, and monitoring.
|
||||
icon: material/docker
|
||||
---
|
||||
|
||||
# Deployment
|
||||
|
||||
This guide covers how to take Changemaker Lite from a local development setup to a publicly accessible production deployment. The main decision is **how to expose your services to the internet**.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Regardless of which exposure method you choose, the internal architecture is the same:
|
||||
|
||||
```
|
||||
Internet → [Your exposure method] → Nginx (port 80) → Backend Services
|
||||
```
|
||||
|
||||
Nginx handles all subdomain routing internally. Every service is accessed through nginx on port 80, which proxies to the correct container based on the `Host` header.
|
||||
|
||||
| Subdomain | Service | Container Port |
|
||||
|-----------|---------|---------------|
|
||||
| `app.DOMAIN` | Admin GUI + public pages | 3000 |
|
||||
| `api.DOMAIN` | Express API | 4000 |
|
||||
| `media.DOMAIN` | Fastify Media API | 4100 |
|
||||
| `DOMAIN` (root) | MkDocs documentation site | 4001 |
|
||||
| `db.DOMAIN` | NocoDB | 8091 |
|
||||
| `docs.DOMAIN` | MkDocs live preview | 4003 |
|
||||
| `code.DOMAIN` | Code Server | 8888 |
|
||||
| `git.DOMAIN` | Gitea | 3030 |
|
||||
| `n8n.DOMAIN` | Workflow automation | 5678 |
|
||||
| `home.DOMAIN` | Homepage dashboard | 3010 |
|
||||
| `listmonk.DOMAIN` | Newsletter manager | 9001 |
|
||||
| `mail.DOMAIN` | MailHog (dev email) | 8025 |
|
||||
| `qr.DOMAIN` | Mini QR generator | 8089 |
|
||||
| `draw.DOMAIN` | Excalidraw whiteboard | 8090 |
|
||||
| `grafana.DOMAIN` | Monitoring dashboards | 3001 |
|
||||
|
||||
---
|
||||
|
||||
## Exposure Methods
|
||||
|
||||
### Option 1: Pangolin + Newt Tunnel (Recommended) { #pangolin }
|
||||
|
||||
!!! tip "Admin GUI: Tunnel Management Page"
|
||||
The admin dashboard includes a dedicated **Tunnel Management** page at **Admin → Settings → Tunnel**. This page provides:
|
||||
|
||||
- **Live status** of the Pangolin connection and Newt container health
|
||||
- **Step-by-step setup instructions** if credentials aren't configured yet
|
||||
- **Full resource table** listing every service, its domain, and target — useful as a reference when creating resources in the Pangolin dashboard
|
||||
- **API-based site creation** as an alternative to the Pangolin dashboard UI
|
||||
- **Restart Newt** button for quick container restarts without the terminal
|
||||
|
||||
If you're unsure about any step above, the Tunnel page walks you through the same process interactively.
|
||||
|
||||
|
||||
[Pangolin](https://github.com/fosrl/pangolin) is a self-hosted tunnel server. The **Newt** client container runs alongside your stack and establishes an outbound connection to your Pangolin server, which then routes public traffic back through the tunnel. No port forwarding or static IP required.
|
||||
|
||||
**Advantages:**
|
||||
|
||||
- No port forwarding needed on your router/firewall
|
||||
- Works behind CGNAT, double NAT, or restrictive networks
|
||||
- SSL/TLS handled by the Pangolin server
|
||||
- Self-hosted — you control the tunnel infrastructure
|
||||
- Built-in access control (optional per-resource authentication)
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- A Pangolin server (self-hosted on a VPS with a public IP)
|
||||
- A domain with DNS pointing to the Pangolin server
|
||||
- Pangolin API key and organization ID
|
||||
|
||||
#### Step 1: Configure Pangolin Credentials
|
||||
|
||||
If you used `config.sh`, you may have already set these. Otherwise, add to your `.env`:
|
||||
|
||||
```bash
|
||||
PANGOLIN_API_URL=https://api.your-pangolin-server.org/v1
|
||||
PANGOLIN_API_KEY=your_api_key_here
|
||||
PANGOLIN_ORG_ID=your_org_id
|
||||
```
|
||||
|
||||
#### Step 2: Create a Site in Pangolin
|
||||
|
||||
Log in to your Pangolin dashboard and create a new site:
|
||||
|
||||
1. Navigate to **Sites** → **Create New Site**
|
||||
2. Choose type: **Newt**
|
||||
3. Enter a name (e.g., `changemaker-yourdomain.org`)
|
||||
4. Choose a subnet (e.g., `100.90.128.3/24`)
|
||||
5. Select an exit node (if applicable)
|
||||
6. Click **Create Site**
|
||||
7. **Copy the credentials** — you'll need the Site ID, Newt ID, and Newt Secret
|
||||
|
||||
!!! warning "Save the credentials"
|
||||
The Newt Secret is only shown once during site creation. Copy it immediately.
|
||||
|
||||
#### Step 3: Update `.env` with Site Credentials
|
||||
|
||||
```bash
|
||||
PANGOLIN_SITE_ID=your_site_id
|
||||
PANGOLIN_ENDPOINT=https://your-pangolin-server.org
|
||||
PANGOLIN_NEWT_ID=your_newt_id
|
||||
PANGOLIN_NEWT_SECRET=your_newt_secret
|
||||
```
|
||||
|
||||
#### Step 4: Start the Newt Container
|
||||
|
||||
```bash
|
||||
docker compose up -d newt
|
||||
```
|
||||
|
||||
The Newt container connects to nginx (its only dependency) and establishes the tunnel:
|
||||
|
||||
```yaml
|
||||
# From docker-compose.yml
|
||||
newt:
|
||||
image: fosrl/newt
|
||||
container_name: newt-changemaker
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PANGOLIN_ENDPOINT=${PANGOLIN_ENDPOINT}
|
||||
- NEWT_ID=${PANGOLIN_NEWT_ID}
|
||||
- NEWT_SECRET=${PANGOLIN_NEWT_SECRET}
|
||||
depends_on:
|
||||
- nginx
|
||||
```
|
||||
|
||||
Verify the connection:
|
||||
|
||||
```bash
|
||||
docker compose logs newt --tail 20
|
||||
```
|
||||
|
||||
You should see a successful connection message.
|
||||
|
||||
#### Step 5: Create Public HTTP Resources
|
||||
|
||||
In the Pangolin dashboard, create an HTTP resource for each service you want exposed. All resources point to `nginx:80` — nginx handles the routing internally.
|
||||
|
||||
**Required resources** (minimum for a working deployment):
|
||||
|
||||
| Resource Name | Domain | Target | Auth |
|
||||
|--------------|--------|--------|------|
|
||||
| Admin GUI | `app.yourdomain.org` | `nginx:80` | Not Protected |
|
||||
| API Server | `api.yourdomain.org` | `nginx:80` | Not Protected |
|
||||
| Public Site | `yourdomain.org` | `nginx:80` | Not Protected |
|
||||
|
||||
**Optional resources** (add as needed):
|
||||
|
||||
| Resource Name | Domain | Target |
|
||||
|--------------|--------|--------|
|
||||
| Media API | `media.yourdomain.org` | `nginx:80` |
|
||||
| NocoDB | `db.yourdomain.org` | `nginx:80` |
|
||||
| Documentation | `docs.yourdomain.org` | `nginx:80` |
|
||||
| Code Server | `code.yourdomain.org` | `nginx:80` |
|
||||
| Gitea | `git.yourdomain.org` | `nginx:80` |
|
||||
| Grafana | `grafana.yourdomain.org` | `nginx:80` |
|
||||
|
||||
!!! danger "Set resources to Not Protected"
|
||||
By default, Pangolin may enable authentication on new resources. This causes 302 redirects to the Pangolin login page instead of reaching your services. Set each resource to **Not Protected** (public access) unless you intentionally want Pangolin SSO in front of it.
|
||||
|
||||
#### Step 6: Update CORS for Production
|
||||
|
||||
Add your production domain to `CORS_ORIGINS` in `.env`:
|
||||
|
||||
```bash
|
||||
CORS_ORIGINS=https://app.yourdomain.org,http://localhost:3000,http://localhost
|
||||
```
|
||||
|
||||
Then restart the API:
|
||||
|
||||
```bash
|
||||
docker compose restart api
|
||||
```
|
||||
|
||||
#### Step 7: Verify
|
||||
|
||||
```bash
|
||||
# Should return JSON (not a 302 redirect)
|
||||
curl https://api.yourdomain.org/api/health
|
||||
|
||||
# Admin GUI should load
|
||||
curl -I https://app.yourdomain.org
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Option 2: Cloudflare Tunnel { #cloudflare }
|
||||
|
||||
Cloudflare Tunnel (`cloudflared`) provides a similar zero-trust tunnel approach using Cloudflare's network. No port forwarding needed, and you get Cloudflare's CDN and DDoS protection.
|
||||
|
||||
**Advantages:**
|
||||
|
||||
- Free tier available
|
||||
- Built-in CDN and DDoS protection
|
||||
- No port forwarding needed
|
||||
- Managed SSL certificates
|
||||
|
||||
**Disadvantages:**
|
||||
|
||||
- Proprietary service (not self-hosted)
|
||||
- Cloudflare sees all traffic (no end-to-end encryption to your origin)
|
||||
- Subject to Cloudflare's Terms of Service
|
||||
|
||||
#### Setup
|
||||
|
||||
1. **Create a Cloudflare Tunnel** in the [Zero Trust dashboard](https://one.dash.cloudflare.com/)
|
||||
|
||||
2. **Add a `cloudflared` service** to your `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
cloudflared:
|
||||
image: cloudflare/cloudflared:latest
|
||||
container_name: cloudflared-changemaker
|
||||
restart: unless-stopped
|
||||
command: tunnel run
|
||||
environment:
|
||||
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
|
||||
depends_on:
|
||||
- nginx
|
||||
networks:
|
||||
- changemaker-lite
|
||||
```
|
||||
|
||||
3. **Add your tunnel token** to `.env`:
|
||||
|
||||
```bash
|
||||
CLOUDFLARE_TUNNEL_TOKEN=your_tunnel_token_here
|
||||
```
|
||||
|
||||
4. **Configure public hostnames** in the Cloudflare dashboard, all pointing to `http://nginx:80`:
|
||||
|
||||
| Hostname | Service |
|
||||
|----------|---------|
|
||||
| `app.yourdomain.org` | `http://nginx:80` |
|
||||
| `api.yourdomain.org` | `http://nginx:80` |
|
||||
| `yourdomain.org` | `http://nginx:80` |
|
||||
| *(add more as needed)* | `http://nginx:80` |
|
||||
|
||||
5. **Start the tunnel:**
|
||||
|
||||
```bash
|
||||
docker compose up -d cloudflared
|
||||
```
|
||||
|
||||
!!! note
|
||||
The `cloudflared` service is not included in the default `docker-compose.yml`. Add it manually if you choose this method. The Newt service can be removed or left stopped.
|
||||
|
||||
---
|
||||
|
||||
### Option 3: Direct DNS + Reverse Proxy { #direct }
|
||||
|
||||
If your server has a public IP address (e.g., a VPS or dedicated server), you can point DNS directly to it and use nginx with SSL certificates.
|
||||
|
||||
**Advantages:**
|
||||
|
||||
- No tunnel overhead or third-party dependency
|
||||
- Full control over the network path
|
||||
- Lowest latency
|
||||
|
||||
**Disadvantages:**
|
||||
|
||||
- Requires a public IP and open ports (80, 443)
|
||||
- You manage SSL certificates yourself
|
||||
- Server IP is exposed
|
||||
|
||||
#### Setup
|
||||
|
||||
1. **Point DNS** for your domain and all subdomains to your server's IP:
|
||||
|
||||
```
|
||||
A yourdomain.org → YOUR_SERVER_IP
|
||||
A *.yourdomain.org → YOUR_SERVER_IP
|
||||
```
|
||||
|
||||
Or use individual A records for each subdomain if your DNS provider doesn't support wildcards.
|
||||
|
||||
2. **Open ports** 80 and 443 on your server's firewall.
|
||||
|
||||
3. **Install Certbot** (or another ACME client) for SSL certificates:
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt install certbot
|
||||
|
||||
# Get a wildcard certificate with DNS challenge
|
||||
sudo certbot certonly --manual --preferred-challenges dns \
|
||||
-d yourdomain.org -d '*.yourdomain.org'
|
||||
```
|
||||
|
||||
Alternatively, use the [Certbot Docker image](https://hub.docker.com/r/certbot/certbot/) or a Let's Encrypt companion container.
|
||||
|
||||
4. **Update nginx** to listen on 443 with your certificates. Add an SSL server block to `nginx/conf.d/ssl.conf`:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name app.yourdomain.org;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://changemaker-v2-admin:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
|
||||
# Repeat for api.yourdomain.org, media.yourdomain.org, etc.
|
||||
# Or use a single server block with $host matching
|
||||
```
|
||||
|
||||
5. **Mount certificates** into the nginx container via `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
nginx:
|
||||
volumes:
|
||||
- /etc/letsencrypt/live/yourdomain.org:/etc/nginx/ssl:ro
|
||||
```
|
||||
|
||||
6. **Set up auto-renewal** with a cron job or systemd timer:
|
||||
|
||||
```bash
|
||||
0 3 * * * certbot renew --quiet && docker compose restart nginx
|
||||
```
|
||||
|
||||
!!! tip "Traefik alternative"
|
||||
If you prefer automatic SSL and don't want to manage nginx SSL config manually, consider replacing nginx with [Traefik](https://traefik.io/). Traefik can auto-discover Docker containers and provision Let's Encrypt certificates automatically. This would require adapting the container labels and removing the nginx service.
|
||||
|
||||
---
|
||||
|
||||
### Option 4: Tailscale / WireGuard (Private Access) { #tailscale }
|
||||
|
||||
For deployments that should only be accessible to specific people (not the general public), a mesh VPN like Tailscale or plain WireGuard gives you private networking without exposing anything to the internet.
|
||||
|
||||
**Use cases:**
|
||||
|
||||
- Internal team deployments
|
||||
- Development/staging servers
|
||||
- Access from mobile devices without public exposure
|
||||
|
||||
#### Tailscale Setup
|
||||
|
||||
1. Install Tailscale on your server and client devices
|
||||
2. Access services via Tailscale IP (e.g., `http://100.x.x.x:3000`)
|
||||
3. Optionally use [Tailscale Funnel](https://tailscale.com/kb/1223/funnel/) to selectively expose specific services publicly
|
||||
|
||||
#### WireGuard Setup
|
||||
|
||||
1. Set up a WireGuard server on your host
|
||||
2. Connect client devices via WireGuard config
|
||||
3. Access services via the WireGuard interface IP
|
||||
|
||||
!!! note
|
||||
With private access methods, you may not need subdomain routing at all. Access services directly by port: `http://server-ip:3000` (admin), `http://server-ip:4000` (API), etc.
|
||||
|
||||
---
|
||||
|
||||
## Production Checklist
|
||||
|
||||
Before going live, verify each item:
|
||||
|
||||
### Security
|
||||
|
||||
- [ ] All placeholder passwords changed (`grep -c "REQUIRED_STRONG" .env` should return `0`)
|
||||
- [ ] `NODE_ENV=production` set in `.env`
|
||||
- [ ] `ENCRYPTION_KEY` set and differs from JWT secrets
|
||||
- [ ] `EMAIL_TEST_MODE=false` (unless you want MailHog in production)
|
||||
- [ ] `CORS_ORIGINS` includes your production domain
|
||||
- [ ] Admin password changed after first login
|
||||
- [ ] Redis password set (`REDIS_PASSWORD`)
|
||||
|
||||
### Networking
|
||||
|
||||
- [ ] DNS records configured for your domain and subdomains
|
||||
- [ ] SSL/TLS working (tunnel handles this, or manual certs)
|
||||
- [ ] All Pangolin resources set to "Not Protected" (if using Pangolin)
|
||||
- [ ] `curl https://api.yourdomain.org/api/health` returns JSON
|
||||
|
||||
### Services
|
||||
|
||||
- [ ] Core services running: `docker compose ps` shows `api`, `admin`, `v2-postgres`, `redis`, `nginx` healthy
|
||||
- [ ] Database migrated: `docker compose exec api npx prisma migrate deploy`
|
||||
- [ ] Database seeded: `docker compose exec api npx prisma db seed`
|
||||
- [ ] Admin GUI accessible at `https://app.yourdomain.org`
|
||||
|
||||
### Backups
|
||||
|
||||
- [ ] Backup script tested: `./scripts/backup.sh`
|
||||
- [ ] Backup cron job configured (see [Backups](#backups) below)
|
||||
- [ ] Restore procedure tested at least once
|
||||
|
||||
### Monitoring (Optional)
|
||||
|
||||
- [ ] Monitoring stack started: `docker compose --profile monitoring up -d`
|
||||
- [ ] Grafana accessible and dashboards loading
|
||||
- [ ] Alert rules configured in Alertmanager
|
||||
|
||||
---
|
||||
|
||||
## Backups
|
||||
|
||||
The included backup script dumps PostgreSQL databases, archives uploads, and optionally uploads to S3.
|
||||
|
||||
### Running a Backup
|
||||
|
||||
```bash
|
||||
./scripts/backup.sh
|
||||
```
|
||||
|
||||
This creates a timestamped directory under `./backups/` containing:
|
||||
|
||||
- `changemaker_v2.sql.gz` — Main PostgreSQL dump (compressed)
|
||||
- `listmonk.sql.gz` — Listmonk database dump (if running)
|
||||
- `uploads.tar.gz` — Media uploads archive
|
||||
- `manifest.json` — Backup metadata
|
||||
|
||||
### Options
|
||||
|
||||
```bash
|
||||
# Upload to S3 (requires AWS CLI + S3_BUCKET env var)
|
||||
./scripts/backup.sh --s3
|
||||
|
||||
# Custom retention (delete local backups older than N days)
|
||||
./scripts/backup.sh --retention 14
|
||||
```
|
||||
|
||||
### Automated Backups
|
||||
|
||||
Add a cron job for daily backups:
|
||||
|
||||
```bash
|
||||
# Edit crontab
|
||||
crontab -e
|
||||
|
||||
# Add daily backup at 3 AM
|
||||
0 3 * * * /path/to/changemaker.lite/scripts/backup.sh >> /var/log/changemaker-backup.log 2>&1
|
||||
|
||||
# With S3 upload
|
||||
0 3 * * * /path/to/changemaker.lite/scripts/backup.sh --s3 >> /var/log/changemaker-backup.log 2>&1
|
||||
```
|
||||
|
||||
### Restore
|
||||
|
||||
```bash
|
||||
# Restore main database
|
||||
gunzip -c backups/changemaker-v2-backup-TIMESTAMP/changemaker_v2.sql.gz | \
|
||||
docker compose exec -T v2-postgres psql -U changemaker changemaker_v2
|
||||
|
||||
# Restore Listmonk database
|
||||
gunzip -c backups/changemaker-v2-backup-TIMESTAMP/listmonk.sql.gz | \
|
||||
docker compose exec -T listmonk-db psql -U listmonk listmonk
|
||||
|
||||
# Restore uploads
|
||||
tar xzf backups/changemaker-v2-backup-TIMESTAMP/uploads.tar.gz -C ./
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring
|
||||
|
||||
The monitoring stack runs behind a Docker Compose profile and is not started by default.
|
||||
|
||||
### Starting the Monitoring Stack
|
||||
|
||||
```bash
|
||||
docker compose --profile monitoring up -d
|
||||
```
|
||||
|
||||
This starts:
|
||||
|
||||
| Service | Port | Purpose |
|
||||
|---------|------|---------|
|
||||
| Prometheus | 9090 | Metrics collection and queries |
|
||||
| Grafana | 3001 | Dashboards and visualization |
|
||||
| Alertmanager | 9093 | Alert routing and notifications |
|
||||
| cAdvisor | 8080 | Container resource metrics |
|
||||
| Node Exporter | 9100 | Host system metrics |
|
||||
| Redis Exporter | 9121 | Redis metrics |
|
||||
| Gotify | 8889 | Push notifications |
|
||||
|
||||
### Pre-configured Dashboards
|
||||
|
||||
Grafana includes 3 auto-provisioned dashboards:
|
||||
|
||||
1. **API Overview** — HTTP request rates, latency, error rates, active sessions
|
||||
2. **Infrastructure** — Container CPU/memory, PostgreSQL connections, Redis memory
|
||||
3. **Campaign Activity** — Email queue size, campaign sends, response submissions
|
||||
|
||||
### Custom Metrics
|
||||
|
||||
The API exposes 12 custom Prometheus metrics with the `cm_` prefix:
|
||||
|
||||
- `cm_api_uptime_seconds` — API uptime
|
||||
- `cm_email_queue_size` — BullMQ pending emails
|
||||
- `cm_active_canvass_sessions` — Active canvassing sessions
|
||||
- `cm_locations_total` — Total locations in database
|
||||
- And more — see `api/src/utils/metrics.ts`
|
||||
|
||||
### Alert Rules
|
||||
|
||||
Pre-configured alerts in `configs/prometheus/alerts.yml`:
|
||||
|
||||
- API down for more than 5 minutes
|
||||
- High error rate (>5% of requests returning 5xx)
|
||||
- Database connection failures
|
||||
- Redis connection failures
|
||||
- Email queue backlog
|
||||
- Disk space warnings
|
||||
|
||||
---
|
||||
|
||||
## Upgrading
|
||||
|
||||
### Pulling Updates
|
||||
|
||||
```bash
|
||||
# Pull latest code
|
||||
git pull origin v2
|
||||
|
||||
# Rebuild and restart containers
|
||||
docker compose build api admin
|
||||
docker compose up -d api admin
|
||||
|
||||
# Run any new migrations
|
||||
docker compose exec api npx prisma migrate deploy
|
||||
```
|
||||
|
||||
### Database Migrations
|
||||
|
||||
Always run migrations after pulling updates:
|
||||
|
||||
```bash
|
||||
docker compose exec api npx prisma migrate deploy
|
||||
```
|
||||
|
||||
!!! warning "Back up first"
|
||||
Always run `./scripts/backup.sh` before applying migrations in production. Migrations may alter table structures and are not easily reversible.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Production Issues
|
||||
|
||||
### Pangolin: 302 Redirects Instead of Content
|
||||
|
||||
**Symptom:** API returns 302 redirects to the Pangolin authentication page.
|
||||
|
||||
**Fix:** In the Pangolin dashboard, edit each resource and set Authentication to **Not Protected**.
|
||||
|
||||
### CORS Errors
|
||||
|
||||
**Symptom:** Browser console shows CORS errors when accessing the production domain.
|
||||
|
||||
**Fix:** Add your production `app.` subdomain to `CORS_ORIGINS` in `.env`, then `docker compose restart api`.
|
||||
|
||||
### Newt Won't Connect
|
||||
|
||||
Check in order:
|
||||
|
||||
1. **Credentials:** Verify `PANGOLIN_NEWT_ID` and `PANGOLIN_NEWT_SECRET` in `.env`
|
||||
2. **Endpoint:** Confirm `PANGOLIN_ENDPOINT` matches your Pangolin server URL
|
||||
3. **Logs:** `docker compose logs newt --tail 50`
|
||||
4. **Nginx running:** Newt depends on nginx — `docker compose ps nginx`
|
||||
5. **Network:** Ensure outbound HTTPS is not blocked by your firewall
|
||||
|
||||
### Services Unreachable via Tunnel
|
||||
|
||||
1. Verify nginx is running: `docker compose ps nginx`
|
||||
2. Test locally first: `curl http://localhost:4000/api/health`
|
||||
3. Check nginx logs: `docker compose logs nginx --tail 50`
|
||||
4. Verify DNS: `dig app.yourdomain.org` should point to your Pangolin server
|
||||
|
||||
See [Troubleshooting](../troubleshooting/index.md) for more common issues.
|
||||
40
mkdocs/docs/docs/features/index.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Feature Guides
|
||||
description: Explore Changemaker Lite's campaign, mapping, media, and communication features.
|
||||
icon: material/star-shooting
|
||||
---
|
||||
|
||||
# Feature Guides
|
||||
|
||||
Changemaker Lite bundles advocacy campaigns, geographic mapping, volunteer management, media hosting, and landing pages into a single self-hosted platform.
|
||||
|
||||
!!! warning "Under Construction"
|
||||
Detailed feature guides are being written. This page will link to in-depth documentation for each module.
|
||||
|
||||
## Modules Overview
|
||||
|
||||
### Influence (Advocacy Campaigns)
|
||||
|
||||
- **Campaigns** — create email advocacy campaigns targeting elected representatives
|
||||
- **Postal Code Lookup** — match constituents to their representatives via postal code
|
||||
- **Response Wall** — public wall where supporters share their stories, with admin moderation
|
||||
- **Email Queue** — BullMQ-powered async email delivery with tracking
|
||||
|
||||
### Map (Locations & Canvassing)
|
||||
|
||||
- **Locations** — manage addresses with multi-provider geocoding and CSV import/export
|
||||
- **Cuts** — define polygon regions for organizing canvassing areas
|
||||
- **Shifts** — schedule volunteer shifts with public signup
|
||||
- **Canvassing** — GPS-tracked door-to-door canvassing with visit recording
|
||||
|
||||
### Media Manager
|
||||
|
||||
- **Video Library** — upload, organize, and manage campaign videos
|
||||
- **Public Gallery** — share videos publicly with analytics tracking
|
||||
- **Scheduled Publishing** — automate video publish/unpublish with timezone support
|
||||
|
||||
### Pages & Templates
|
||||
|
||||
- **Landing Pages** — drag-and-drop page builder (GrapesJS)
|
||||
- **Email Templates** — reusable email templates with variable substitution
|
||||
- **MkDocs Export** — export pages to the documentation site
|
||||
503
mkdocs/docs/docs/getting-started/environment-variables.md
Normal file
@@ -0,0 +1,503 @@
|
||||
---
|
||||
title: Environment Variables
|
||||
description: Complete reference for every .env variable in Changemaker Lite.
|
||||
icon: material/file-cog
|
||||
---
|
||||
|
||||
# Environment Variables
|
||||
|
||||
Changemaker Lite uses a single `.env` file at the project root to configure all services. Copy the example file to get started:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
!!! danger "Security Essentials"
|
||||
- **Change every** `REQUIRED_STRONG_PASSWORD_CHANGE_THIS` value before starting services
|
||||
- **Generate secrets** with `openssl rand -hex 32` (or `-hex 16` where noted)
|
||||
- **Never** commit `.env` to version control
|
||||
- Use unique values for each secret — do not reuse JWT secrets as encryption keys
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
Variables are grouped by service. Each table marks whether a variable is **required** for a basic deployment or **optional** (has a sensible default or only needed for specific features).
|
||||
|
||||
| Symbol | Meaning |
|
||||
|--------|---------|
|
||||
| :material-alert-circle:{ .text-red } | Must be set before first run |
|
||||
| :material-tune-variant: | Has a working default; change for production |
|
||||
| :material-flask: | Feature flag — opt-in |
|
||||
|
||||
---
|
||||
|
||||
## General
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `NODE_ENV` | `development` | Set to `production` for production deployments. Controls logging, error detail, and security checks. |
|
||||
| `DOMAIN` | `cmlite.org` | Root domain. Used for nginx subdomain routing (`app.DOMAIN`, `api.DOMAIN`, etc.). The root domain serves the MkDocs documentation site; all application routes live under `app.DOMAIN`. |
|
||||
| `USER_ID` | `1000` | UID for container file ownership. Match your host user's UID (`id -u`). |
|
||||
| `GROUP_ID` | `1000` | GID for container file ownership. Match your host user's GID (`id -g`). |
|
||||
| `DOCKER_GROUP_ID` | `984` | GID of the `docker` group on the host. Needed for containers that access the Docker socket. Find with `getent group docker`. |
|
||||
|
||||
---
|
||||
|
||||
## PostgreSQL (Main Database) :material-alert-circle:{ .text-red }
|
||||
|
||||
The primary database for both the Express API and the Fastify Media API (shared).
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `V2_POSTGRES_USER` | `changemaker` | Database username. |
|
||||
| `V2_POSTGRES_PASSWORD` | — | :material-alert-circle:{ .text-red } **Must change.** Database password. |
|
||||
| `V2_POSTGRES_DB` | `changemaker_v2` | Database name. |
|
||||
| `V2_POSTGRES_PORT` | `5433` | Host port mapping. The container listens on `5432` internally. |
|
||||
|
||||
!!! tip "Connection string"
|
||||
The `DATABASE_URL` is constructed automatically inside Docker. If running locally, set:
|
||||
```
|
||||
DATABASE_URL=postgresql://changemaker:YOUR_PASSWORD@localhost:5433/changemaker_v2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## JWT Authentication :material-alert-circle:{ .text-red }
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `JWT_ACCESS_SECRET` | — | :material-alert-circle:{ .text-red } Secret for signing access tokens. Generate with `openssl rand -hex 32`. |
|
||||
| `JWT_REFRESH_SECRET` | — | :material-alert-circle:{ .text-red } Secret for signing refresh tokens. **Must differ** from the access secret. |
|
||||
| `JWT_ACCESS_EXPIRY` | `15m` | Access token lifetime. Short-lived by design. |
|
||||
| `JWT_REFRESH_EXPIRY` | `7d` | Refresh token lifetime. Tokens are rotated atomically on each refresh. |
|
||||
|
||||
---
|
||||
|
||||
## Encryption Key :material-alert-circle:{ .text-red }
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ENCRYPTION_KEY` | — | :material-alert-circle:{ .text-red } AES key for encrypting secrets stored in the database (SMTP passwords, API keys, etc.). Generate with `openssl rand -hex 32`. **Must not** reuse a JWT secret. Required in production (`NODE_ENV=production`). |
|
||||
|
||||
---
|
||||
|
||||
## Initial Admin Account :material-alert-circle:{ .text-red }
|
||||
|
||||
These credentials create the first super-admin user during database seeding (`npx prisma db seed`).
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `INITIAL_ADMIN_EMAIL` | `admin@cmlite.org` | Email address for the initial admin. |
|
||||
| `INITIAL_ADMIN_PASSWORD` | — | :material-alert-circle:{ .text-red } **Must change.** Must be 12+ characters with uppercase, lowercase, and a digit. Change this password after first login. |
|
||||
|
||||
---
|
||||
|
||||
## API Server
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `API_PORT` | `4000` | Host port for the Express API. |
|
||||
| `API_URL` | `http://localhost:4000` | Public URL of the API. Used for generating links in emails and QR codes. |
|
||||
| `CORS_ORIGINS` | `http://localhost:3000,http://localhost` | Comma-separated list of allowed CORS origins. **Add your production domain** (e.g., `https://app.yourdomain.org`) for production. |
|
||||
|
||||
!!! warning "Production CORS"
|
||||
If you deploy behind a tunnel (Pangolin, Cloudflare) and API requests fail with CORS errors, add your production `app.` subdomain here:
|
||||
```
|
||||
CORS_ORIGINS=https://app.betteredmonton.org,http://localhost:3000,http://localhost
|
||||
```
|
||||
Then restart the API: `docker compose restart api`
|
||||
|
||||
---
|
||||
|
||||
## Admin GUI
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ADMIN_PORT` | `3000` | Host port for the React admin dashboard. |
|
||||
| `ADMIN_URL` | `http://localhost:3000` | Public URL of the admin GUI. |
|
||||
|
||||
---
|
||||
|
||||
## Nginx Reverse Proxy
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `NGINX_HTTP_PORT` | `80` | HTTP port. All subdomains route through nginx. |
|
||||
| `NGINX_HTTPS_PORT` | `443` | HTTPS port. SSL is typically handled by the tunnel provider (Pangolin/Cloudflare). |
|
||||
|
||||
---
|
||||
|
||||
## Redis :material-alert-circle:{ .text-red }
|
||||
|
||||
Shared by rate limiting, BullMQ job queues, geocoding cache, and session data.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `REDIS_PASSWORD` | — | :material-alert-circle:{ .text-red } **Must change.** Redis requires authentication. |
|
||||
| `REDIS_URL` | `redis://:${REDIS_PASSWORD}@redis-changemaker:6379` | Full connection URL. Uses the password variable automatically. |
|
||||
|
||||
---
|
||||
|
||||
## Email / SMTP :material-tune-variant:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `SMTP_HOST` | `mailhog-changemaker` | SMTP server. Default points to the MailHog dev container. |
|
||||
| `SMTP_PORT` | `1025` | SMTP port. `1025` for MailHog, `587` for most production SMTP. |
|
||||
| `SMTP_USER` | *(empty)* | SMTP username. Not needed for MailHog. |
|
||||
| `SMTP_PASS` | *(empty)* | SMTP password. |
|
||||
| `SMTP_FROM` | `noreply@cmlite.org` | "From" address on outgoing emails. |
|
||||
| `SMTP_FROM_NAME` | `Changemaker Lite` | Display name for the "From" header. |
|
||||
| `EMAIL_TEST_MODE` | `true` | When `true`, all emails go to MailHog instead of real SMTP. **Set to `false` in production.** |
|
||||
| `TEST_EMAIL_RECIPIENT` | `admin@cmlite.org` | Catch-all recipient when test mode is on. |
|
||||
|
||||
!!! info "Development email"
|
||||
With `EMAIL_TEST_MODE=true`, all outgoing email is captured in MailHog at `http://localhost:8025`. No real emails are sent.
|
||||
|
||||
---
|
||||
|
||||
## Listmonk (Newsletters) :material-flask:
|
||||
|
||||
Listmonk handles newsletter/marketing campaigns. Sync with the main platform is opt-in.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `LISTMONK_PORT` | `9001` | Listmonk web UI port. |
|
||||
| `LISTMONK_DB_PORT` | `5432` | Listmonk's own PostgreSQL port (separate from the main DB). |
|
||||
| `LISTMONK_DB_USER` | `listmonk` | Listmonk database user. |
|
||||
| `LISTMONK_DB_PASSWORD` | — | :material-alert-circle:{ .text-red } Listmonk database password. |
|
||||
| `LISTMONK_DB_NAME` | `listmonk` | Listmonk database name. |
|
||||
| `LISTMONK_WEB_ADMIN_USER` | `admin` | Login for the Listmonk web dashboard. |
|
||||
| `LISTMONK_WEB_ADMIN_PASSWORD` | — | :material-alert-circle:{ .text-red } Password for the Listmonk web dashboard. |
|
||||
| `LISTMONK_API_USER` | `v2-api` | API user for programmatic access (auto-created by init container). |
|
||||
| `LISTMONK_API_TOKEN` | — | Token for API user. Generate with `openssl rand -hex 16`. |
|
||||
| `LISTMONK_ADMIN_USER` | `v2-api` | Same as `LISTMONK_API_USER` (used by the sync service). |
|
||||
| `LISTMONK_ADMIN_PASSWORD` | — | Same as `LISTMONK_API_TOKEN`. |
|
||||
| `LISTMONK_SYNC_ENABLED` | `false` | :material-flask: Set to `true` to sync participants/locations/users to Listmonk lists. |
|
||||
| `LISTMONK_PROXY_PORT` | `9002` | Nginx proxy port for Listmonk. |
|
||||
|
||||
??? example "Listmonk SMTP settings"
|
||||
Listmonk has its own SMTP configuration, separate from the main platform's:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `LISTMONK_SMTP_HOST` | `mailhog-changemaker` | SMTP host for Listmonk. |
|
||||
| `LISTMONK_SMTP_PORT` | `1025` | SMTP port. |
|
||||
| `LISTMONK_SMTP_USER` | *(empty)* | SMTP username. |
|
||||
| `LISTMONK_SMTP_PASSWORD` | *(empty)* | SMTP password. |
|
||||
| `LISTMONK_SMTP_TLS_TYPE` | `none` | TLS mode: `none`, `STARTTLS`, or `TLS`. |
|
||||
| `LISTMONK_SMTP_FROM` | `Changemaker Lite <noreply@cmlite.org>` | From address for newsletters. |
|
||||
|
||||
---
|
||||
|
||||
## Represent API (Canadian Electoral Data)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `REPRESENT_API_URL` | `https://represent.opennorth.ca` | OpenNorth Represent API endpoint. Used for postal code → representative lookups. No API key required. |
|
||||
|
||||
---
|
||||
|
||||
## NocoDB (Data Browser) :material-tune-variant:
|
||||
|
||||
Read-only database browser. Useful for inspecting data without SQL.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `NOCODB_V2_PORT` / `NOCODB_PORT` | `8091` | Host port for the NocoDB web UI. |
|
||||
| `NOCODB_URL` | `http://changemaker-v2-nocodb:8080` | Internal Docker URL. |
|
||||
| `NC_ADMIN_EMAIL` | `admin@cmlite.org` | NocoDB admin email. |
|
||||
| `NC_ADMIN_PASSWORD` | — | :material-alert-circle:{ .text-red } NocoDB admin password. |
|
||||
|
||||
---
|
||||
|
||||
## Media Manager :material-flask:
|
||||
|
||||
Video library with upload, analytics, scheduling, and a public gallery.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ENABLE_MEDIA_FEATURES` | `false` | :material-flask: Set to `true` to enable the media system. |
|
||||
| `MEDIA_API_PORT` | `4100` | Fastify media API port. |
|
||||
| `MEDIA_API_PUBLIC_URL` | `http://media-api:4100` | Internal URL for the media API container. |
|
||||
| `MEDIA_ROOT` | `/media/library` | Path to the video library inside the container. |
|
||||
| `MEDIA_UPLOADS` | `/media/uploads` | Path for upload processing. |
|
||||
| `MAX_UPLOAD_SIZE_GB` | `10` | Maximum single-file upload size in gigabytes. |
|
||||
| `VIDEO_PLAYER_DEBUG` | `false` | Enable verbose video player logging. |
|
||||
|
||||
??? example "Analytics & scheduling settings"
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `VIDEO_ANALYTICS_RETENTION_DAYS` | `90` | Days to retain analytics data. GDPR-compliant with IP hashing. |
|
||||
| `VIDEO_ANALYTICS_IP_HASHING_ENABLED` | `true` | Hash viewer IPs for privacy. |
|
||||
| `VIDEO_SCHEDULE_DEFAULT_TIMEZONE` | `UTC` | Default timezone for scheduled publishing. |
|
||||
| `VIDEO_SCHEDULE_NOTIFICATION_ENABLED` | `true` | Notify on scheduled publish/unpublish. |
|
||||
| `VIDEO_PREVIEW_LINK_EXPIRY_HOURS` | `24` | Preview link JWT expiry (hours). |
|
||||
|
||||
---
|
||||
|
||||
## Gitea (Git Hosting) :material-tune-variant:
|
||||
|
||||
Self-hosted Git repository. Optional service.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `GITEA_PORT` / `GITEA_WEB_PORT` | `3030` | Gitea web UI port. |
|
||||
| `GITEA_SSH_PORT` | `2222` | Gitea SSH port for git operations. |
|
||||
| `GITEA_DB_TYPE` | `mysql` | Database type (Gitea uses its own MySQL). |
|
||||
| `GITEA_DB_HOST` | `gitea-db:3306` | Internal database host. |
|
||||
| `GITEA_DB_NAME` | `gitea` | Database name. |
|
||||
| `GITEA_DB_USER` | `gitea` | Database user. |
|
||||
| `GITEA_DB_PASSWD` | — | :material-alert-circle:{ .text-red } Gitea database password. |
|
||||
| `GITEA_DB_ROOT_PASSWORD` | — | :material-alert-circle:{ .text-red } MySQL root password for Gitea. |
|
||||
| `GITEA_ROOT_URL` | `https://git.cmlite.org` | Public-facing URL for Gitea. |
|
||||
| `GITEA_DOMAIN` | `git.cmlite.org` | Domain used in git clone URLs. |
|
||||
|
||||
---
|
||||
|
||||
## n8n (Workflow Automation) :material-tune-variant:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `N8N_PORT` | `5678` | n8n web UI port. |
|
||||
| `N8N_HOST` | `n8n.cmlite.org` | Public hostname for n8n. |
|
||||
| `N8N_ENCRYPTION_KEY` | — | :material-alert-circle:{ .text-red } Encryption key for n8n credentials storage. |
|
||||
| `N8N_USER_EMAIL` | `admin@example.com` | Initial n8n admin email. |
|
||||
| `N8N_USER_PASSWORD` | — | :material-alert-circle:{ .text-red } Initial n8n admin password. |
|
||||
| `GENERIC_TIMEZONE` | `UTC` | Timezone for n8n cron triggers. |
|
||||
|
||||
---
|
||||
|
||||
## MkDocs (Documentation)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `MKDOCS_PORT` | `4003` | MkDocs dev server port (live preview). |
|
||||
| `MKDOCS_SITE_SERVER_PORT` | `4001` | MkDocs static site server port. |
|
||||
| `BASE_DOMAIN` | `https://cmlite.org` | Base URL for generated documentation links. |
|
||||
| `MKDOCS_PREVIEW_URL` | `http://mkdocs:8000` | Internal container URL. |
|
||||
| `MKDOCS_DOCS_PATH` | `/mkdocs/docs` | Documentation source directory inside the container. |
|
||||
|
||||
---
|
||||
|
||||
## Code Server (Web IDE)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `CODE_SERVER_PORT` | `8888` | Code Server web UI port. |
|
||||
| `CODE_SERVER_URL` | `http://code-server:8080` | Internal container URL. |
|
||||
| `USER_NAME` | `coder` | User account inside the Code Server container. |
|
||||
|
||||
---
|
||||
|
||||
## Homepage (Service Dashboard)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `HOMEPAGE_PORT` | `3010` | Homepage web UI port. |
|
||||
| `HOMEPAGE_EMBED_PORT` | `8887` | Port for iframe embedding in admin. |
|
||||
| `HOMEPAGE_VAR_BASE_URL` | `http://localhost` | Base URL used in Homepage service links. |
|
||||
|
||||
---
|
||||
|
||||
## Mini QR (QR Code Generator)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `MINI_QR_PORT` | `8089` | Mini QR direct access port. |
|
||||
| `MINI_QR_URL` | `http://mini-qr:8080` | Internal container URL. |
|
||||
| `MINI_QR_EMBED_PORT` | `8885` | Port for iframe embedding (walk sheets, cut exports). |
|
||||
|
||||
---
|
||||
|
||||
## Excalidraw (Whiteboard)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `EXCALIDRAW_PORT` | `8090` | Excalidraw web UI port. |
|
||||
| `EXCALIDRAW_URL` | `http://excalidraw-changemaker:80` | Internal container URL. |
|
||||
| `EXCALIDRAW_EMBED_PORT` | `8886` | Port for iframe embedding. |
|
||||
| `EXCALIDRAW_WS_URL` | `wss://draw.cmlite.org` | WebSocket URL for real-time collaboration. |
|
||||
|
||||
---
|
||||
|
||||
## MailHog (Development Email)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `MAILHOG_SMTP_PORT` | `1025` | SMTP port for capturing emails. |
|
||||
| `MAILHOG_WEB_PORT` | `8025` | Web UI to view captured emails. |
|
||||
|
||||
---
|
||||
|
||||
## NAR (National Address Register) :material-tune-variant:
|
||||
|
||||
Canadian address data import for geographic canvassing.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `NAR_DATA_DIR` | `/data` | Path to extracted NAR data inside the container. Expects `YYYYMM/Addresses/` and `YYYYMM/Locations/` subdirectories. Mount via `./data:/data:ro` in Docker Compose. |
|
||||
|
||||
Download NAR data from [Statistics Canada](https://www150.statcan.gc.ca/n1/pub/46-26-0002/462600022022001-eng.htm).
|
||||
|
||||
---
|
||||
|
||||
## Geocoding :material-tune-variant:
|
||||
|
||||
Multi-provider geocoding for address resolution. Works out of the box with free providers; optional paid providers improve accuracy.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `MAPBOX_API_KEY` | *(empty)* | Mapbox API key for improved geocoding accuracy. Free tier: 100k requests/month. [Sign up](https://www.mapbox.com/pricing). |
|
||||
| `GEOCODING_RATE_LIMIT_MS` | `1100` | Delay between requests to free providers (ms). Respects rate limits. |
|
||||
| `GEOCODING_CACHE_ENABLED` | `true` | Enable Redis-backed geocoding cache. |
|
||||
| `GEOCODING_CACHE_TTL_HOURS` | `24` | Cache lifetime in hours. |
|
||||
| `GOOGLE_MAPS_API_KEY` | *(empty)* | Google Maps API key. Most accurate but $0.005/request after free tier. |
|
||||
| `GOOGLE_MAPS_ENABLED` | `false` | Enable Google Maps as a geocoding provider. |
|
||||
| `GEOCODING_PARALLEL_ENABLED` | `true` | Enable parallel geocoding for bulk imports (~10x speedup). |
|
||||
| `GEOCODING_BATCH_SIZE` | `10` | Number of concurrent geocoding requests during bulk operations. |
|
||||
| `BULK_GEOCODE_ENABLED` | `true` | Enable bulk re-geocoding from the admin UI. |
|
||||
| `BULK_GEOCODE_MAX_BATCH` | `5000` | Maximum locations per bulk geocoding run. |
|
||||
|
||||
---
|
||||
|
||||
## Overpass / Area Import :material-tune-variant:
|
||||
|
||||
OpenStreetMap data import for map enrichment.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `OVERPASS_API_URL` | `https://overpass-api.de/api/interpreter` | Overpass API endpoint. Use a private instance for heavy usage. |
|
||||
| `OVERPASS_MIN_DELAY_MS` | `30000` | Minimum delay between requests (ms). The public API requires 30 seconds. |
|
||||
| `AREA_IMPORT_MAX_GRID_POINTS` | `500` | Maximum reverse-geocode grid points per area import. |
|
||||
|
||||
---
|
||||
|
||||
## Pangolin Tunnel :material-tune-variant:
|
||||
|
||||
Expose services to the internet without port forwarding, using a self-hosted [Pangolin](https://github.com/fosrl/pangolin) instance.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PANGOLIN_API_URL` | `https://api.bnkserve.org/v1` | Pangolin server API endpoint. |
|
||||
| `PANGOLIN_API_KEY` | *(empty)* | API key for Pangolin management. |
|
||||
| `PANGOLIN_ORG_ID` | *(empty)* | Organization ID in Pangolin. |
|
||||
| `PANGOLIN_SITE_ID` | *(empty)* | Site ID (populated after setup via admin GUI). |
|
||||
| `PANGOLIN_ENDPOINT` | `https://pangolin.bnkserve.org` | Pangolin tunnel endpoint. |
|
||||
| `PANGOLIN_NEWT_ID` | *(empty)* | Newt client ID (populated after setup). |
|
||||
| `PANGOLIN_NEWT_SECRET` | *(empty)* | Newt client secret (populated after setup). |
|
||||
|
||||
!!! tip "Setup flow"
|
||||
Configure the tunnel from **Admin → Settings → Pangolin**. The setup wizard walks you through creating a site, copying credentials, and connecting the Newt container. See [Deployment](../deployment/index.md) for the full guide.
|
||||
|
||||
---
|
||||
|
||||
## Monitoring :material-flask:
|
||||
|
||||
These services are behind the `monitoring` Docker Compose profile. Start them with:
|
||||
|
||||
```bash
|
||||
docker compose --profile monitoring up -d
|
||||
```
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PROMETHEUS_PORT` | `9090` | Prometheus web UI / query port. |
|
||||
| `GRAFANA_PORT` | `3001` | Grafana dashboard port. |
|
||||
| `GRAFANA_ADMIN_PASSWORD` | `admin` | :material-tune-variant: Change in production. |
|
||||
| `GRAFANA_ROOT_URL` | `http://localhost:3001` | Public URL for Grafana (used in links). |
|
||||
| `CADVISOR_PORT` | `8080` | cAdvisor container metrics port. |
|
||||
| `NODE_EXPORTER_PORT` | `9100` | Prometheus node exporter port. |
|
||||
| `REDIS_EXPORTER_PORT` | `9121` | Redis metrics exporter port. |
|
||||
| `ALERTMANAGER_PORT` | `9093` | Alertmanager web UI port. |
|
||||
| `GOTIFY_PORT` | `8889` | Gotify push notification port. |
|
||||
| `GOTIFY_ADMIN_USER` | `admin` | Gotify admin username. |
|
||||
| `GOTIFY_ADMIN_PASSWORD` | `admin` | :material-tune-variant: Change in production. |
|
||||
|
||||
---
|
||||
|
||||
## Generating Secrets
|
||||
|
||||
Use these commands to generate all required secrets at once:
|
||||
|
||||
```bash
|
||||
# JWT secrets (two separate values)
|
||||
echo "JWT_ACCESS_SECRET=$(openssl rand -hex 32)"
|
||||
echo "JWT_REFRESH_SECRET=$(openssl rand -hex 32)"
|
||||
|
||||
# Encryption key (must differ from JWT secrets)
|
||||
echo "ENCRYPTION_KEY=$(openssl rand -hex 32)"
|
||||
|
||||
# Database and Redis passwords
|
||||
echo "V2_POSTGRES_PASSWORD=$(openssl rand -hex 24)"
|
||||
echo "REDIS_PASSWORD=$(openssl rand -hex 24)"
|
||||
|
||||
# Listmonk
|
||||
echo "LISTMONK_DB_PASSWORD=$(openssl rand -hex 24)"
|
||||
echo "LISTMONK_WEB_ADMIN_PASSWORD=$(openssl rand -hex 16)"
|
||||
LISTMONK_TOKEN=$(openssl rand -hex 16)
|
||||
echo "LISTMONK_API_TOKEN=$LISTMONK_TOKEN"
|
||||
echo "LISTMONK_ADMIN_PASSWORD=$LISTMONK_TOKEN"
|
||||
|
||||
# Supporting services
|
||||
echo "GITEA_DB_PASSWD=$(openssl rand -hex 24)"
|
||||
echo "GITEA_DB_ROOT_PASSWORD=$(openssl rand -hex 24)"
|
||||
echo "N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)"
|
||||
echo "N8N_USER_PASSWORD=$(openssl rand -hex 16)"
|
||||
echo "NC_ADMIN_PASSWORD=$(openssl rand -hex 16)"
|
||||
echo "INITIAL_ADMIN_PASSWORD=$(openssl rand -base64 18)"
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Copy the output and paste the values into your `.env` file. The `INITIAL_ADMIN_PASSWORD` uses base64 encoding to ensure it contains uppercase, lowercase, and digits (meeting the password policy).
|
||||
|
||||
---
|
||||
|
||||
## Minimal vs Full Deployment
|
||||
|
||||
=== "Minimal (Core Only)"
|
||||
|
||||
For a basic deployment with campaigns, map, and admin:
|
||||
|
||||
```bash title="Required variables"
|
||||
V2_POSTGRES_PASSWORD=...
|
||||
REDIS_PASSWORD=...
|
||||
JWT_ACCESS_SECRET=...
|
||||
JWT_REFRESH_SECRET=...
|
||||
ENCRYPTION_KEY=...
|
||||
INITIAL_ADMIN_PASSWORD=...
|
||||
```
|
||||
|
||||
```bash title="Start services"
|
||||
docker compose up -d v2-postgres redis api admin
|
||||
```
|
||||
|
||||
=== "Full Stack"
|
||||
|
||||
For the complete platform including media, newsletters, monitoring, and all services:
|
||||
|
||||
```bash title="Additional variables needed"
|
||||
# Everything above, plus:
|
||||
ENABLE_MEDIA_FEATURES=true
|
||||
LISTMONK_SYNC_ENABLED=true
|
||||
LISTMONK_DB_PASSWORD=...
|
||||
LISTMONK_WEB_ADMIN_PASSWORD=...
|
||||
LISTMONK_API_TOKEN=...
|
||||
NC_ADMIN_PASSWORD=...
|
||||
GITEA_DB_PASSWD=...
|
||||
GITEA_DB_ROOT_PASSWORD=...
|
||||
N8N_ENCRYPTION_KEY=...
|
||||
N8N_USER_PASSWORD=...
|
||||
EMAIL_TEST_MODE=false
|
||||
SMTP_HOST=smtp.your-provider.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=you@example.com
|
||||
SMTP_PASS=your-smtp-password
|
||||
```
|
||||
|
||||
```bash title="Start services"
|
||||
docker compose up -d
|
||||
docker compose --profile monitoring up -d
|
||||
```
|
||||
146
mkdocs/docs/docs/getting-started/index.md
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: Install and configure Changemaker Lite from scratch.
|
||||
icon: material/rocket-launch
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
This guide walks you through installing Changemaker Lite, running your first deployment, and logging into the admin dashboard.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Docker** 24+ and **Docker Compose** v2
|
||||
- **OpenSSL** (for secret generation)
|
||||
- A Linux server (Ubuntu 22.04+ recommended) or macOS for development
|
||||
- At least 2 GB RAM and 10 GB disk space
|
||||
- A domain name (optional, but recommended for production)
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://gitea.bnkops.com/admin/changemaker.lite
|
||||
cd changemaker.lite
|
||||
git checkout v2
|
||||
```
|
||||
|
||||
### 2. Run the Configuration Wizard
|
||||
|
||||
The fastest way to get a working `.env` file is the interactive configuration wizard:
|
||||
|
||||
```bash
|
||||
bash config.sh
|
||||
```
|
||||
|
||||
The wizard walks you through each step:
|
||||
|
||||
| Step | What it does |
|
||||
|------|-------------|
|
||||
| **Prerequisites check** | Verifies Docker, Docker Compose, and OpenSSL are installed |
|
||||
| **Domain** | Sets your root domain and updates all subdomain references (nginx, Gitea, n8n, MkDocs, etc.) |
|
||||
| **Admin credentials** | Prompts for the initial super-admin email and password (enforces 12+ chars, uppercase, lowercase, digit) |
|
||||
| **Secret generation** | Auto-generates 16 unique secrets — JWT keys, encryption key, database passwords, Redis password, API tokens |
|
||||
| **SMTP** | Optionally configures production SMTP (defaults to MailHog for development) |
|
||||
| **Feature flags** | Enable/disable Media Manager and Listmonk newsletter sync |
|
||||
| **Pangolin tunnel** | Optionally configures tunnel credentials for public access |
|
||||
| **CORS** | Auto-sets allowed origins based on your domain |
|
||||
| **Homepage** | Generates `configs/homepage/services.yaml` with all service links for your domain |
|
||||
| **Permissions** | Creates required directories and sets container-friendly permissions |
|
||||
|
||||
After completion you'll have a fully populated `.env` with no placeholder passwords remaining.
|
||||
|
||||
!!! tip "Already have a `.env`?"
|
||||
If a `.env` file exists, the wizard offers to back it up before creating a fresh one, or update values in place.
|
||||
|
||||
??? example "What the wizard looks like"
|
||||
```
|
||||
██████╗██╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ███████╗
|
||||
██╔════╝██║ ██║██╔══██╗████╗ ██║██╔════╝ ██╔════╝
|
||||
██║ ███████║███████║██╔██╗ ██║██║ ███╗█████╗
|
||||
██║ ██╔══██║██╔══██║██║╚██╗██║██║ ██║██╔══╝
|
||||
╚██████╗██║ ██║██║ ██║██║ ╚████║╚██████╔╝███████╗
|
||||
╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝
|
||||
|
||||
███╗ ███╗ █████╗ ██╗ ██╗███████╗██████╗
|
||||
████╗ ████║██╔══██╗██║ ██╔╝██╔════╝██╔══██╗
|
||||
██╔████╔██║███████║█████╔╝ █████╗ ██████╔╝
|
||||
██║╚██╔╝██║██╔══██║██╔═██╗ ██╔══╝ ██╔══██╗
|
||||
██║ ╚═╝ ██║██║ ██║██║ ██╗███████╗██║ ██║
|
||||
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
|
||||
V2 Configuration Wizard
|
||||
|
||||
[INFO] This wizard will create your .env file, generate secure secrets,
|
||||
[INFO] and prepare your system to run the full Changemaker Lite stack.
|
||||
```
|
||||
|
||||
### 3. Manual Setup (Alternative)
|
||||
|
||||
If you prefer to configure things by hand:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Then edit `.env` and at minimum set these values:
|
||||
|
||||
```bash
|
||||
V2_POSTGRES_PASSWORD=<strong password>
|
||||
REDIS_PASSWORD=<strong password>
|
||||
JWT_ACCESS_SECRET=<openssl rand -hex 32>
|
||||
JWT_REFRESH_SECRET=<openssl rand -hex 32>
|
||||
ENCRYPTION_KEY=<openssl rand -hex 32>
|
||||
INITIAL_ADMIN_PASSWORD=<12+ chars, mixed case + digit>
|
||||
```
|
||||
|
||||
See [Environment Variables](environment-variables.md) for every available option.
|
||||
|
||||
### 4. Start Services
|
||||
|
||||
```bash
|
||||
# Start core services
|
||||
docker compose up -d v2-postgres redis api admin
|
||||
|
||||
# Run database migrations and seed the initial admin account
|
||||
docker compose exec api npx prisma migrate deploy
|
||||
docker compose exec api npx prisma db seed
|
||||
```
|
||||
|
||||
### 5. Log In
|
||||
|
||||
Open **http://localhost:3000** and sign in with the admin email and password you configured.
|
||||
|
||||
!!! warning "Change your password"
|
||||
If you used the wizard's generated password, change it immediately from the admin dashboard.
|
||||
|
||||
## Optional Services
|
||||
|
||||
Once the core is running, add more services as needed:
|
||||
|
||||
```bash
|
||||
# Reverse proxy (required for subdomain routing)
|
||||
docker compose up -d nginx
|
||||
|
||||
# Video library
|
||||
docker compose up -d media-api
|
||||
|
||||
# Newsletters
|
||||
docker compose up -d listmonk-app
|
||||
|
||||
# Service dashboard
|
||||
docker compose up -d homepage
|
||||
|
||||
# All services at once
|
||||
docker compose up -d
|
||||
|
||||
# Monitoring stack (Prometheus, Grafana, Alertmanager)
|
||||
docker compose --profile monitoring up -d
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Environment Variables](environment-variables.md) — complete `.env` reference with every configurable option
|
||||
- [Feature Guides](../features/index.md) — explore campaigns, map, and media
|
||||
- [Deployment](../deployment/index.md) — production setup with SSL and tunneling
|
||||
- [Architecture](../architecture/index.md) — understand the system design
|
||||
146
mkdocs/docs/docs/index.md
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
title: Documentation
|
||||
description: Changemaker Lite documentation hub — guides for users, admins, and operators.
|
||||
icon: material/book-open-variant
|
||||
---
|
||||
|
||||
# Documentation
|
||||
|
||||
Welcome to the Changemaker Lite documentation. Whether you're a campaign volunteer, an admin managing operations, or a sysadmin deploying the platform — start here.
|
||||
|
||||
---
|
||||
|
||||
## Use the Platform
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-rocket-launch:{ .lg .middle } **Getting Started**
|
||||
|
||||
---
|
||||
|
||||
Install Changemaker Lite, create your first admin account, and explore the dashboard.
|
||||
|
||||
[:octicons-arrow-right-24: Getting Started](getting-started/index.md)
|
||||
|
||||
- :material-star-shooting:{ .lg .middle } **Feature Guides**
|
||||
|
||||
---
|
||||
|
||||
Campaigns, email advocacy, response walls, map locations, landing pages, and media.
|
||||
|
||||
[:octicons-arrow-right-24: Feature Guides](features/index.md)
|
||||
|
||||
- :material-shield-account:{ .lg .middle } **Administration**
|
||||
|
||||
---
|
||||
|
||||
User management, roles and permissions, site settings, email templates, and newsletters.
|
||||
|
||||
[:octicons-arrow-right-24: Administration](admin/index.md)
|
||||
|
||||
- :material-walk:{ .lg .middle } **Volunteer Guide**
|
||||
|
||||
---
|
||||
|
||||
Sign up for shifts, use the canvassing map, record visits, and track your activity.
|
||||
|
||||
[:octicons-arrow-right-24: Volunteer Guide](volunteer/index.md)
|
||||
|
||||
</div>
|
||||
|
||||
## Deploy & Operate
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-docker:{ .lg .middle } **Deployment**
|
||||
|
||||
---
|
||||
|
||||
Docker Compose setup, environment variables, SSL/TLS, backups, and production checklist.
|
||||
|
||||
[:octicons-arrow-right-24: Deployment](deployment/index.md)
|
||||
|
||||
- :material-sitemap:{ .lg .middle } **Architecture**
|
||||
|
||||
---
|
||||
|
||||
Dual API design, database schema, authentication flow, and system diagram.
|
||||
|
||||
[:octicons-arrow-right-24: Architecture](architecture/index.md)
|
||||
|
||||
- :material-server-network:{ .lg .middle } **Services**
|
||||
|
||||
---
|
||||
|
||||
Nginx routing, Redis, PostgreSQL, Listmonk, MkDocs, Gitea, NocoDB, and more.
|
||||
|
||||
[:octicons-arrow-right-24: Services](services/index.md)
|
||||
|
||||
- :material-chart-line:{ .lg .middle } **Monitoring**
|
||||
|
||||
---
|
||||
|
||||
Prometheus metrics, Grafana dashboards, Alertmanager rules, and health checks.
|
||||
|
||||
[:octicons-arrow-right-24: Monitoring](../blog/index.md){ .md-button .md-button--secondary } *Coming soon*
|
||||
|
||||
</div>
|
||||
|
||||
## Reference
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-api:{ .lg .middle } **API Reference**
|
||||
|
||||
---
|
||||
|
||||
REST endpoints for auth, campaigns, locations, shifts, media, and more.
|
||||
|
||||
[:octicons-arrow-right-24: API Reference](api/index.md)
|
||||
|
||||
- :material-bug:{ .lg .middle } **Troubleshooting**
|
||||
|
||||
---
|
||||
|
||||
Common errors, CORS issues, database problems, tunnel debugging, and FAQ.
|
||||
|
||||
[:octicons-arrow-right-24: Troubleshooting](troubleshooting/index.md)
|
||||
|
||||
- :material-lock-check:{ .lg .middle } **Security**
|
||||
|
||||
---
|
||||
|
||||
Password policy, rate limiting, token rotation, encryption, and audit report.
|
||||
|
||||
[:octicons-arrow-right-24: Security](deployment/index.md){ .md-button .md-button--secondary } *See Deployment*
|
||||
|
||||
- :material-source-pull:{ .lg .middle } **Contributing**
|
||||
|
||||
---
|
||||
|
||||
Development setup, code style, git workflow, and pull request guidelines.
|
||||
|
||||
[:octicons-arrow-right-24: Contributing](../blog/index.md){ .md-button .md-button--secondary } *Coming soon*
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Platform at a Glance
|
||||
|
||||
| Component | Technology | Purpose |
|
||||
|-----------|-----------|---------|
|
||||
| **Main API** | Express.js + Prisma | Auth, campaigns, map, shifts, pages, email |
|
||||
| **Media API** | Fastify + Prisma | Video library, analytics, upload, scheduling |
|
||||
| **Admin GUI** | React + Ant Design + Zustand | Dashboard for admins and organizers |
|
||||
| **Database** | PostgreSQL 16 | Single shared database for both APIs |
|
||||
| **Cache** | Redis | Rate limiting, BullMQ jobs, geocoding queue |
|
||||
| **Proxy** | Nginx | Subdomain routing, security headers, SSL |
|
||||
| **Tunnel** | Pangolin + Newt | Expose services without port forwarding |
|
||||
| **Monitoring** | Prometheus + Grafana | Metrics, dashboards, alerts |
|
||||
|
||||
!!! tip "New here?"
|
||||
Start with the [Getting Started](getting-started/index.md) guide to have the platform running in under 30 minutes.
|
||||
|
||||
!!! info "Looking for the source?"
|
||||
Changemaker Lite is 100% open source. Browse the code on [Gitea](https://gitea.bnkops.com/admin/changemaker.lite).
|
||||
361
mkdocs/docs/docs/services/index.md
Normal file
@@ -0,0 +1,361 @@
|
||||
---
|
||||
title: Services
|
||||
description: All services that make up the Changemaker Lite platform — configuration, ports, and links to upstream docs.
|
||||
icon: material/server-network
|
||||
---
|
||||
|
||||
# Services
|
||||
|
||||
Changemaker Lite orchestrates 20+ services via Docker Compose. This page is your map to every service: what it does, how to reach it, and where to find its upstream documentation.
|
||||
|
||||
---
|
||||
|
||||
## Core Platform
|
||||
|
||||
The essential services that power the application.
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-api:{ .lg .middle } **Express API**
|
||||
|
||||
---
|
||||
|
||||
Main V2 API server. Handles authentication, campaigns, map, shifts, pages, email, and all business logic. Prisma ORM with PostgreSQL.
|
||||
|
||||
**Port:** `4000` · **Container:** `changemaker-v2-api`
|
||||
|
||||
[:octicons-arrow-right-24: API Reference](../api/index.md)
|
||||
|
||||
- :material-video:{ .lg .middle } **Fastify Media API**
|
||||
|
||||
---
|
||||
|
||||
Video library server. Upload, metadata extraction (FFprobe), analytics, scheduled publishing, and public gallery. Shares the same PostgreSQL database.
|
||||
|
||||
**Port:** `4100` · **Container:** `changemaker-media-api`
|
||||
|
||||
[:octicons-arrow-right-24: Media Guide](../features/index.md)
|
||||
|
||||
- :material-react:{ .lg .middle } **Admin GUI**
|
||||
|
||||
---
|
||||
|
||||
React single-page application (Vite + Ant Design + Zustand). Serves the admin dashboard, public campaign pages, volunteer portal, and media gallery — all from one build.
|
||||
|
||||
**Port:** `3000` · **Container:** `changemaker-v2-admin`
|
||||
|
||||
[:octicons-arrow-right-24: Feature Guides](../features/index.md)
|
||||
|
||||
- :material-database:{ .lg .middle } **PostgreSQL 16**
|
||||
|
||||
---
|
||||
|
||||
Primary database shared by both APIs. Managed by Prisma migrations. Contains 30+ tables covering users, campaigns, locations, shifts, media, and more.
|
||||
|
||||
**Port:** `5433` (host) / `5432` (container) · **Container:** `changemaker-v2-postgres`
|
||||
|
||||
[:octicons-arrow-right-24: PostgreSQL Docs](https://www.postgresql.org/docs/16/){ target="_blank" }
|
||||
|
||||
- :material-memory:{ .lg .middle } **Redis**
|
||||
|
||||
---
|
||||
|
||||
In-memory store for rate limiting, BullMQ job queues (email, video scheduling), geocoding cache, and session data. Requires authentication.
|
||||
|
||||
**Port:** `6379` · **Container:** `redis-changemaker`
|
||||
|
||||
[:octicons-arrow-right-24: Redis Docs](https://redis.io/docs/){ target="_blank" }
|
||||
|
||||
- :material-web:{ .lg .middle } **Nginx**
|
||||
|
||||
---
|
||||
|
||||
Reverse proxy handling all subdomain routing (`app.`, `api.`, `media.`, `docs.`, etc.). Includes security headers (HSTS, CSP, Permissions-Policy) and WebSocket support.
|
||||
|
||||
**Port:** `80` / `443` · **Container:** `changemaker-v2-nginx`
|
||||
|
||||
[:octicons-arrow-right-24: Nginx Docs](https://nginx.org/en/docs/){ target="_blank" }
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Communication & Email
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-email-newsletter:{ .lg .middle } **Listmonk**
|
||||
|
||||
---
|
||||
|
||||
Self-hosted newsletter and mailing list manager. Drop-in replacement for Mailchimp. Opt-in sync with the main platform imports participants, locations, and users as subscriber lists.
|
||||
|
||||
**Port:** `9001` · **Container:** `listmonk-app` · **Subdomain:** `listmonk.DOMAIN`
|
||||
|
||||
[:octicons-arrow-right-24: Listmonk Docs](https://listmonk.app/docs/){ target="_blank" }
|
||||
|
||||
- :material-email-check:{ .lg .middle } **MailHog**
|
||||
|
||||
---
|
||||
|
||||
Email capture for development. All outgoing email is intercepted and displayed in a web UI when `EMAIL_TEST_MODE=true`. No real emails are sent.
|
||||
|
||||
**Port:** `8025` (web) / `1025` (SMTP) · **Container:** `mailhog-changemaker` · **Subdomain:** `mail.DOMAIN`
|
||||
|
||||
[:octicons-arrow-right-24: MailHog GitHub](https://github.com/mailhog/MailHog){ target="_blank" }
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Content & Editing
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-book-open-page-variant:{ .lg .middle } **MkDocs**
|
||||
|
||||
---
|
||||
|
||||
Material-themed documentation site with full-text search, blog, social cards, and Jinja2 template overrides. Two containers: live preview (dev) and static site (production).
|
||||
|
||||
**Port:** `4003` (dev) / `4001` (static) · **Container:** `mkdocs-changemaker` · **Subdomain:** `docs.DOMAIN`
|
||||
|
||||
[:octicons-arrow-right-24: MkDocs Material](https://squidfunk.github.io/mkdocs-material/){ target="_blank" }
|
||||
|
||||
- :material-microsoft-visual-studio-code:{ .lg .middle } **Code Server**
|
||||
|
||||
---
|
||||
|
||||
Full VS Code in the browser. Edit configuration files, templates, and documentation from anywhere without SSH. Supports extensions.
|
||||
|
||||
**Port:** `8888` · **Container:** `code-server-changemaker` · **Subdomain:** `code.DOMAIN`
|
||||
|
||||
[:octicons-arrow-right-24: Code Server Docs](https://coder.com/docs/code-server){ target="_blank" }
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Data & Automation
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-table:{ .lg .middle } **NocoDB**
|
||||
|
||||
---
|
||||
|
||||
Airtable-alternative database browser. Provides a spreadsheet-like interface to browse, filter, sort, and export campaign data. Read-only access to the main database.
|
||||
|
||||
**Port:** `8091` · **Container:** `changemaker-v2-nocodb` · **Subdomain:** `db.DOMAIN`
|
||||
|
||||
[:octicons-arrow-right-24: NocoDB Docs](https://docs.nocodb.com/){ target="_blank" }
|
||||
|
||||
- :material-robot:{ .lg .middle } **n8n**
|
||||
|
||||
---
|
||||
|
||||
Visual workflow automation platform. Connect APIs, trigger actions on events, schedule tasks, and build custom integrations — all without code. 400+ built-in integrations.
|
||||
|
||||
**Port:** `5678` · **Container:** `n8n-changemaker` · **Subdomain:** `n8n.DOMAIN`
|
||||
|
||||
[:octicons-arrow-right-24: n8n Docs](https://docs.n8n.io/){ target="_blank" }
|
||||
|
||||
- :material-git:{ .lg .middle } **Gitea**
|
||||
|
||||
---
|
||||
|
||||
Self-hosted Git repository hosting. Version control for campaign code, configuration, templates, and documentation. Includes issues, pull requests, and CI/CD.
|
||||
|
||||
**Port:** `3030` (web) / `2222` (SSH) · **Container:** `gitea-changemaker` · **Subdomain:** `git.DOMAIN`
|
||||
|
||||
[:octicons-arrow-right-24: Gitea Docs](https://docs.gitea.com/){ target="_blank" }
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Utilities
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-qrcode:{ .lg .middle } **Mini QR**
|
||||
|
||||
---
|
||||
|
||||
Lightweight QR code generator. Produces PNG images for walk sheets, campaign materials, and event signage. Embedded in the admin dashboard via iframe.
|
||||
|
||||
**Port:** `8089` · **Container:** `mini-qr` · **Subdomain:** `qr.DOMAIN`
|
||||
|
||||
- :material-home:{ .lg .middle } **Homepage**
|
||||
|
||||
---
|
||||
|
||||
Service dashboard showing the status of all containers at a glance. Auto-generated `services.yaml` from `config.sh` provides both production and local links.
|
||||
|
||||
**Port:** `3010` · **Container:** `homepage-changemaker` · **Subdomain:** `home.DOMAIN`
|
||||
|
||||
[:octicons-arrow-right-24: Homepage Docs](https://gethomepage.dev/){ target="_blank" }
|
||||
|
||||
- :material-draw:{ .lg .middle } **Excalidraw**
|
||||
|
||||
---
|
||||
|
||||
Collaborative whiteboard for brainstorming, diagramming, and visual planning. Real-time collaboration via WebSocket.
|
||||
|
||||
**Port:** `8090` · **Container:** `excalidraw-changemaker` · **Subdomain:** `draw.DOMAIN`
|
||||
|
||||
[:octicons-arrow-right-24: Excalidraw](https://excalidraw.com/){ target="_blank" }
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Networking & Tunneling
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-tunnel:{ .lg .middle } **Pangolin + Newt**
|
||||
|
||||
---
|
||||
|
||||
Self-hosted tunnel server with the Newt client container. Exposes your services to the internet without port forwarding. Handles SSL/TLS, works behind CGNAT and double NAT.
|
||||
|
||||
**Container:** `newt-changemaker` · Managed from **Admin → Settings → Tunnel**
|
||||
|
||||
[:octicons-arrow-right-24: Deployment Guide](../deployment/index.md#pangolin) · [:octicons-arrow-right-24: Pangolin GitHub](https://github.com/fosrl/pangolin){ target="_blank" }
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Monitoring Stack
|
||||
|
||||
These services run behind the `monitoring` Docker Compose profile. Start them with:
|
||||
|
||||
```bash
|
||||
docker compose --profile monitoring up -d
|
||||
```
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-chart-line:{ .lg .middle } **Prometheus**
|
||||
|
||||
---
|
||||
|
||||
Metrics collection and time-series database. Scrapes 12 custom `cm_*` application metrics plus container, host, and Redis metrics. Pre-configured alert rules.
|
||||
|
||||
**Port:** `9090` · **Container:** `prometheus-changemaker`
|
||||
|
||||
[:octicons-arrow-right-24: Prometheus Docs](https://prometheus.io/docs/){ target="_blank" }
|
||||
|
||||
- :material-chart-box:{ .lg .middle } **Grafana**
|
||||
|
||||
---
|
||||
|
||||
Metrics visualization with 3 auto-provisioned dashboards: API Overview, Infrastructure, and Campaign Activity. Supports custom dashboards and alerting.
|
||||
|
||||
**Port:** `3001` · **Container:** `grafana-changemaker` · **Subdomain:** `grafana.DOMAIN`
|
||||
|
||||
[:octicons-arrow-right-24: Grafana Docs](https://grafana.com/docs/grafana/latest/){ target="_blank" }
|
||||
|
||||
- :material-bell-alert:{ .lg .middle } **Alertmanager**
|
||||
|
||||
---
|
||||
|
||||
Alert routing and notification delivery. Receives alerts from Prometheus and dispatches to Gotify, email, or webhooks based on configurable rules.
|
||||
|
||||
**Port:** `9093` · **Container:** `alertmanager-changemaker`
|
||||
|
||||
[:octicons-arrow-right-24: Alertmanager Docs](https://prometheus.io/docs/alerting/latest/alertmanager/){ target="_blank" }
|
||||
|
||||
- :material-docker:{ .lg .middle } **cAdvisor**
|
||||
|
||||
---
|
||||
|
||||
Container resource metrics. Exposes CPU, memory, network, and filesystem usage per container for Prometheus to scrape.
|
||||
|
||||
**Port:** `8080` · **Container:** `cadvisor-changemaker`
|
||||
|
||||
[:octicons-arrow-right-24: cAdvisor GitHub](https://github.com/google/cadvisor){ target="_blank" }
|
||||
|
||||
- :material-server:{ .lg .middle } **Node Exporter**
|
||||
|
||||
---
|
||||
|
||||
Host system metrics. Reports CPU, memory, disk, and network stats for the underlying server.
|
||||
|
||||
**Port:** `9100` · **Container:** `node-exporter-changemaker`
|
||||
|
||||
[:octicons-arrow-right-24: Node Exporter](https://prometheus.io/docs/guides/node-exporter/){ target="_blank" }
|
||||
|
||||
- :material-database-export:{ .lg .middle } **Redis Exporter**
|
||||
|
||||
---
|
||||
|
||||
Redis metrics for Prometheus. Exposes connection counts, memory usage, command stats, and keyspace info.
|
||||
|
||||
**Port:** `9121` · **Container:** `redis-exporter-changemaker`
|
||||
|
||||
[:octicons-arrow-right-24: Redis Exporter GitHub](https://github.com/oliver006/redis_exporter){ target="_blank" }
|
||||
|
||||
- :material-cellphone-message:{ .lg .middle } **Gotify**
|
||||
|
||||
---
|
||||
|
||||
Self-hosted push notification server. Receives alerts from Alertmanager and delivers them to mobile/desktop clients.
|
||||
|
||||
**Port:** `8889` · **Container:** `gotify-changemaker`
|
||||
|
||||
[:octicons-arrow-right-24: Gotify Docs](https://gotify.net/docs/){ target="_blank" }
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
All services at a glance with their default ports and subdomains.
|
||||
|
||||
| Service | Port | Subdomain | Docker Profile |
|
||||
|---------|------|-----------|---------------|
|
||||
| Express API | 4000 | `api.` | default |
|
||||
| Media API | 4100 | `media.` | default |
|
||||
| Admin GUI | 3000 | `app.` | default |
|
||||
| PostgreSQL | 5433 | — | default |
|
||||
| Redis | 6379 | — | default |
|
||||
| Nginx | 80/443 | *(all)* | default |
|
||||
| Listmonk | 9001 | `listmonk.` | default |
|
||||
| MailHog | 8025 | `mail.` | default |
|
||||
| MkDocs (dev) | 4003 | `docs.` | default |
|
||||
| MkDocs (static) | 4001 | *(root)* | default |
|
||||
| Code Server | 8888 | `code.` | default |
|
||||
| NocoDB | 8091 | `db.` | default |
|
||||
| n8n | 5678 | `n8n.` | default |
|
||||
| Gitea | 3030 | `git.` | default |
|
||||
| Mini QR | 8089 | `qr.` | default |
|
||||
| Homepage | 3010 | `home.` | default |
|
||||
| Excalidraw | 8090 | `draw.` | default |
|
||||
| Newt (tunnel) | — | — | default |
|
||||
| Prometheus | 9090 | — | `monitoring` |
|
||||
| Grafana | 3001 | `grafana.` | `monitoring` |
|
||||
| Alertmanager | 9093 | — | `monitoring` |
|
||||
| cAdvisor | 8080 | — | `monitoring` |
|
||||
| Node Exporter | 9100 | — | `monitoring` |
|
||||
| Redis Exporter | 9121 | — | `monitoring` |
|
||||
| Gotify | 8889 | — | `monitoring` |
|
||||
|
||||
!!! tip "Starting services selectively"
|
||||
You don't need to run everything. Start only what you need:
|
||||
|
||||
```bash
|
||||
# Core only
|
||||
docker compose up -d v2-postgres redis api admin
|
||||
|
||||
# Add nginx for subdomain routing
|
||||
docker compose up -d nginx
|
||||
|
||||
# Add monitoring
|
||||
docker compose --profile monitoring up -d
|
||||
```
|
||||
|
||||
See [Getting Started](../getting-started/index.md) for the recommended startup order.
|
||||
48
mkdocs/docs/docs/troubleshooting/index.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
description: Solutions for common errors, CORS issues, database problems, and tunnel debugging.
|
||||
icon: material/bug
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
Common issues and their solutions when running Changemaker Lite.
|
||||
|
||||
!!! warning "Under Construction"
|
||||
This troubleshooting guide is being expanded. Check back soon for more solutions.
|
||||
|
||||
## CORS Errors in Production
|
||||
|
||||
**Symptom:** Browser console shows CORS errors when accessing production domain.
|
||||
|
||||
**Fix:** Add your production domain to `CORS_ORIGINS` in `.env`:
|
||||
|
||||
```bash
|
||||
CORS_ORIGINS=https://app.yourdomain.org,http://localhost:3000
|
||||
```
|
||||
|
||||
Then restart the API: `docker compose restart api`
|
||||
|
||||
## Pangolin Tunnel 403/302 Errors
|
||||
|
||||
**Symptom:** All API endpoints return 302 redirects to Pangolin auth page.
|
||||
|
||||
**Fix:** In the Pangolin dashboard, set each resource to "Not Protected" (public access).
|
||||
|
||||
## Database Connection Failures
|
||||
|
||||
1. Check PostgreSQL: `docker compose ps v2-postgres`
|
||||
2. Verify `DATABASE_URL` in `.env`
|
||||
3. View logs: `docker compose logs v2-postgres --tail 50`
|
||||
|
||||
## Redis Connection Failures
|
||||
|
||||
1. Check Redis: `docker compose ps redis-changemaker`
|
||||
2. Verify `REDIS_PASSWORD` and `REDIS_URL` format in `.env`
|
||||
3. Test: `docker compose exec redis-changemaker redis-cli -a $REDIS_PASSWORD ping`
|
||||
|
||||
## API Not Starting
|
||||
|
||||
1. Check logs: `docker compose logs api --tail 100`
|
||||
2. Verify all required env vars are set (see `.env.example`)
|
||||
3. Run migrations: `docker compose exec api npx prisma migrate deploy`
|
||||
26
mkdocs/docs/docs/volunteer/index.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Volunteer Guide
|
||||
description: Guide for volunteers using the canvassing and shift signup features.
|
||||
icon: material/walk
|
||||
---
|
||||
|
||||
# Volunteer Guide
|
||||
|
||||
This guide helps campaign volunteers get started with shifts, canvassing, and activity tracking.
|
||||
|
||||
!!! warning "Under Construction"
|
||||
The volunteer guide is being written. Check back soon.
|
||||
|
||||
## Getting Started as a Volunteer
|
||||
|
||||
1. **Sign up for a shift** — visit the public shifts page and register for available time slots
|
||||
2. **Log in** — use the credentials from your confirmation email
|
||||
3. **View assignments** — see your assigned cuts and shifts in the volunteer portal
|
||||
4. **Start canvassing** — open the full-screen map, start a session, and record visits
|
||||
|
||||
## Volunteer Portal Features
|
||||
|
||||
- **Assignments** — see your upcoming shifts and assigned canvassing areas
|
||||
- **Canvass Map** — GPS-tracked map with walking routes and visit recording
|
||||
- **Activity Log** — review your visit history and outcome breakdowns
|
||||
- **Routes** — view past canvassing routes
|
||||
@@ -18,22 +18,36 @@ def on_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
Hook that runs when MkDocs loads the configuration.
|
||||
Injects environment variables as extra JavaScript.
|
||||
"""
|
||||
import re
|
||||
|
||||
# Read environment variables (with fallbacks)
|
||||
media_api_url = os.environ.get('MEDIA_API_PUBLIC_URL', 'http://localhost:4100')
|
||||
public_url = os.environ.get('ADMIN_URL', 'http://localhost:3000')
|
||||
|
||||
# For production, check for public-facing URLs
|
||||
# Fallback to subdomain-based URLs if available
|
||||
media_api_port = os.environ.get('MEDIA_API_PORT', '4100')
|
||||
admin_port = os.environ.get('ADMIN_PORT', '3000')
|
||||
admin_url = os.environ.get('ADMIN_URL', '')
|
||||
base_domain = os.environ.get('BASE_DOMAIN', '')
|
||||
|
||||
if base_domain and not base_domain.startswith('http'):
|
||||
base_domain = f'https://{base_domain}'
|
||||
|
||||
# Use base_domain to construct URLs if env vars not explicitly set
|
||||
if media_api_url == 'http://localhost:4100' and base_domain:
|
||||
media_api_url = base_domain.replace('cmlite.org', 'media.cmlite.org')
|
||||
# Helper: detect Docker container hostnames (not browser-accessible)
|
||||
def is_docker_hostname(url: str) -> bool:
|
||||
"""Check if URL uses a Docker container hostname instead of localhost/domain."""
|
||||
host = re.sub(r'^https?://', '', url).split(':')[0].split('/')[0]
|
||||
# Docker hostnames typically contain hyphens and no dots (not localhost, not a domain)
|
||||
return host != 'localhost' and '.' not in host
|
||||
|
||||
if public_url == 'http://localhost:3000' and base_domain:
|
||||
public_url = base_domain.replace('cmlite.org', 'app.cmlite.org')
|
||||
# Resolve media API URL — must be browser-accessible (not Docker hostname)
|
||||
if is_docker_hostname(media_api_url):
|
||||
media_api_url = f'http://localhost:{media_api_port}'
|
||||
|
||||
# Resolve public URL (admin app)
|
||||
if admin_url and not is_docker_hostname(admin_url) and 'localhost' not in admin_url:
|
||||
# Production domain — use as-is
|
||||
public_url = admin_url
|
||||
else:
|
||||
# Dev: use ADMIN_PORT (most reliable source of truth)
|
||||
public_url = f'http://localhost:{admin_port}'
|
||||
|
||||
# Create inline JavaScript with config
|
||||
config_script = f"""
|
||||
@@ -61,18 +75,25 @@ def on_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# Note: We'll need to create a file for this
|
||||
env_config_path = 'assets/js/env-config.js'
|
||||
|
||||
# Write the generated config to a file
|
||||
# Write the generated config to a file (only if content changed to avoid
|
||||
# triggering MkDocs file watcher rebuild loop in serve mode)
|
||||
import pathlib
|
||||
docs_dir = pathlib.Path(config['docs_dir'])
|
||||
env_config_file = docs_dir / env_config_path
|
||||
env_config_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(env_config_file, 'w') as f:
|
||||
f.write(config_script)
|
||||
existing_content = ''
|
||||
if env_config_file.exists():
|
||||
existing_content = env_config_file.read_text()
|
||||
|
||||
logger.info(f"✓ Generated video config: {env_config_file}")
|
||||
logger.info(f" MEDIA_API_URL: {media_api_url}")
|
||||
logger.info(f" PUBLIC_URL: {public_url}")
|
||||
if existing_content != config_script:
|
||||
with open(env_config_file, 'w') as f:
|
||||
f.write(config_script)
|
||||
logger.info(f"✓ Generated video config: {env_config_file}")
|
||||
logger.info(f" MEDIA_API_URL: {media_api_url}")
|
||||
logger.info(f" PUBLIC_URL: {public_url}")
|
||||
else:
|
||||
logger.info(f"✓ Video config unchanged, skipping write")
|
||||
|
||||
# Insert at the beginning of extra_javascript list
|
||||
if env_config_path not in config['extra_javascript']:
|
||||
|
||||
@@ -99,7 +99,7 @@ def generate_repo_data(repo_config: Dict[str, Any], output_dir: Path) -> None:
|
||||
if repo_config.get('github'):
|
||||
api_url = f"https://api.github.com/repos/{repo}"
|
||||
headers = {'Accept': 'application/vnd.github.v3+json'}
|
||||
github_token = "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" # Replace with your GitHub token
|
||||
github_token = os.getenv('GITHUB_TOKEN', '')
|
||||
if github_token:
|
||||
headers['Authorization'] = f'token {github_token}'
|
||||
else:
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Canvas
|
||||
|
||||
This is BNKops canvassing how to! In the following document, you will find all sorts of tips and tricks for door knocking, canvassing, and using the BNKops canvassing app.
|
||||
|
||||
@@ -4,160 +4,3 @@ hide:
|
||||
- navigation
|
||||
- toc
|
||||
---
|
||||
|
||||
# Welcome to Changemaker Lite
|
||||
|
||||
Stop feeding your secrets to corporations. Own your political infrastructure.
|
||||
|
||||
!!! success "Changemaker Lite V2 is Now Available"
|
||||
**V2 is a complete architectural rebuild** with a modern TypeScript stack, dual API design, React admin interface, and comprehensive feature modules. Production ready with security audit completed.
|
||||
|
||||
[→ Explore V2 Documentation](v2/index.md){ .md-button .md-button--primary }
|
||||
[→ Quick Start Guide](v2/getting-started/quick-start.md){ .md-button }
|
||||
|
||||
## Changemaker Lite V2
|
||||
|
||||
V2 is the **recommended version** for all new installations. It offers:
|
||||
|
||||
### ✨ Modern Architecture
|
||||
- **Dual API Design**: Express.js (main features) + Fastify (media library)
|
||||
- **TypeScript Throughout**: Type-safe development with better IDE support
|
||||
- **Prisma + Drizzle ORM**: Direct database access (no NocoDB middleware)
|
||||
- **React Admin**: Modern UI with Vite + Ant Design + Zustand
|
||||
|
||||
### 🚀 Comprehensive Features
|
||||
- **Influence Module**: Email advocacy campaigns targeting elected representatives
|
||||
- **Map Module**: Geographic mapping with GPS-tracked canvassing
|
||||
- **Landing Pages**: GrapesJS page builder with MkDocs export
|
||||
- **Email Templates**: Template management system
|
||||
- **Media Library**: Video management with public gallery
|
||||
- **Newsletter Integration**: Listmonk sync for email marketing
|
||||
- **Observability**: Prometheus + Grafana monitoring stack
|
||||
|
||||
### 🔒 Production Ready
|
||||
- **Security Audited**: 13 findings addressed (Feb 2026)
|
||||
- **JWT Authentication**: Role-based access control with refresh tokens
|
||||
- **Password Policy**: Enforced complexity requirements
|
||||
- **Rate Limiting**: Per-endpoint protection
|
||||
- **Monitoring**: 12 custom metrics + 3 Grafana dashboards
|
||||
|
||||
### 📚 Complete Documentation
|
||||
- [Getting Started](v2/getting-started/index.md) - Installation and setup
|
||||
- [Architecture](v2/architecture/index.md) - System design and components
|
||||
- [Features](v2/features/index.md) - Module documentation
|
||||
- [API Reference](v2/api-reference/index.md) - Complete endpoint docs
|
||||
- [User Guides](v2/user-guides/index.md) - Role-based manuals
|
||||
|
||||
[**Explore V2 Documentation →**](v2/index.md)
|
||||
|
||||
---
|
||||
|
||||
## Changemaker Lite V1 (Legacy)
|
||||
|
||||
!!! warning "V1 is Deprecated"
|
||||
V1 documentation is preserved below for reference. **We strongly recommend migrating to V2** for improved performance, security, and features.
|
||||
|
||||
[→ View Migration Guide](v2/migration/index.md)
|
||||
|
||||
### Quick Start (V1)
|
||||
|
||||
Get V1 running in minutes:
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://gitea.bnkops.com/admin/changemaker.lite
|
||||
cd changemaker.lite
|
||||
|
||||
# Configure environment
|
||||
./config.sh
|
||||
|
||||
# Start all services
|
||||
docker compose up -d
|
||||
|
||||
# For production deployment with Cloudflare tunnels
|
||||
./start-production.sh
|
||||
```
|
||||
|
||||
## Services
|
||||
|
||||
Changemaker Lite includes these essential services:
|
||||
|
||||
### Core Services
|
||||
|
||||
- **[Homepage](v1/services/homepage.md)** (Port 3010) - Central dashboard and service monitoring
|
||||
- **[Code Server](v1/services/code-server.md)** (Port 8888) - VS Code in your browser
|
||||
- **[MkDocs](v1/services/mkdocs.md)** (Port 4000) - Documentation with live preview
|
||||
- **[Static Server](v1/services/static-server.md)** (Port 4001) - Production documentation site
|
||||
|
||||
### Communication & Automation
|
||||
|
||||
- **[Listmonk](v1/services/listmonk.md)** (Port 9000) - Newsletter and email campaign management
|
||||
- **[n8n](v1/services/n8n.md)** (Port 5678) - Workflow automation platform
|
||||
|
||||
### Data & Development
|
||||
|
||||
- **[NocoDB](v1/services/nocodb.md)** (Port 8090) - No-code database platform
|
||||
- **[PostgreSQL](v1/services/postgresql.md)** (Port 5432) - Database backend for Listmonk
|
||||
- **[Gitea](v1/services/gitea.md)** (Port 3030) - Self-hosted Git service
|
||||
|
||||
### Interactive Tools
|
||||
|
||||
- **[Map Viewer](v1/services/map.md)** (Port 3000) - Interactive map with NocoDB integration
|
||||
- **[Mini QR](v1/services/mini-qr.md)** (Port 8089) - QR code generator
|
||||
|
||||
## Getting Started (V1)
|
||||
|
||||
1. **Setup**: Run `./config.sh` to configure your environment
|
||||
2. **Launch**: Start services with `docker compose up -d`
|
||||
3. **Dashboard**: Access the Homepage at [http://localhost:3010](http://localhost:3010)
|
||||
4. **Production**: Deploy with Cloudflare tunnels using `./start-production.sh`
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
changemaker.lite/
|
||||
├── docker-compose.yml # Service definitions
|
||||
├── config.sh # Configuration wizard
|
||||
├── start-production.sh # Production deployment script
|
||||
├── mkdocs/ # Documentation source
|
||||
│ ├── docs/ # Markdown files
|
||||
│ └── mkdocs.yml # MkDocs configuration
|
||||
├── configs/ # Service configurations
|
||||
│ ├── homepage/ # Homepage dashboard config
|
||||
│ ├── code-server/ # VS Code settings
|
||||
│ └── cloudflare/ # Tunnel configurations
|
||||
├── map/ # Map application
|
||||
│ ├── app/ # Node.js application
|
||||
│ ├── Dockerfile # Container definition
|
||||
│ └── .env # Map configuration
|
||||
└── assets/ # Shared assets
|
||||
├── images/ # Image files
|
||||
├── icons/ # Service icons
|
||||
└── uploads/ # Listmonk uploads
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
- 🐳 **Fully Containerized** - All services run in Docker containers
|
||||
- 🔒 **Production Ready** - Built-in Cloudflare tunnel support for secure access
|
||||
- 📦 **All-in-One** - Everything you need for documentation, development, and campaigns
|
||||
- 🗺️ **Geographic Data** - Interactive maps with real-time location tracking
|
||||
- 📧 **Email Campaigns** - Professional newsletter management
|
||||
- 🔄 **Automation** - Connect services and automate workflows
|
||||
- 💾 **Version Control** - Self-hosted Git repository
|
||||
- 🎯 **No-Code Database** - Build applications without programming
|
||||
|
||||
## System Requirements
|
||||
|
||||
- **OS**: Ubuntu 24.04 LTS (Noble Numbat) or compatible Linux distribution
|
||||
- **Docker**: Version 24.0+ with Docker Compose v2
|
||||
- **Memory**: Minimum 4GB RAM (8GB recommended)
|
||||
- **Storage**: 20GB+ available disk space
|
||||
- **Network**: Internet connection for initial setup
|
||||
|
||||
## Learn More (V1)
|
||||
|
||||
- [Getting Started](v1/build/index.md) - Detailed installation guide
|
||||
- [Services Overview](v1/services/index.md) - Deep dive into each service
|
||||
- [Blog](blog/index.md) - Updates and tutorials
|
||||
- [GitHub Repository](https://gitea.bnkops.com/admin/Changemaker) - Source code
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
# Cost Comparison: Corporation vs. Community
|
||||
|
||||
## The True Cost of Corporate Dependency
|
||||
|
||||
When movements choose corporate software, they're not just paying subscription fees—they're paying with their power, their privacy, and their future. Let's break down the real costs.
|
||||
|
||||
## Monthly Cost Analysis
|
||||
|
||||
### Small Campaign (50 supporters, 5,000 emails/month)
|
||||
|
||||
| Service Category | Corporate Solution | Monthly Cost | Changemaker Lite | Monthly Cost |
|
||||
|------------------|-------------------|--------------|------------------|--------------|
|
||||
| **Email Marketing** | Mailchimp | $59/month | Listmonk | $0* |
|
||||
| **Database & CRM** | Airtable Pro | $240/month | NocoDB | $0* |
|
||||
| **Website Hosting** | Squarespace | $40/month | Static Server | $0* |
|
||||
| **Documentation** | Notion Team | $96/month | MkDocs | $0* |
|
||||
| **Development** | GitHub Codespaces | $87/month | Code Server | $0* |
|
||||
| **Automation** | Zapier Professional | $73/month | n8n | $0* |
|
||||
| **File Storage** | Google Workspace | $72/month | PostgreSQL + Storage | $0* |
|
||||
| **Analytics** | Corporate tracking | Privacy cost† | Self-hosted | $0* |
|
||||
| **TOTAL** | | **$667/month** | | **$50/month** |
|
||||
|
||||
*\*Included in base Changemaker Lite hosting cost*
|
||||
*†Privacy costs are incalculable but include surveillance, data sales, and community manipulation*
|
||||
|
||||
---
|
||||
|
||||
### Medium Campaign (500 supporters, 50,000 emails/month)
|
||||
|
||||
| Service Category | Corporate Solution | Monthly Cost | Changemaker Lite | Monthly Cost |
|
||||
|------------------|-------------------|--------------|------------------|--------------|
|
||||
| **Email Marketing** | Mailchimp | $299/month | Listmonk | $0* |
|
||||
| **Database & CRM** | Airtable Pro | $600/month | NocoDB | $0* |
|
||||
| **Website Hosting** | Squarespace | $65/month | Static Server | $0* |
|
||||
| **Documentation** | Notion Team | $240/month | MkDocs | $0* |
|
||||
| **Development** | GitHub Codespaces | $174/month | Code Server | $0* |
|
||||
| **Automation** | Zapier Professional | $146/month | n8n | $0* |
|
||||
| **File Storage** | Google Workspace | $144/month | PostgreSQL + Storage | $0* |
|
||||
| **Analytics** | Corporate tracking | Privacy cost† | Self-hosted | $0* |
|
||||
| **TOTAL** | | **$1,668/month** | | **$75/month** |
|
||||
|
||||
---
|
||||
|
||||
### Large Campaign (5,000 supporters, 500,000 emails/month)
|
||||
|
||||
| Service Category | Corporate Solution | Monthly Cost | Changemaker Lite | Monthly Cost |
|
||||
|------------------|-------------------|--------------|------------------|--------------|
|
||||
| **Email Marketing** | Mailchimp | $1,499/month | Listmonk | $0* |
|
||||
| **Database & CRM** | Airtable Pro | $1,200/month | NocoDB | $0* |
|
||||
| **Website Hosting** | Squarespace + CDN | $120/month | Static Server | $0* |
|
||||
| **Documentation** | Notion Team | $480/month | MkDocs | $0* |
|
||||
| **Development** | GitHub Codespaces | $348/month | Code Server | $0* |
|
||||
| **Automation** | Zapier Professional | $292/month | n8n | $0* |
|
||||
| **File Storage** | Google Workspace | $288/month | PostgreSQL + Storage | $0* |
|
||||
| **Analytics** | Corporate tracking | Privacy cost† | Self-hosted | $0* |
|
||||
| **TOTAL** | | **$4,227/month** | | **$150/month** |
|
||||
|
||||
## Annual Savings Breakdown
|
||||
|
||||
### 3-Year Cost Comparison
|
||||
|
||||
| Campaign Size | Corporate Total | Changemaker Total | **Savings** |
|
||||
|---------------|----------------|-------------------|-------------|
|
||||
| **Small** | $24,012 | $1,800 | **$22,212** |
|
||||
| **Medium** | $60,048 | $2,700 | **$57,348** |
|
||||
| **Large** | $152,172 | $5,400 | **$146,772** |
|
||||
|
||||
## Hidden Costs of Corporate Software
|
||||
|
||||
### What You Can't Put a Price On
|
||||
|
||||
#### Privacy Violations
|
||||
- **Data Harvesting**: Every interaction monitored and stored
|
||||
- **Behavioral Profiling**: Your community mapped and analyzed
|
||||
- **Third-Party Sales**: Your data sold to unknown entities
|
||||
- **Government Access**: Warrantless surveillance through corporate partnerships
|
||||
|
||||
#### Political Manipulation
|
||||
- **Algorithmic Suppression**: Your content reach artificially limited
|
||||
- **Narrative Control**: Corporate interests shape what your community sees
|
||||
- **Shadow Banning**: Activists systematically de-platformed
|
||||
- **Counter-Intelligence**: Your strategies monitored by opposition
|
||||
|
||||
#### Movement Disruption
|
||||
- **Dependency Creation**: Critical infrastructure controlled by adversaries
|
||||
- **Community Fragmentation**: Platforms designed to extract attention, not build power
|
||||
- **Organizing Interference**: Corporate algorithms prioritize engagement over solidarity
|
||||
- **Cultural Assimilation**: Movement culture shaped by corporate values
|
||||
|
||||
## The Changemaker Advantage
|
||||
|
||||
### What You Get for $50-150/month
|
||||
|
||||
#### Complete Infrastructure
|
||||
- **Email System**: Unlimited contacts, unlimited sends
|
||||
- **Database Power**: Unlimited records, unlimited complexity
|
||||
- **Web Presence**: Unlimited sites, unlimited traffic
|
||||
- **Development Environment**: Full coding environment with AI assistance
|
||||
- **Documentation Platform**: Beautiful, searchable knowledge base
|
||||
- **Automation Engine**: Connect everything, automate everything
|
||||
- **File Storage**: Unlimited files, unlimited backups
|
||||
|
||||
#### True Ownership
|
||||
- **Your Domain**: No corporate branding or limitations
|
||||
- **Your Data**: Complete export capability, no lock-in
|
||||
- **Your Rules**: No terms of service to violate
|
||||
- **Your Community**: No algorithmic manipulation
|
||||
|
||||
#### Community Support
|
||||
- **Open Documentation**: Complete guides and tutorials available
|
||||
- **Community-Driven Development**: Built by and for liberation movements
|
||||
- **Technical Support**: Professional assistance from BNKops cooperative
|
||||
- **Political Alignment**: Technology designed with movement values
|
||||
|
||||
## The Compound Effect
|
||||
|
||||
### Year Over Year Savings
|
||||
|
||||
Corporate software costs grow exponentially:
|
||||
- **Year 1**: "Starter" pricing to hook you
|
||||
- **Year 2**: Feature limits force tier upgrades
|
||||
- **Year 3**: Usage growth triggers premium pricing
|
||||
- **Year 4**: Platform changes force expensive migrations
|
||||
- **Year 5**: Lock-in enables arbitrary price increases
|
||||
|
||||
Changemaker Lite costs grow linearly with actual infrastructure needs:
|
||||
- **Year 1**: Base infrastructure costs
|
||||
- **Year 2**: Modest increases for storage/bandwidth only
|
||||
- **Year 3**: Scale only with actual technical requirements
|
||||
- **Year 4**: Community-driven improvements at no extra cost
|
||||
- **Year 5**: Established infrastructure with declining per-user costs
|
||||
|
||||
### 10-Year Projection
|
||||
|
||||
| Year | Corporate (Medium Campaign) | Changemaker Lite | Annual Savings |
|
||||
|------|---------------------------|------------------|----------------|
|
||||
| 1 | $20,016 | $900 | $19,116 |
|
||||
| 2 | $22,017 | $900 | $21,117 |
|
||||
| 3 | $24,219 | $1,080 | $23,139 |
|
||||
| 4 | $26,641 | $1,080 | $25,561 |
|
||||
| 5 | $29,305 | $1,260 | $28,045 |
|
||||
| 6 | $32,235 | $1,260 | $30,975 |
|
||||
| 7 | $35,459 | $1,440 | $34,019 |
|
||||
| 8 | $39,005 | $1,440 | $37,565 |
|
||||
| 9 | $42,905 | $1,620 | $41,285 |
|
||||
| 10 | $47,196 | $1,620 | $45,576 |
|
||||
| **TOTAL** | **$318,998** | **$12,600** | **$306,398** |
|
||||
|
||||
## Calculate Your Own Savings
|
||||
|
||||
### Current Corporate Costs Worksheet
|
||||
|
||||
**Email Marketing**: $____/month
|
||||
**Database/CRM**: $____/month
|
||||
**Website Hosting**: $____/month
|
||||
**Documentation**: $____/month
|
||||
**Development Tools**: $____/month
|
||||
**Automation**: $____/month
|
||||
**File Storage**: $____/month
|
||||
**Other SaaS**: $____/month
|
||||
|
||||
**Monthly Total**: $____
|
||||
**Annual Total**: $____
|
||||
|
||||
**Changemaker Alternative**: $50-150/month
|
||||
**Your Annual Savings**: $____
|
||||
|
||||
## Beyond the Numbers
|
||||
|
||||
### What Movements Do With Their Savings
|
||||
|
||||
The money saved by choosing community-controlled technology doesn't disappear—it goes directly back into movement building:
|
||||
|
||||
- **Hire organizers** instead of paying corporate executives
|
||||
- **Fund direct actions** instead of funding surveillance infrastructure
|
||||
- **Support community members** instead of enriching shareholders
|
||||
- **Build lasting power** instead of temporary platform dependency
|
||||
|
||||
## Making the Switch
|
||||
|
||||
### Transition Strategy
|
||||
|
||||
You don't have to switch everything at once:
|
||||
|
||||
1. **Start with documentation** - Move your knowledge base to MkDocs
|
||||
2. **Add email infrastructure** - Set up Listmonk for newsletters
|
||||
3. **Build your database** - Move contact management to NocoDB
|
||||
4. **Automate connections** - Use n8n to integrate everything
|
||||
5. **Phase out corporate tools** - Cancel subscriptions as you replicate functionality
|
||||
|
||||
### Investment Timeline
|
||||
|
||||
- **Month 1**: Initial setup and learning ($150 including setup time)
|
||||
- **Month 2-3**: Data migration and team training ($100/month)
|
||||
- **Month 4+**: Full operation at optimal cost ($50-150/month based on scale)
|
||||
|
||||
### ROI Calculation
|
||||
|
||||
Most campaigns recover their entire first-year investment in **60-90 days** through subscription savings alone.
|
||||
|
||||
---
|
||||
|
||||
*Ready to stop feeding your budget to corporate surveillance? [Get started with Changemaker Lite today](../build/index.md) and take control of your digital infrastructure.*
|
||||
@@ -1,163 +0,0 @@
|
||||
# Philosophy: Your Secrets, Your Power, Your Movement
|
||||
|
||||
## The Question That Changes Everything!
|
||||
|
||||
**If you are a political actor, who do you trust with your secrets?**
|
||||
|
||||
This isn't just a technical question—it's the core political question of our time. Every email you send, every document you create, every contact list you build, every strategy you develop: where does it live? Who owns the servers? Who has the keys?
|
||||
|
||||
## The Corporate Extraction Machine
|
||||
|
||||
### How They Hook You
|
||||
|
||||
Corporate software companies have perfected the art of digital snake oil sales:
|
||||
|
||||
1. **Free Trials** - They lure you in with "free" accounts
|
||||
2. **Feature Creep** - Essential features require paid tiers
|
||||
3. **Data Lock-In** - Your data becomes harder to export
|
||||
4. **Price Escalation** - $40/month becomes $750/month as you grow
|
||||
5. **Surveillance Integration** - Your organizing becomes their intelligence
|
||||
|
||||
### The Real Product
|
||||
|
||||
!!! warning "You Are Not the Customer"
|
||||
If you're not paying for the product, you ARE the product. But even when you are paying, you're often still the product.
|
||||
|
||||
Corporate platforms don't make money from your subscription fees—they make money from:
|
||||
|
||||
- **Data Sales** to third parties
|
||||
- **Algorithmic Manipulation** for corporate and political interests
|
||||
- **Surveillance Contracts** with governments and corporations
|
||||
- **Predictive Analytics** about your community and movement
|
||||
|
||||
## The BNKops Alternative
|
||||
|
||||
### Who We Are
|
||||
|
||||
**BNKops** is a cooperative based in amiskwaciy-wâskahikan (Edmonton, Alberta) on Treaty 6 territory. We're not a corporation—we're a collective of skilled organizers, developers, and community builders who believe technology should serve liberation, not oppression.
|
||||
|
||||
### Our Principles
|
||||
|
||||
#### 🏳️⚧️ 🏳️🌈 🇵🇸 Liberation First
|
||||
|
||||
Technology that centers the most marginalized voices and fights for collective liberation. We believe strongly that the medium is the message; if you the use the medium of fascists, what does that say about your movement?
|
||||
|
||||
#### 🤝 Community Over Profit
|
||||
|
||||
We operate as a cooperative because we believe in shared ownership and democratic decision-making. No venture capitalists, no shareholders, no extraction.
|
||||
|
||||
#### ⚡ Data Sovereignty
|
||||
|
||||
Your data belongs to you and your community. We build tools that let you own your digital infrastructure completely.
|
||||
|
||||
#### 🔒 Security Culture
|
||||
|
||||
Real security comes from community control, not corporate promises. We integrate security culture practices into our technology design.
|
||||
|
||||
### Why This Matters
|
||||
|
||||
When you control your technology infrastructure:
|
||||
|
||||
- **Your secrets stay secret** - No corporate access to sensitive organizing data
|
||||
- **Your community stays connected** - No algorithmic manipulation of your reach
|
||||
- **Your costs stay low** - No extraction-based pricing as you grow
|
||||
- **Your future stays yours** - No vendor lock-in or platform dependency
|
||||
|
||||
## The Philosophy in Practice
|
||||
|
||||
### Security Culture Meets Technology
|
||||
|
||||
Traditional security culture asks: "Who needs to know this information?"
|
||||
|
||||
Digital security culture asks: "Who controls the infrastructure where this information lives?"
|
||||
|
||||
### Community Technology
|
||||
|
||||
We believe in **community technology** - tools that:
|
||||
|
||||
- Are owned and controlled by the communities that use them
|
||||
- Are designed with liberation politics from the ground up using free and open source software
|
||||
- Prioritize care, consent, and collective power
|
||||
- Can be understood, modified, and improved by community members
|
||||
|
||||
### Prefigurative Politics
|
||||
|
||||
The tools we use shape the movements we build. Corporate tools create corporate movements—hierarchical, surveilled, and dependent. Community-controlled tools create community-controlled movements—democratic, secure, and sovereign.
|
||||
|
||||
## Common Questions
|
||||
|
||||
### "Isn't this just for tech people?"
|
||||
|
||||
**No.** We specifically designed Changemaker Lite for organizers, activists, and movement builders who may not have technical backgrounds. Our philosophy is that everyone deserves digital sovereignty, not just people with computer science degrees.
|
||||
|
||||
This is not to say that you won't need to learn! These tools are just that; tools. They have no fancy or white-labeled marketing and are technical in nature. You will need to learn to use them, just as any worker needs to learn the power tools they use on the job.
|
||||
|
||||
### "What about convenience?"
|
||||
|
||||
Corporate platforms are convenient because they've extracted billions of dollars from users to fund that convenience. When you own your tools, there's a learning curve—but it's the same learning curve as learning to organize, learning to build power, learning to create change.
|
||||
|
||||
### "Can't we just use corporate tools carefully?"
|
||||
|
||||
Would you hold your most sensitive organizing meetings in a room owned by your opposition? Would you store your membership lists in filing cabinets at a corporation that profits from surveillance? Digital tools are the same.
|
||||
|
||||
### "What about security?"
|
||||
|
||||
Real security comes from community control, not corporate promises. When you control your infrastructure:
|
||||
|
||||
- You decide what gets logged and what doesn't
|
||||
- You choose who has access and who doesn't
|
||||
- You know exactly where your data is and who can see it
|
||||
- You can't be de-platformed or locked out of your own data
|
||||
|
||||
### The Surveillance Capitalism Trap
|
||||
|
||||
As Shoshana Zuboff documents in "The Age of Surveillance Capitalism," we're living through a new form of capitalism that extracts value from human experience itself. Political movements are particularly valuable targets because:
|
||||
|
||||
- Political data predicts behavior
|
||||
- Movement intelligence can be used to counter-organize
|
||||
- Community networks can be mapped and disrupted
|
||||
- Organizing strategies can be monitored and neutralized
|
||||
|
||||
## Taking Action
|
||||
|
||||
### Start Where You Are
|
||||
|
||||
You don't have to replace everything at once. Start with one tool, one campaign, one project. Learn the technology alongside your organizing.
|
||||
|
||||
### Build Community Capacity
|
||||
|
||||
The goal isn't individual self-sufficiency—it's community technological sovereignty. Share skills, pool resources, learn together.
|
||||
|
||||
### Connect with Others
|
||||
|
||||
You're not alone in this. The free and open source software community, the digital security community, and the appropriate technology movement are all working on similar problems.
|
||||
|
||||
### Remember Why
|
||||
|
||||
This isn't about technology for its own sake. It's about building the infrastructure for the world we want to see—where communities have power, where people control their own data, where technology serves liberation.
|
||||
|
||||
---
|
||||
|
||||
## Resources for Deeper Learning
|
||||
|
||||
### Essential Reading
|
||||
|
||||
- [De-corp Your Software Stack](https://docs.bnkops.com/archive/repo.archive/thatreallyblondehuman/Thoughts%20🤔/If%20you%20do%20politics%20who%20is%20reading%20your%20secrets%20-%20why%20you%20should%20de-corp%20your%20software%20stack/) - Our full manifesto
|
||||
- [The Age of Surveillance Capitalism](https://en.wikipedia.org/wiki/The_Age_of_Surveillance_Capitalism) by Shoshana Zuboff
|
||||
- [Security Culture Handbook](https://docs.bnkops.com/archive/repo.archive/Zines%20We%20Like%20😎/What%20Is%20Security%20Culture%20☠/)
|
||||
|
||||
### Community Resources
|
||||
|
||||
- [BNKops Repository](https://docs.bnkops.com/) - Documentation and knowledge base
|
||||
- [Activist Handbook](https://activist.org/) - Movement building resources
|
||||
- [EFF Surveillance Self-Defense](https://ssd.eff.org/) - Digital security guides
|
||||
|
||||
### Technical Learning
|
||||
|
||||
- [Self-Hosted Awesome List](https://github.com/awesome-selfhosted/awesome-selfhosted) - Open source alternatives
|
||||
- [Linux Journey](https://linuxjourney.com/) - Learn Linux basics
|
||||
- [Docker Curriculum](https://docker-curriculum.com/) - Learn containerization
|
||||
|
||||
---
|
||||
|
||||
*This philosophy document is a living document. Contribute your thoughts, experiences, and improvements through the [BNKops documentation platform](https://docs.bnkops.com/).*
|
||||
25
mkdocs/docs/test.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Test
|
||||
|
||||
Testing page.
|
||||
|
||||
|
||||
<div class="video-card-block" data-video-id="2" data-video-title="Testing This Sucker" data-video-duration="594" data-video-quality="" data-video-views="0" style="max-width: 480px; margin: 0 auto;">
|
||||
<a href="http://app.org/gallery/watch/2" style="display: block; text-decoration: none; color: inherit; border-radius: 12px; overflow: hidden; background: #1b2838; box-shadow: 0 4px 12px rgba(0,0,0,0.3);">
|
||||
<div style="position: relative; padding-bottom: 56.25%; background: #0d1b2a; overflow: hidden;">
|
||||
<img src="http://app.orgdata:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22480%22%20height%3D%22270%22%20viewBox%3D%220%200%20480%20270%22%3E%3Crect%20fill%3D%22%230d1b2a%22%20width%3D%22480%22%20height%3D%22270%22%2F%3E%3Ccircle%20cx%3D%22240%22%20cy%3D%22135%22%20r%3D%2232%22%20fill%3D%22rgba(157%2C78%2C221%2C0.6)%22%2F%3E%3Cpolygon%20points%3D%22230%2C118%20258%2C135%20230%2C152%22%20fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E" alt="Testing This Sucker" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;" />
|
||||
|
||||
<span style="position: absolute; bottom: 8px; right: 8px; background: rgba(0,0,0,0.8); color: #fff; font-size: 12px; font-weight: 500; padding: 2px 6px; border-radius: 4px;">9:54</span>
|
||||
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 56px; height: 56px; background: rgba(0,0,0,0.5); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
||||
<svg width="24" height="24" viewBox="0 0 20 20" fill="#fff"><path d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 12px 16px;">
|
||||
<div style="color: #fff; font-size: 15px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">Testing This Sucker</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 6px;">
|
||||
<span style="color: #8899aa; font-size: 13px;">0 views</span>
|
||||
<span style="color: #9d4edd; font-size: 13px; font-weight: 500;">Watch →</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,525 +0,0 @@
|
||||
# Setting Up Ansible with Tailscale for Remote Server Management
|
||||
|
||||
## Overview
|
||||
|
||||
This guide walks you through setting up Ansible to manage remote servers (like ThinkCentre units) using Tailscale for secure networking. This approach provides reliable remote access without complex port forwarding or VPN configurations.
|
||||
|
||||
In plainer language; this allows you to manage several Changemaker nodes remotely. If you are a full time campaigner, this can enable you to manage several campaigns infrastructure from a central location while each user gets their own Changemaker box.
|
||||
|
||||
## What You'll Learn
|
||||
|
||||
- How to set up Ansible for infrastructure automation
|
||||
- How to configure secure remote access using Tailscale
|
||||
- How to troubleshoot common SSH and networking issues
|
||||
- Why this approach is better than alternatives like Cloudflare Tunnels for simple SSH access
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Master Node**: Your main computer running Ubuntu/Linux (control machine)
|
||||
- **Target Nodes**: Remote servers/ThinkCentres running Ubuntu/Linux
|
||||
- **Both machines**: Must have internet access
|
||||
- **User Account**: Same username on all machines (recommended)
|
||||
|
||||
## Part 1: Initial Setup on Master Node
|
||||
|
||||
### 1. Create Ansible Directory Structure
|
||||
|
||||
```bash
|
||||
# Create project directory
|
||||
mkdir ~/ansible_quickstart
|
||||
cd ~/ansible_quickstart
|
||||
|
||||
# Create directory structure
|
||||
mkdir -p group_vars host_vars roles playbooks
|
||||
```
|
||||
|
||||
### 2. Install Ansible
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install ansible
|
||||
```
|
||||
|
||||
### 3. Generate SSH Keys (if not already done)
|
||||
|
||||
```bash
|
||||
# Generate SSH key pair
|
||||
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa
|
||||
|
||||
# Display public key (save this for later)
|
||||
cat ~/.ssh/id_rsa.pub
|
||||
```
|
||||
|
||||
## Part 2: Target Node Setup (Physical Access Required Initially)
|
||||
|
||||
### 1. Enable SSH on Target Node
|
||||
|
||||
Access each target node physically (monitor + keyboard):
|
||||
|
||||
```bash
|
||||
# Update system
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
# Install and enable SSH
|
||||
sudo apt install openssh-server
|
||||
sudo systemctl enable ssh
|
||||
sudo systemctl start ssh
|
||||
|
||||
# Check SSH status
|
||||
sudo systemctl status ssh
|
||||
```
|
||||
|
||||
**Note**: If you get "Unit ssh.service could not be found", you need to install the SSH server first:
|
||||
|
||||
```bash
|
||||
# Install OpenSSH server
|
||||
sudo apt install openssh-server
|
||||
|
||||
# Then start and enable SSH
|
||||
sudo systemctl start ssh
|
||||
sudo systemctl enable ssh
|
||||
|
||||
# Verify SSH is running and listening
|
||||
sudo ss -tlnp | grep :22
|
||||
```
|
||||
|
||||
You should see SSH listening on port 22.
|
||||
|
||||
### 2. Configure SSH Key Authentication
|
||||
|
||||
```bash
|
||||
# Create .ssh directory
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
|
||||
# Create authorized_keys file
|
||||
nano ~/.ssh/authorized_keys
|
||||
```
|
||||
|
||||
Paste your public key from the master node, then:
|
||||
|
||||
```bash
|
||||
# Set proper permissions
|
||||
chmod 600 ~/.ssh/authorized_keys
|
||||
```
|
||||
|
||||
### 3. Configure SSH Security
|
||||
|
||||
```bash
|
||||
# Edit SSH config
|
||||
sudo nano /etc/ssh/sshd_config
|
||||
```
|
||||
|
||||
Ensure these lines are uncommented:
|
||||
|
||||
```
|
||||
PubkeyAuthentication yes
|
||||
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
|
||||
```
|
||||
|
||||
```bash
|
||||
# Restart SSH service
|
||||
sudo systemctl restart ssh
|
||||
```
|
||||
|
||||
### 4. Configure Firewall
|
||||
|
||||
```bash
|
||||
# Check firewall status
|
||||
sudo ufw status
|
||||
|
||||
# Allow SSH through firewall
|
||||
sudo ufw allow ssh
|
||||
|
||||
# Fix home directory permissions (required for SSH keys)
|
||||
chmod 755 ~/
|
||||
```
|
||||
|
||||
## Part 3: Test Local SSH Connection
|
||||
|
||||
Before proceeding with remote access, test SSH connectivity locally:
|
||||
|
||||
```bash
|
||||
# From master node, test SSH to target
|
||||
ssh username@<target-local-ip>
|
||||
```
|
||||
|
||||
**Common Issues and Solutions:**
|
||||
|
||||
- **Connection hangs**: Check firewall rules (`sudo ufw allow ssh`)
|
||||
- **Permission denied**: Verify SSH keys and file permissions
|
||||
- **SSH config errors**: Ensure `PubkeyAuthentication yes` is set
|
||||
|
||||
## Part 4: Set Up Tailscale for Remote Access
|
||||
|
||||
### Why Tailscale Over Alternatives
|
||||
|
||||
We initially tried Cloudflare Tunnels but encountered complexity with:
|
||||
|
||||
- DNS routing issues
|
||||
- Complex configuration for SSH
|
||||
- Same-network testing problems
|
||||
- Multiple configuration approaches with varying success
|
||||
|
||||
**Tailscale is superior because:**
|
||||
|
||||
- Zero configuration mesh networking
|
||||
- Works from any network
|
||||
- Persistent IP addresses
|
||||
- No port forwarding needed
|
||||
- Free for personal use
|
||||
|
||||
### 1. Install Tailscale on Master Node
|
||||
|
||||
```bash
|
||||
# Install Tailscale
|
||||
curl -fsSL https://tailscale.com/install.sh | sh
|
||||
|
||||
# Connect to Tailscale network
|
||||
sudo tailscale up
|
||||
```
|
||||
|
||||
Follow the authentication URL to connect with your Google/Microsoft/GitHub account.
|
||||
|
||||
### 2. Install Tailscale on Target Nodes
|
||||
|
||||
**On each target node:**
|
||||
|
||||
```bash
|
||||
# Install Tailscale
|
||||
curl -fsSL https://tailscale.com/install.sh | sh
|
||||
|
||||
# Connect to Tailscale network
|
||||
sudo tailscale up
|
||||
```
|
||||
|
||||
Authenticate each device through the provided URL.
|
||||
|
||||
### 3. Get Tailscale IP Addresses
|
||||
|
||||
**On each machine:**
|
||||
|
||||
```bash
|
||||
# Get your Tailscale IP
|
||||
tailscale ip -4
|
||||
```
|
||||
|
||||
Each device receives a persistent IP like `100.x.x.x`.
|
||||
|
||||
## Part 5: Configure Ansible
|
||||
|
||||
### 1. Create Inventory File
|
||||
|
||||
```bash
|
||||
# Create inventory.ini
|
||||
cd ~/ansible_quickstart
|
||||
nano inventory.ini
|
||||
```
|
||||
|
||||
**Content:**
|
||||
|
||||
```ini
|
||||
[thinkcenter]
|
||||
tc-node1 ansible_host=100.x.x.x ansible_user=your-username
|
||||
tc-node2 ansible_host=100.x.x.x ansible_user=your-username
|
||||
|
||||
[all:vars]
|
||||
ansible_ssh_private_key_file=~/.ssh/id_rsa
|
||||
ansible_host_key_checking=False
|
||||
```
|
||||
|
||||
Replace:
|
||||
|
||||
- `100.x.x.x` with actual Tailscale IPs
|
||||
- `your-username` with your actual username
|
||||
|
||||
### 2. Test Ansible Connectivity
|
||||
|
||||
```bash
|
||||
# Test connection to all nodes
|
||||
ansible all -i inventory.ini -m ping
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
|
||||
```
|
||||
tc-node1 | SUCCESS => {
|
||||
"changed": false,
|
||||
"ping": "pong"
|
||||
}
|
||||
```
|
||||
|
||||
## Part 6: Create and Run Playbooks
|
||||
|
||||
### 1. Simple Information Gathering Playbook
|
||||
|
||||
```bash
|
||||
mkdir -p playbooks
|
||||
nano playbooks/info-playbook.yml
|
||||
```
|
||||
|
||||
**Content:**
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Gather Node Information
|
||||
hosts: all
|
||||
tasks:
|
||||
- name: Get system information
|
||||
setup:
|
||||
|
||||
- name: Display basic system info
|
||||
debug:
|
||||
msg: |
|
||||
Hostname: {{ ansible_hostname }}
|
||||
Operating System: {{ ansible_distribution }} {{ ansible_distribution_version }}
|
||||
Architecture: {{ ansible_architecture }}
|
||||
Memory: {{ ansible_memtotal_mb }}MB
|
||||
CPU Cores: {{ ansible_processor_vcpus }}
|
||||
|
||||
- name: Show disk usage
|
||||
command: df -h /
|
||||
register: disk_info
|
||||
|
||||
- name: Display disk usage
|
||||
debug:
|
||||
msg: "Root filesystem usage: {{ disk_info.stdout_lines[1] }}"
|
||||
|
||||
- name: Check uptime
|
||||
command: uptime
|
||||
register: uptime_info
|
||||
|
||||
- name: Display uptime
|
||||
debug:
|
||||
msg: "System uptime: {{ uptime_info.stdout }}"
|
||||
```
|
||||
|
||||
### 2. Run the Playbook
|
||||
|
||||
```bash
|
||||
ansible-playbook -i inventory.ini playbooks/info-playbook.yml
|
||||
```
|
||||
|
||||
## Part 7: Advanced Playbook Example
|
||||
|
||||
### System Setup Playbook
|
||||
|
||||
```bash
|
||||
nano playbooks/setup-node.yml
|
||||
```
|
||||
|
||||
**Content:**
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Setup ThinkCentre Node
|
||||
hosts: all
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Update package cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: Install essential packages
|
||||
package:
|
||||
name:
|
||||
- htop
|
||||
- vim
|
||||
- curl
|
||||
- git
|
||||
- docker.io
|
||||
state: present
|
||||
|
||||
- name: Add user to docker group
|
||||
user:
|
||||
name: "{{ ansible_user }}"
|
||||
groups: docker
|
||||
append: yes
|
||||
|
||||
- name: Create management directory
|
||||
file:
|
||||
path: /opt/management
|
||||
state: directory
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
```
|
||||
|
||||
## Troubleshooting Guide
|
||||
|
||||
### SSH Issues
|
||||
|
||||
**Problem: SSH connection hangs**
|
||||
|
||||
- Check firewall: `sudo ufw status` and `sudo ufw allow ssh`
|
||||
- Verify SSH service: `sudo systemctl status ssh`
|
||||
- Test local connectivity first
|
||||
|
||||
**Problem: Permission denied (publickey)**
|
||||
|
||||
- Check SSH key permissions: `chmod 600 ~/.ssh/authorized_keys`
|
||||
- Verify home directory permissions: `chmod 755 ~/`
|
||||
- Ensure SSH config allows key auth: `PubkeyAuthentication yes`
|
||||
|
||||
**Problem: Bad owner or permissions on SSH config**
|
||||
|
||||
```bash
|
||||
chmod 600 ~/.ssh/config
|
||||
```
|
||||
|
||||
### Ansible Issues
|
||||
|
||||
**Problem: Host key verification failed**
|
||||
|
||||
- Add to inventory: `ansible_host_key_checking=False`
|
||||
|
||||
**Problem: Ansible command not found**
|
||||
|
||||
```bash
|
||||
sudo apt install ansible
|
||||
```
|
||||
|
||||
**Problem: Connection timeouts**
|
||||
|
||||
- Verify Tailscale connectivity: `ping <tailscale-ip>`
|
||||
- Check if both nodes are connected: `tailscale status`
|
||||
|
||||
### Tailscale Issues
|
||||
|
||||
**Problem: Can't connect to Tailscale IP**
|
||||
|
||||
- Verify both devices are authenticated: `tailscale status`
|
||||
- Check Tailscale is running: `sudo systemctl status tailscaled`
|
||||
- Restart Tailscale: `sudo tailscale up`
|
||||
|
||||
## Scaling to Multiple Nodes
|
||||
|
||||
### Adding New Nodes
|
||||
|
||||
1. **Install Tailscale on new node**
|
||||
2. **Set up SSH access** (repeat Part 2)
|
||||
3. **Add to inventory.ini:**
|
||||
|
||||
```ini
|
||||
[thinkcenter]
|
||||
tc-node1 ansible_host=100.125.148.60 ansible_user=bunker-admin
|
||||
tc-node2 ansible_host=100.x.x.x ansible_user=bunker-admin
|
||||
tc-node3 ansible_host=100.x.x.x ansible_user=bunker-admin
|
||||
```
|
||||
|
||||
### Group Management
|
||||
|
||||
```ini
|
||||
[webservers]
|
||||
tc-node1 ansible_host=100.x.x.x ansible_user=bunker-admin
|
||||
tc-node2 ansible_host=100.x.x.x ansible_user=bunker-admin
|
||||
|
||||
[databases]
|
||||
tc-node3 ansible_host=100.x.x.x ansible_user=bunker-admin
|
||||
|
||||
[all:vars]
|
||||
ansible_ssh_private_key_file=~/.ssh/id_rsa
|
||||
ansible_host_key_checking=False
|
||||
```
|
||||
|
||||
Run playbooks on specific groups:
|
||||
|
||||
```bash
|
||||
ansible-playbook -i inventory.ini -l webservers playbook.yml
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Security
|
||||
|
||||
- Use SSH keys, not passwords
|
||||
- Keep Tailscale client updated
|
||||
- Regular security updates via Ansible
|
||||
- Use `become: yes` only when necessary
|
||||
|
||||
### Organization
|
||||
|
||||
```
|
||||
ansible_quickstart/
|
||||
├── inventory.ini
|
||||
├── group_vars/
|
||||
├── host_vars/
|
||||
├── roles/
|
||||
└── playbooks/
|
||||
├── info-playbook.yml
|
||||
├── setup-node.yml
|
||||
└── maintenance.yml
|
||||
```
|
||||
|
||||
### Monitoring and Maintenance
|
||||
|
||||
Create regular maintenance playbooks:
|
||||
|
||||
```yaml
|
||||
- name: System maintenance
|
||||
hosts: all
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Update all packages
|
||||
apt:
|
||||
upgrade: dist
|
||||
update_cache: yes
|
||||
|
||||
- name: Clean package cache
|
||||
apt:
|
||||
autoclean: yes
|
||||
autoremove: yes
|
||||
```
|
||||
|
||||
## Alternative Approaches We Considered
|
||||
|
||||
### Cloudflare Tunnels
|
||||
|
||||
- **Pros**: Good for web services, handles NAT traversal
|
||||
- **Cons**: Complex SSH setup, DNS routing issues, same-network problems
|
||||
- **Use case**: Better for web applications than SSH access
|
||||
|
||||
### Traditional VPN
|
||||
|
||||
- **Pros**: Full network access
|
||||
- **Cons**: Complex setup, port forwarding required, router configuration
|
||||
- **Use case**: When you control the network infrastructure
|
||||
|
||||
### SSH Reverse Tunnels
|
||||
|
||||
- **Pros**: Simple concept
|
||||
- **Cons**: Requires VPS, single point of failure, manual setup
|
||||
- **Use case**: Temporary access or when other methods fail
|
||||
|
||||
## Conclusion
|
||||
|
||||
This setup provides:
|
||||
|
||||
- **Reliable remote access** from anywhere
|
||||
- **Secure mesh networking** with Tailscale
|
||||
- **Infrastructure automation** with Ansible
|
||||
- **Easy scaling** to multiple nodes
|
||||
- **No complex networking** required
|
||||
|
||||
The combination of Ansible + Tailscale is ideal for managing distributed infrastructure without the complexity of traditional VPN setups or the limitations of cloud-specific solutions.
|
||||
|
||||
## Quick Reference Commands
|
||||
|
||||
```bash
|
||||
# Check Tailscale status
|
||||
tailscale status
|
||||
|
||||
# Test Ansible connectivity
|
||||
ansible all -i inventory.ini -m ping
|
||||
|
||||
# Run playbook on all hosts
|
||||
ansible-playbook -i inventory.ini playbook.yml
|
||||
|
||||
# Run playbook on specific group
|
||||
ansible-playbook -i inventory.ini -l groupname playbook.yml
|
||||
|
||||
# Run single command on all hosts
|
||||
ansible all -i inventory.ini -m command -a "uptime"
|
||||
|
||||
# SSH to node via Tailscale
|
||||
ssh username@100.x.x.x
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
# Advanced Configurations
|
||||
|
||||
We are also publishing how BNKops does several advanced workflows. These include things like assembling hardware, how to manage a network, how to manage several changemakers simultaneously, and integrating AI.
|
||||
@@ -1,685 +0,0 @@
|
||||
# Remote Development with VSCode over Tailscale
|
||||
|
||||
## Overview
|
||||
|
||||
This guide describes how to set up Visual Studio Code for remote development on servers using the Tailscale network. This enables development directly on remote machines as if they were local, with full access to files, terminals, and debugging capabilities.
|
||||
|
||||
## What You'll Learn
|
||||
|
||||
- How to configure VSCode for remote SSH connections
|
||||
- How to set up remote development environments
|
||||
- How to manage multiple remote servers efficiently
|
||||
- How to troubleshoot common remote development issues
|
||||
- Best practices for remote development workflows
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Ansible + Tailscale setup completed** (see previous guide)
|
||||
- **VSCode installed** on the local machine (master node)
|
||||
- **Working SSH access** to remote servers via Tailscale
|
||||
- **Tailscale running** on both local and remote machines
|
||||
|
||||
## Verify Prerequisites
|
||||
|
||||
Before starting, verify the setup:
|
||||
|
||||
```bash
|
||||
# Check Tailscale connectivity
|
||||
tailscale status
|
||||
|
||||
# Test SSH access
|
||||
ssh <username>@<tailscale-ip>
|
||||
|
||||
# Check VSCode is installed
|
||||
code --version
|
||||
```
|
||||
|
||||
## Part 1: Install and Configure Remote-SSH Extension
|
||||
|
||||
### 1. Install the Remote Development Extensions
|
||||
|
||||
**Option A: Install Remote Development Pack (Recommended)**
|
||||
|
||||
1. Open VSCode
|
||||
2. Press **Ctrl+Shift+X** (or **Cmd+Shift+X** on Mac)
|
||||
3. Search for **"Remote Development"**
|
||||
4. Install the **Remote Development extension pack** by Microsoft
|
||||
|
||||
This pack includes:
|
||||
|
||||
- Remote - SSH
|
||||
- Remote - SSH: Editing Configuration Files
|
||||
- Remote - Containers
|
||||
- Remote - WSL (Windows only)
|
||||
|
||||
**Option B: Install Individual Extension**
|
||||
|
||||
1. Search for **"Remote - SSH"**
|
||||
2. Install **Remote - SSH** by Microsoft
|
||||
|
||||
### 2. Verify Installation
|
||||
|
||||
After installation, the following should be visible:
|
||||
|
||||
- Remote Explorer icon in the Activity Bar (left sidebar)
|
||||
- "Remote-SSH" commands in Command Palette (Ctrl+Shift+P)
|
||||
|
||||
## Part 2: Configure SSH Connections
|
||||
|
||||
### 1. Access SSH Configuration
|
||||
|
||||
**Method A: Through VSCode**
|
||||
|
||||
1. Press **Ctrl+Shift+P** to open Command Palette
|
||||
2. Type **"Remote-SSH: Open SSH Configuration File..."**
|
||||
3. Select the SSH config file (usually the first option)
|
||||
|
||||
**Method B: Direct File Editing**
|
||||
```bash
|
||||
# Edit SSH config file directly
|
||||
nano ~/.ssh/config
|
||||
```
|
||||
|
||||
### 2. Add Server Configurations
|
||||
|
||||
Add servers to the SSH config file:
|
||||
|
||||
```
|
||||
# Example Node
|
||||
Host node1
|
||||
HostName <tailscale-ip>
|
||||
User <username>
|
||||
IdentityFile ~/.ssh/id_rsa
|
||||
ForwardAgent yes
|
||||
ServerAliveInterval 60
|
||||
ServerAliveCountMax 3
|
||||
|
||||
# Additional nodes (add as needed)
|
||||
Host node2
|
||||
HostName <tailscale-ip>
|
||||
User <username>
|
||||
IdentityFile ~/.ssh/id_rsa
|
||||
ForwardAgent yes
|
||||
ServerAliveInterval 60
|
||||
ServerAliveCountMax 3
|
||||
```
|
||||
|
||||
**Configuration Options Explained:**
|
||||
|
||||
- `Host`: Friendly name for the connection
|
||||
- `HostName`: Tailscale IP address
|
||||
- `User`: Username on the remote server
|
||||
- `IdentityFile`: Path to the SSH private key
|
||||
- `ForwardAgent`: Enables SSH agent forwarding for Git operations
|
||||
- `ServerAliveInterval`: Keeps connection alive (prevents timeouts)
|
||||
- `ServerAliveCountMax`: Number of keepalive attempts
|
||||
|
||||
### 3. Set Proper SSH Key Permissions
|
||||
|
||||
```bash
|
||||
# Ensure SSH config has correct permissions
|
||||
chmod 600 ~/.ssh/config
|
||||
|
||||
# Verify SSH key permissions
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
chmod 644 ~/.ssh/id_rsa.pub
|
||||
```
|
||||
|
||||
## Part 3: Connect to Remote Servers
|
||||
|
||||
### 1. Connect via Command Palette
|
||||
|
||||
1. Press **Ctrl+Shift+P**
|
||||
2. Type **"Remote-SSH: Connect to Host..."**
|
||||
3. Select the server (e.g., `node1`)
|
||||
4. VSCode will open a new window connected to the remote server
|
||||
|
||||
### 2. Connect via Remote Explorer
|
||||
|
||||
1. Click the **Remote Explorer** icon in Activity Bar
|
||||
2. Expand **SSH Targets**
|
||||
3. Click the **connect** icon next to the server name
|
||||
|
||||
### 3. Connect via Quick Menu
|
||||
|
||||
1. Click the **remote indicator** in bottom-left corner (looks like ><)
|
||||
2. Select **"Connect to Host..."**
|
||||
3. Choose the server from the list
|
||||
|
||||
### 4. First Connection Process
|
||||
|
||||
On first connection, VSCode will:
|
||||
|
||||
1. **Verify the host key** (click "Continue" if prompted)
|
||||
2. **Install VSCode Server** on the remote machine (automatic)
|
||||
3. **Open a remote window** with access to the remote file system
|
||||
|
||||
**Expected Timeline:**
|
||||
- First connection: 1-3 minutes (installs VSCode Server)
|
||||
- Subsequent connections: 10-30 seconds
|
||||
|
||||
## Part 4: Remote Development Environment Setup
|
||||
|
||||
### 1. Open Remote Workspace
|
||||
|
||||
Once connected:
|
||||
|
||||
```bash
|
||||
# In the VSCode terminal (now running on remote server)
|
||||
# Navigate to the project directory
|
||||
cd /home/<username>/projects
|
||||
|
||||
# Open current directory in VSCode
|
||||
code .
|
||||
|
||||
# Or open a specific project
|
||||
code /opt/myproject
|
||||
```
|
||||
|
||||
### 2. Install Extensions on Remote Server
|
||||
|
||||
Extensions must be installed separately on the remote server:
|
||||
|
||||
**Essential Development Extensions:**
|
||||
|
||||
1. **Python** (Microsoft) - Python development
|
||||
2. **GitLens** (GitKraken) - Enhanced Git capabilities
|
||||
3. **Docker** (Microsoft) - Container development
|
||||
4. **Prettier** - Code formatting
|
||||
5. **ESLint** - JavaScript linting
|
||||
6. **Auto Rename Tag** - HTML/XML tag editing
|
||||
|
||||
**To Install:**
|
||||
|
||||
1. Go to Extensions (Ctrl+Shift+X)
|
||||
2. Find the desired extension
|
||||
3. Click **"Install in SSH: node1"** (not local install)
|
||||
|
||||
### 3. Configure Git on Remote Server
|
||||
|
||||
```bash
|
||||
# In VSCode terminal (remote)
|
||||
git config --global user.name "<Full Name>"
|
||||
git config --global user.email "<email@example.com>"
|
||||
|
||||
# Test Git connectivity
|
||||
git clone https://github.com/<username>/<repo>.git
|
||||
```
|
||||
|
||||
## Part 5: Remote Development Workflows
|
||||
|
||||
### 1. File Management
|
||||
|
||||
**File Explorer:**
|
||||
|
||||
- Shows remote server's file system
|
||||
- Create, edit, delete files directly
|
||||
- Drag and drop between local and remote (limited)
|
||||
|
||||
**File Transfer:**
|
||||
```bash
|
||||
# Upload files to remote (from local terminal)
|
||||
scp localfile.txt <username>@<tailscale-ip>:/home/<username>/
|
||||
|
||||
# Download files from remote
|
||||
scp <username>@<tailscale-ip>:/remote/path/file.txt ./local/path/
|
||||
```
|
||||
|
||||
### 2. Terminal Usage
|
||||
|
||||
**Integrated Terminal:**
|
||||
|
||||
- Press **Ctrl+`** to open terminal
|
||||
- Runs directly on remote server
|
||||
- Multiple terminals supported
|
||||
- Full shell access (bash, zsh, etc.)
|
||||
|
||||
**Common Remote Terminal Commands:**
|
||||
```bash
|
||||
# Check system resources
|
||||
htop
|
||||
df -h
|
||||
free -h
|
||||
|
||||
# Install packages
|
||||
sudo apt update
|
||||
sudo apt install nodejs npm
|
||||
|
||||
# Start services
|
||||
sudo systemctl start nginx
|
||||
sudo docker-compose up -d
|
||||
```
|
||||
|
||||
### 3. Port Forwarding
|
||||
|
||||
**Automatic Port Forwarding:**
|
||||
VSCode automatically detects and forwards common development ports.
|
||||
|
||||
**Manual Port Forwarding:**
|
||||
|
||||
1. Open **Ports** tab in terminal panel
|
||||
2. Click **"Forward a Port"**
|
||||
3. Enter port number (e.g., 3000, 8080, 5000)
|
||||
4. Access via `http://localhost:port` on the local machine
|
||||
|
||||
**Example: Web Development**
|
||||
```bash
|
||||
# Start a web server on remote (port 3000)
|
||||
npm start
|
||||
|
||||
# VSCode automatically suggests forwarding port 3000
|
||||
# Access at http://localhost:3000 on the local machine
|
||||
```
|
||||
|
||||
### 4. Debugging Remote Applications
|
||||
|
||||
**Python Debugging:**
|
||||
```json
|
||||
// .vscode/launch.json on remote server
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Current File",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Node.js Debugging:**
|
||||
```json
|
||||
// .vscode/launch.json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Program",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/app.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Part 6: Advanced Configuration
|
||||
|
||||
### 1. Workspace Settings
|
||||
|
||||
Create remote-specific settings:
|
||||
|
||||
```json
|
||||
// .vscode/settings.json (on remote server)
|
||||
{
|
||||
"python.defaultInterpreterPath": "/usr/bin/python3",
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"files.autoSave": "afterDelay",
|
||||
"editor.formatOnSave": true,
|
||||
"remote.SSH.remotePlatform": {
|
||||
"node1": "linux"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Multi-Server Management
|
||||
|
||||
**Switch Between Servers:**
|
||||
|
||||
1. Click remote indicator (bottom-left)
|
||||
2. Select **"Connect to Host..."**
|
||||
3. Choose a different server
|
||||
|
||||
**Compare Files Across Servers:**
|
||||
|
||||
1. Open file from server A
|
||||
2. Connect to server B in new window
|
||||
3. Open corresponding file
|
||||
4. Use **"Compare with..."** command
|
||||
|
||||
### 3. Sync Configuration
|
||||
|
||||
**Settings Sync:**
|
||||
|
||||
1. Enable Settings Sync in VSCode
|
||||
2. Settings, extensions, and keybindings sync to remote
|
||||
3. Consistent experience across all servers
|
||||
|
||||
## Part 7: Project-Specific Setups
|
||||
|
||||
### 1. Python Development
|
||||
|
||||
```bash
|
||||
# On remote server
|
||||
# Create virtual environment
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# Install packages
|
||||
pip install flask django requests
|
||||
|
||||
# VSCode automatically detects Python interpreter
|
||||
```
|
||||
|
||||
**VSCode Python Configuration:**
|
||||
```json
|
||||
// .vscode/settings.json
|
||||
{
|
||||
"python.defaultInterpreterPath": "./venv/bin/python",
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": true
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Node.js Development
|
||||
|
||||
```bash
|
||||
# On remote server
|
||||
# Install Node.js
|
||||
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
# Create project
|
||||
mkdir myapp && cd myapp
|
||||
npm init -y
|
||||
npm install express
|
||||
```
|
||||
|
||||
### 3. Docker Development
|
||||
|
||||
```bash
|
||||
# On remote server
|
||||
# Install Docker (if not already done via Ansible)
|
||||
sudo apt install docker.io docker-compose
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
# Create Dockerfile
|
||||
cat > Dockerfile << EOF
|
||||
FROM node:18
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
EOF
|
||||
```
|
||||
|
||||
**VSCode Docker Integration:**
|
||||
|
||||
- Install Docker extension on remote
|
||||
- Right-click Dockerfile → "Build Image"
|
||||
- Manage containers from VSCode interface
|
||||
|
||||
## Part 8: Troubleshooting Guide
|
||||
|
||||
### Common Connection Issues
|
||||
|
||||
**Problem: "Could not establish connection to remote host"**
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Check Tailscale connectivity
|
||||
tailscale status
|
||||
ping <tailscale-ip>
|
||||
|
||||
# Test SSH manually
|
||||
ssh <username>@<tailscale-ip>
|
||||
|
||||
# Check SSH config syntax
|
||||
ssh -T node1
|
||||
```
|
||||
|
||||
**Problem: "Permission denied (publickey)"**
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Check SSH key permissions
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/config
|
||||
|
||||
# Verify SSH agent
|
||||
ssh-add ~/.ssh/id_rsa
|
||||
ssh-add -l
|
||||
|
||||
# Test SSH connection verbosely
|
||||
ssh -v <username>@<tailscale-ip>
|
||||
```
|
||||
|
||||
**Problem: "Host key verification failed"**
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Remove old host key
|
||||
ssh-keygen -R <tailscale-ip>
|
||||
|
||||
# Or disable host key checking (less secure)
|
||||
# Add to SSH config:
|
||||
# StrictHostKeyChecking no
|
||||
```
|
||||
|
||||
### VSCode-Specific Issues
|
||||
|
||||
**Problem: Extensions not working on remote**
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Install extensions specifically for the remote server
|
||||
2. Check extension compatibility with remote development
|
||||
3. Reload VSCode window: Ctrl+Shift+P → "Developer: Reload Window"
|
||||
|
||||
**Problem: Slow performance**
|
||||
|
||||
**Solutions:**
|
||||
- Use `.vscode/settings.json` to exclude large directories:
|
||||
```json
|
||||
{
|
||||
"files.watcherExclude": {
|
||||
"**/node_modules/**": true,
|
||||
"**/.git/objects/**": true,
|
||||
"**/dist/**": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Problem: Terminal not starting**
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Check shell path in remote settings
|
||||
"terminal.integrated.shell.linux": "/bin/bash"
|
||||
|
||||
# Or let VSCode auto-detect
|
||||
"terminal.integrated.defaultProfile.linux": "bash"
|
||||
```
|
||||
|
||||
### Network and Performance Issues
|
||||
|
||||
**Problem: Connection timeouts**
|
||||
|
||||
**Solutions:**
|
||||
Add to SSH config:
|
||||
```
|
||||
ServerAliveInterval 60
|
||||
ServerAliveCountMax 3
|
||||
TCPKeepAlive yes
|
||||
```
|
||||
|
||||
**Problem: File transfer slow**
|
||||
|
||||
**Solutions:**
|
||||
- Use `.vscodeignore` to exclude unnecessary files
|
||||
- Compress large files before transfer
|
||||
- Use `rsync` for large file operations:
|
||||
```bash
|
||||
rsync -avz --progress localdir/ <username>@<tailscale-ip>:remotedir/
|
||||
```
|
||||
|
||||
## Part 9: Best Practices
|
||||
|
||||
### Security Best Practices
|
||||
|
||||
1. **Use SSH keys, never passwords**
|
||||
2. **Keep SSH agent secure**
|
||||
3. **Regular security updates on remote servers**
|
||||
4. **Use VSCode's secure connection verification**
|
||||
|
||||
### Performance Optimization
|
||||
|
||||
1. **Exclude unnecessary files**:
|
||||
```json
|
||||
// .vscode/settings.json
|
||||
{
|
||||
"files.watcherExclude": {
|
||||
"**/node_modules/**": true,
|
||||
"**/.git/**": true,
|
||||
"**/dist/**": true,
|
||||
"**/build/**": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/bower_components": true,
|
||||
"**/*.code-search": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Use remote workspace for large projects**
|
||||
3. **Close unnecessary windows and extensions**
|
||||
4. **Use efficient development workflows**
|
||||
|
||||
### Development Workflow
|
||||
|
||||
1. **Use version control effectively**:
|
||||
```bash
|
||||
# Always work in Git repositories
|
||||
git status
|
||||
git add .
|
||||
git commit -m "feature: add new functionality"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
2. **Environment separation**:
|
||||
```bash
|
||||
# Development
|
||||
ssh node1
|
||||
cd /home/<username>/dev-projects
|
||||
|
||||
# Production
|
||||
ssh node2
|
||||
cd /opt/production-apps
|
||||
```
|
||||
|
||||
3. **Backup important work**:
|
||||
```bash
|
||||
# Regular backups via Git
|
||||
git push origin main
|
||||
|
||||
# Or manual backup
|
||||
scp -r <username>@<tailscale-ip>:/important/project ./backup/
|
||||
```
|
||||
|
||||
## Part 10: Team Collaboration
|
||||
|
||||
### Shared Development Servers
|
||||
|
||||
**SSH Config for Team:**
|
||||
```
|
||||
# Shared development server
|
||||
Host team-dev
|
||||
HostName <tailscale-ip>
|
||||
User <team-user>
|
||||
IdentityFile ~/.ssh/team_dev_key
|
||||
ForwardAgent yes
|
||||
|
||||
# Personal development
|
||||
Host my-dev
|
||||
HostName <tailscale-ip>
|
||||
User <username>
|
||||
IdentityFile ~/.ssh/id_rsa
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
/opt/projects/
|
||||
├── project-a/
|
||||
│ ├── dev/ # Development branch
|
||||
│ ├── staging/ # Staging environment
|
||||
│ └── docs/ # Documentation
|
||||
├── project-b/
|
||||
└── shared-tools/ # Common utilities
|
||||
```
|
||||
|
||||
### Access Management
|
||||
|
||||
```bash
|
||||
# Create shared project directory
|
||||
sudo mkdir -p /opt/projects
|
||||
sudo chown -R :developers /opt/projects
|
||||
sudo chmod -R g+w /opt/projects
|
||||
|
||||
# Add users to developers group
|
||||
sudo usermod -a -G developers <username>
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential VSCode Remote Commands
|
||||
|
||||
```bash
|
||||
# Command Palette shortcuts
|
||||
Ctrl+Shift+P → "Remote-SSH: Connect to Host..."
|
||||
Ctrl+Shift+P → "Remote-SSH: Open SSH Configuration File..."
|
||||
Ctrl+Shift+P → "Remote-SSH: Kill VS Code Server on Host..."
|
||||
|
||||
# Terminal
|
||||
Ctrl+` → Open integrated terminal
|
||||
Ctrl+Shift+` → Create new terminal
|
||||
|
||||
# File operations
|
||||
Ctrl+O → Open file
|
||||
Ctrl+S → Save file
|
||||
Ctrl+Shift+E → Focus file explorer
|
||||
```
|
||||
|
||||
### SSH Connection Quick Test
|
||||
|
||||
```bash
|
||||
# Test connectivity
|
||||
ssh -T node1
|
||||
|
||||
# Connect with verbose output
|
||||
ssh -v <username>@<tailscale-ip>
|
||||
|
||||
# Check SSH config
|
||||
ssh -F ~/.ssh/config node1
|
||||
```
|
||||
|
||||
### Port Forwarding Commands
|
||||
|
||||
```bash
|
||||
# Manual port forwarding
|
||||
ssh -L 3000:localhost:3000 <username>@<tailscale-ip>
|
||||
|
||||
# Background tunnel
|
||||
ssh -f -N -L 8080:localhost:80 <username>@<tailscale-ip>
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
This remote development setup provides:
|
||||
|
||||
- **Full development environment** on remote servers
|
||||
- **Seamless file access** and editing capabilities
|
||||
- **Integrated debugging** and terminal access
|
||||
- **Port forwarding** for web development
|
||||
- **Extension ecosystem** available remotely
|
||||
- **Secure connections** through Tailscale network
|
||||
|
||||
The combination of VSCode Remote Development with Tailscale networking creates a powerful, flexible development environment that works from anywhere while maintaining security and performance.
|
||||
|
||||
Whether developing Python applications, Node.js services, or managing Docker containers, this setup provides a professional remote development experience that rivals local development while leveraging the power and resources of remote servers.
|
||||
@@ -1,485 +0,0 @@
|
||||
# Getting Started
|
||||
|
||||
Welcome to Changemaker-Lite! You're about to reclaim your digital sovereignty and stop feeding your secrets to corporations. This guide will help you set up your own political infrastructure that you actually own and control.
|
||||
|
||||
This documentation is broken into a few sections, which you can see in the navigation bar to the left:
|
||||
|
||||
- **Build:** Instructions on how to build the cm-lite on your own hardware
|
||||
- **Services:** Overview of all the services that are installed when you install cm-lite
|
||||
- **Configuration:** Information on how to configure all the services that you install in cm-lite
|
||||
- **Manuals:** Manuals on how to use the applications inside cm-lite (with videos!)
|
||||
|
||||
Of course, everything is also searachable, so if you want to find something specific, just use the search bar at the top right.
|
||||
|
||||
If you come across anything that is unclear, please open an issue in the [Git Repository](https://gitea.bnkops.com/admin/changemaker.lite), reach out to us at [admin@thebunkerops.ca](mailto:admin@thebunkerops.ca), or edit it yourself by clicking the pencil icon at the top right of each page.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Build Changemaker-Lite
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://gitea.bnkops.com/admin/changemaker.lite
|
||||
cd changemaker.lite
|
||||
```
|
||||
|
||||
!!! warning "Cloudflare Credentials"
|
||||
The config.sh script will ask you for your optional Cloudflare credentials to get started. You can find more information on how to find this in the [Cloudlflare Configuration](../config/cloudflare-config.md)
|
||||
|
||||
|
||||
```
|
||||
# Configure environment (creates .env file)
|
||||
./config.sh
|
||||
```
|
||||
|
||||
```
|
||||
# Start all services
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Optional - Site Builld
|
||||
|
||||
If you want to have your site prepared for launch, you can now proceed with reseting the site build. See [Build Site](../build/site.md) for more detials.
|
||||
|
||||
### Deploy
|
||||
|
||||
!!! note "Cloudflare"
|
||||
Right now, we suggest deploying using Cloudflare for simplicity and protections against 99% of surface level attacks to digital infrastructure. If you want to avoid using this service, we recommend checking out [Pagolin](https://github.com/fosrl/pangolin) as a drop in replacement.
|
||||
|
||||
For secure public access, use the production deployment script:
|
||||
|
||||
```bash
|
||||
./start-production.sh
|
||||
```
|
||||
|
||||
### Map
|
||||
|
||||
Map is the canvassing application that is custom view of nocodb data. Map is best built **after production deployment** to reduce duplicate build efforts.
|
||||
|
||||
Instructions on how to build the map are available in the [map manual](../build/map.md) in the build directory.
|
||||
|
||||
#### Quick Start for Map
|
||||
Get your NocoDB API token and URL, update the .env file in the map directory, and then run:
|
||||
|
||||
```
|
||||
cd map
|
||||
chmod +x build-nocodb.sh # builds the nocodb tables
|
||||
./build-nocodb.sh
|
||||
```
|
||||
Copy the urls of the newly created nocodb views and update the .env file in the map directory with them, and then run:
|
||||
|
||||
```
|
||||
cd map
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
You Map instance will be available at [http://localhost:3000](http://localhost:3000) or on the domain you set up during production deployment.
|
||||
|
||||
## Why Changemaker Lite?
|
||||
|
||||
Before we dive into the technical setup, let's be clear about what you're doing here:
|
||||
|
||||
!!! quote "The Reality"
|
||||
**If you do politics, who is reading your secrets?** Every corporate platform you use is extracting your power, selling your data, and building profiles on your community. It's time to break free.
|
||||
|
||||
### What You're Getting
|
||||
|
||||
- **Data Sovereignty**: Your data stays on your servers
|
||||
- **Cost Savings**: $50/month instead of $2,000+/month for corporate solutions
|
||||
- **Community Control**: Technology that serves movements, not shareholders
|
||||
- **Trans Liberation**: Tools built with radical politics and care
|
||||
|
||||
### What You're Leaving Behind
|
||||
|
||||
- ❌ Corporate surveillance and data extraction
|
||||
- ❌ Escalating subscription fees and vendor lock-in
|
||||
- ❌ Algorithmic manipulation of your community
|
||||
- ❌ Terms of service that can silence you anytime
|
||||
|
||||
---
|
||||
|
||||
## System Requirements
|
||||
|
||||
### Operating System
|
||||
|
||||
- **Ubuntu 24.04 LTS (Noble Numbat)** - Recommended and tested
|
||||
|
||||
!!! note "Getting Started on Ubuntu"
|
||||
Want some help getting started with a baseline buildout for a Ubuntu server? You can use our [BNKops Server Build Script](./server.md)
|
||||
|
||||
- Other Linux distributions with systemd support
|
||||
- WSL2 on Windows (limited functionality)
|
||||
- Mac OS
|
||||
|
||||
!!! tip "New to Linux?"
|
||||
Consider [Linux Mint](https://www.linuxmint.com/) - it looks like Windows but opens the door to true digital freedom.
|
||||
|
||||
### Hardware Requirements
|
||||
|
||||
- **CPU**: 2+ cores (4+ recommended)
|
||||
- **RAM**: 4GB minimum (8GB recommended)
|
||||
- **Storage**: 20GB+ available disk space
|
||||
- **Network**: Stable internet connection
|
||||
|
||||
!!! info "Cloud Hosting"
|
||||
You can run this on a VPS from providers like Hetzner, DigitalOcean, or Linode for ~$20/month.
|
||||
|
||||
### Software Prerequisites
|
||||
|
||||
Ensure the following software is installed on your system. The [BNKops Server Build Script](./server.md) can help set these up if you're on Ubuntu.
|
||||
|
||||
1. **Docker Engine** (24.0+)
|
||||
|
||||
```bash
|
||||
# Install Docker
|
||||
curl -fsSL https://get.docker.com | sudo sh
|
||||
|
||||
# Add your user to docker group
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
# Log out and back in for group changes to take effect
|
||||
```
|
||||
|
||||
2. **Docker Compose** (v2.20+)
|
||||
|
||||
```bash
|
||||
# Verify Docker Compose v2 is installed
|
||||
docker compose version
|
||||
```
|
||||
|
||||
3. **Essential Tools**
|
||||
|
||||
```bash
|
||||
# Install required packages
|
||||
sudo apt update
|
||||
sudo apt install -y git curl jq openssl
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Clone Repository
|
||||
|
||||
```bash
|
||||
git clone https://gitea.bnkops.com/admin/changemaker.lite
|
||||
cd changemaker.lite
|
||||
```
|
||||
|
||||
### 2. Run Configuration Wizard
|
||||
|
||||
The `config.sh` script will guide you through the initial setup:
|
||||
|
||||
```bash
|
||||
./config.sh
|
||||
```
|
||||
|
||||
This wizard will:
|
||||
|
||||
- ✅ Create a `.env` file with secure defaults
|
||||
- ✅ Scan for available ports to avoid conflicts
|
||||
- ✅ Set up your domain configuration
|
||||
- ✅ Generate secure passwords for databases
|
||||
- ✅ Configure Cloudflare credentials (optional)
|
||||
- ✅ Update all configuration files with your settings
|
||||
|
||||
#### Configuration Options
|
||||
|
||||
During setup, you'll be prompted for:
|
||||
|
||||
1. **Domain Name**: Your primary domain (e.g., `example.com`)
|
||||
2. **Cloudflare Settings** (optional):
|
||||
- API Token
|
||||
- Zone ID
|
||||
- Account ID
|
||||
3. **Admin Credentials**:
|
||||
- Listmonk admin email and password
|
||||
- n8n admin email and password
|
||||
|
||||
### 3. Start Services
|
||||
|
||||
Launch all services with Docker Compose:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Wait for services to initialize (first run may take 5-10 minutes):
|
||||
|
||||
```bash
|
||||
# Watch container status
|
||||
docker compose ps
|
||||
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
### 4. Verify Installation
|
||||
|
||||
Check that all services are running:
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
Expected output should show all services as "Up":
|
||||
|
||||
- code-server-changemaker
|
||||
- listmonk_app
|
||||
- listmonk_db
|
||||
- mkdocs-changemaker
|
||||
- mkdocs-site-server-changemaker
|
||||
- n8n-changemaker
|
||||
- nocodb
|
||||
- root_db
|
||||
- homepage-changemaker
|
||||
- gitea_changemaker
|
||||
- gitea_mysql_changemaker
|
||||
- mini-qr
|
||||
|
||||
## Local Access
|
||||
|
||||
Once services are running, access them locally:
|
||||
|
||||
### 🏠 Homepage Dashboard
|
||||
- **URL**: [http://localhost:3010](http://localhost:3010)
|
||||
- **Purpose**: Central hub for all services
|
||||
- **Features**: Service status, quick links, monitoring
|
||||
|
||||
### 💻 Development Tools
|
||||
- **Code Server**: [http://localhost:8888](http://localhost:8888) — VS Code in browser
|
||||
- **Gitea**: [http://localhost:3030](http://localhost:3030) — Git repository management
|
||||
- **MkDocs Dev**: [http://localhost:4000](http://localhost:4000) — Live documentation preview
|
||||
- **MkDocs Prod**: [http://localhost:4001](http://localhost:4001) — Built documentation
|
||||
|
||||
### 📧 Communication
|
||||
- **Listmonk**: [http://localhost:9000](http://localhost:9000) — Email campaigns
|
||||
_Login with credentials set during configuration_
|
||||
|
||||
### 🔄 Automation & Data
|
||||
- **n8n**: [http://localhost:5678](http://localhost:5678) — Workflow automation
|
||||
_Login with credentials set during configuration_
|
||||
- **NocoDB**: [http://localhost:8090](http://localhost:8090) — No-code database
|
||||
|
||||
### 🛠️ Interactive Tools
|
||||
- **Mini QR**: [http://localhost:8089](http://localhost:8089) — QR code generator
|
||||
|
||||
## Map
|
||||
|
||||
!!! warning "Map"
|
||||
Map is the canvassing application that is custom view of nocodb data. Map is best built **after production deployment** to reduce duplicate build efforts.
|
||||
|
||||
### [Map Manual](map.md)
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Deploy with Cloudflare Tunnels
|
||||
|
||||
For secure public access, use the production deployment script:
|
||||
|
||||
```bash
|
||||
./start-production.sh
|
||||
```
|
||||
|
||||
This script will:
|
||||
|
||||
1. Install and configure `cloudflared`
|
||||
2. Create a Cloudflare tunnel
|
||||
3. Set up DNS records automatically
|
||||
4. Configure access policies
|
||||
5. Create a systemd service for persistence
|
||||
|
||||
### What Happens During Production Setup
|
||||
|
||||
1. **Cloudflare Authentication**: Browser-based login to Cloudflare
|
||||
2. **Tunnel Creation**: Secure tunnel named `changemaker-lite`
|
||||
3. **DNS Configuration**: Automatic CNAME records for all services
|
||||
4. **Access Policies**: Email-based authentication for sensitive services
|
||||
5. **Service Installation**: Systemd service for automatic startup
|
||||
|
||||
### Production URLs
|
||||
|
||||
After successful deployment, services will be available at:
|
||||
|
||||
**Public Services**:
|
||||
|
||||
- `https://yourdomain.com` - Main documentation site
|
||||
- `https://listmonk.yourdomain.com` - Email campaigns
|
||||
- `https://docs.yourdomain.com` - Documentation preview
|
||||
- `https://n8n.yourdomain.com` - Automation platform
|
||||
- `https://db.yourdomain.com` - NocoDB
|
||||
- `https://git.yourdomain.com` - Gitea
|
||||
- `https://map.yourdomain.com` - Map viewer
|
||||
- `https://qr.yourdomain.com` - QR generator
|
||||
|
||||
**Protected Services** (require authentication):
|
||||
|
||||
- `https://homepage.yourdomain.com` - Dashboard
|
||||
- `https://code.yourdomain.com` - Code Server
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Key settings in `.env` file:
|
||||
|
||||
```env
|
||||
# Domain Configuration
|
||||
DOMAIN=yourdomain.com
|
||||
BASE_DOMAIN=https://yourdomain.com
|
||||
|
||||
# Service Ports (automatically assigned to avoid conflicts)
|
||||
HOMEPAGE_PORT=3010
|
||||
CODE_SERVER_PORT=8888
|
||||
LISTMONK_PORT=9000
|
||||
MKDOCS_PORT=4000
|
||||
MKDOCS_SITE_SERVER_PORT=4001
|
||||
N8N_PORT=5678
|
||||
NOCODB_PORT=8090
|
||||
GITEA_WEB_PORT=3030
|
||||
GITEA_SSH_PORT=2222
|
||||
MAP_PORT=3000
|
||||
MINI_QR_PORT=8089
|
||||
|
||||
# Cloudflare (for production)
|
||||
CF_API_TOKEN=your_token
|
||||
CF_ZONE_ID=your_zone_id
|
||||
CF_ACCOUNT_ID=your_account_id
|
||||
```
|
||||
|
||||
### Reconfigure Services
|
||||
|
||||
To update configuration:
|
||||
|
||||
```bash
|
||||
# Re-run configuration wizard
|
||||
./config.sh
|
||||
|
||||
# Restart services
|
||||
docker compose down && docker compose up -d
|
||||
```
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Service Management
|
||||
|
||||
```bash
|
||||
# View all services
|
||||
docker compose ps
|
||||
|
||||
# View logs for specific service
|
||||
docker compose logs -f [service-name]
|
||||
|
||||
# Restart a service
|
||||
docker compose restart [service-name]
|
||||
|
||||
# Stop all services
|
||||
docker compose down
|
||||
|
||||
# Stop and remove all data (CAUTION!)
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
### Backup Data
|
||||
|
||||
```bash
|
||||
# Backup all volumes
|
||||
docker run --rm -v changemaker_listmonk-data:/data -v $(pwd):/backup alpine tar czf /backup/listmonk-backup.tar.gz -C /data .
|
||||
|
||||
# Backup configuration
|
||||
tar czf configs-backup.tar.gz configs/
|
||||
|
||||
# Backup documentation
|
||||
tar czf docs-backup.tar.gz mkdocs/docs/
|
||||
```
|
||||
|
||||
### Update Services
|
||||
|
||||
```bash
|
||||
# Pull latest images
|
||||
docker compose pull
|
||||
|
||||
# Recreate containers with new images
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Port Conflicts
|
||||
|
||||
If services fail to start due to port conflicts:
|
||||
|
||||
1. Check which ports are in use:
|
||||
|
||||
```bash
|
||||
sudo ss -tulpn | grep LISTEN
|
||||
```
|
||||
|
||||
2. Re-run configuration to get new ports:
|
||||
|
||||
```bash
|
||||
./config.sh
|
||||
```
|
||||
|
||||
3. Or manually edit `.env` file and change conflicting ports
|
||||
|
||||
### Permission Issues
|
||||
|
||||
Fix permission problems:
|
||||
|
||||
```bash
|
||||
# Get your user and group IDs
|
||||
id -u # User ID
|
||||
id -g # Group ID
|
||||
|
||||
# Update .env file with correct IDs
|
||||
USER_ID=1000
|
||||
GROUP_ID=1000
|
||||
|
||||
# Restart services
|
||||
docker compose down && docker compose up -d
|
||||
```
|
||||
|
||||
### Service Won't Start
|
||||
|
||||
Debug service issues:
|
||||
|
||||
```bash
|
||||
# Check detailed logs
|
||||
docker compose logs [service-name] --tail 50
|
||||
|
||||
# Check container status
|
||||
docker ps -a
|
||||
|
||||
# Inspect container
|
||||
docker inspect [container-name]
|
||||
```
|
||||
|
||||
### Cloudflare Tunnel Issues
|
||||
|
||||
```bash
|
||||
# Check tunnel service status
|
||||
sudo systemctl status cloudflared-changemaker
|
||||
|
||||
# View tunnel logs
|
||||
sudo journalctl -u cloudflared-changemaker -f
|
||||
|
||||
# Restart tunnel
|
||||
sudo systemctl restart cloudflared-changemaker
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that your Changemaker Lite instance is running:
|
||||
|
||||
1. **Set up Listmonk** - Configure SMTP and create your first campaign
|
||||
2. **Create workflows** - Build automations in n8n
|
||||
3. **Import data** - Set up your NocoDB databases
|
||||
4. **Configure map** - Add location data for the map viewer
|
||||
5. **Write documentation** - Start creating content in MkDocs
|
||||
6. **Set up Git** - Initialize repositories in Gitea
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Check the [Services](../services/index.md) documentation for detailed guides
|
||||
- Review container logs for specific error messages
|
||||
- Ensure all prerequisites are properly installed
|
||||
- Verify your domain DNS settings for production deployment
|
||||
@@ -1,328 +0,0 @@
|
||||
# Influence Build Guide
|
||||
|
||||
Influence is BNKops campaign tool for connecting Alberta residents with their elected representatives across all levels of government.
|
||||
|
||||
!!! info "Complete Configuration"
|
||||
For detailed configuration, usage instructions, and troubleshooting, see the main [Influence README](https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/influence/README.MD).
|
||||
|
||||
!!! tip "Email Testing"
|
||||
The application includes MailHog integration for safe email testing during development. All test emails are caught locally and never sent to actual representatives.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker and Docker Compose installed
|
||||
- NocoDB instance with API access
|
||||
- SMTP email configuration (or use MailHog for testing)
|
||||
- Domain name (optional but recommended for production)
|
||||
|
||||
## Quick Build Process
|
||||
|
||||
### 1. Get NocoDB API Token
|
||||
|
||||
1. Login to your NocoDB instance
|
||||
2. Click user icon → **Account Settings** → **API Tokens**
|
||||
3. Create new token with read/write permissions
|
||||
4. Copy the token for the next step
|
||||
|
||||
### 2. Configure Environment
|
||||
|
||||
Navigate to the influence directory and create your environment file:
|
||||
|
||||
```bash
|
||||
cd influence
|
||||
cp example.env .env
|
||||
```
|
||||
|
||||
Edit the `.env` file with your configuration:
|
||||
|
||||
#### Development Mode Configuration
|
||||
|
||||
For development and testing, use MailHog to catch emails:
|
||||
|
||||
```env
|
||||
# Development Mode
|
||||
NODE_ENV=development
|
||||
EMAIL_TEST_MODE=true
|
||||
|
||||
# MailHog SMTP (for development)
|
||||
SMTP_HOST=mailhog
|
||||
SMTP_PORT=1025
|
||||
SMTP_SECURE=false
|
||||
SMTP_USER=test
|
||||
SMTP_PASS=test
|
||||
SMTP_FROM_EMAIL=dev@albertainfluence.local
|
||||
SMTP_FROM_NAME="BNKops Influence Campaign (DEV)"
|
||||
|
||||
# Email Testing
|
||||
TEST_EMAIL_RECIPIENT=developer@example.com
|
||||
```
|
||||
|
||||
### 3. Auto-Create Database Structure
|
||||
|
||||
Run the build script to create required NocoDB tables:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/build-nocodb.sh
|
||||
./scripts/build-nocodb.sh
|
||||
```
|
||||
|
||||
This creates six tables:
|
||||
- **Campaigns** - Campaign configurations with email templates and settings
|
||||
- **Campaign Emails** - Tracking of all emails sent through campaigns
|
||||
- **Representatives** - Cached representative data by postal code
|
||||
- **Email Logs** - System-wide email delivery logs
|
||||
- **Postal Codes** - Canadian postal code geolocation data
|
||||
- **Users** - Admin authentication and access control
|
||||
|
||||
### 4. Build and Deploy
|
||||
|
||||
Build the Docker image and start the application:
|
||||
|
||||
```bash
|
||||
# Build the Docker image
|
||||
docker compose build
|
||||
|
||||
# Start the application (includes MailHog in development)
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Verify Installation
|
||||
|
||||
1. Check container status:
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
2. View logs:
|
||||
```bash
|
||||
docker compose logs -f app
|
||||
```
|
||||
|
||||
3. Access the application:
|
||||
- **Main App**: http://localhost:3333
|
||||
- **Admin Panel**: http://localhost:3333/admin.html
|
||||
- **Email Testing** (dev): http://localhost:3333/email-test.html
|
||||
- **MailHog UI** (dev): http://localhost:8025
|
||||
|
||||
## Initial Setup
|
||||
|
||||
### 1. Create Admin User
|
||||
|
||||
Access the admin panel at `/admin.html` and create your first administrator account.
|
||||
|
||||
### 2. Create Your First Campaign
|
||||
|
||||
1. Login to the admin panel
|
||||
2. Click **"Create Campaign"**
|
||||
3. Configure basic settings:
|
||||
- Campaign title and description
|
||||
- Email subject and body template
|
||||
- Upload cover photo (optional)
|
||||
4. Set campaign options:
|
||||
- ✅ Allow SMTP Email - Enable server-side sending
|
||||
- ✅ Allow Mailto Link - Enable browser-based mailto
|
||||
- ✅ Collect User Info - Request name and email
|
||||
- ✅ Show Email Count - Display engagement metrics
|
||||
- ✅ Allow Email Editing - Let users customize message
|
||||
5. Select target government levels (Federal, Provincial, Municipal, School Board)
|
||||
6. Set status to **Active** to make campaign public
|
||||
7. Click **"Create Campaign"**
|
||||
|
||||
### 3. Test Representative Lookup
|
||||
|
||||
1. Visit the homepage
|
||||
2. Enter an Alberta postal code (e.g., T5N4B8)
|
||||
3. View representatives at all government levels
|
||||
4. Test email sending functionality
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Email Testing Interface
|
||||
|
||||
Access the email testing interface at `/email-test.html` (requires admin login):
|
||||
|
||||
**Features:**
|
||||
- 📧 **Quick Test** - Send test email with one click
|
||||
- 👁️ **Email Preview** - Preview email formatting before sending
|
||||
- ✏️ **Custom Composition** - Test with custom subject and message
|
||||
- 📊 **Email Logs** - View all sent emails with filtering
|
||||
- 🔧 **SMTP Diagnostics** - Test connection and troubleshoot
|
||||
|
||||
### MailHog Web Interface
|
||||
|
||||
Access MailHog at http://localhost:8025 to:
|
||||
- View all caught emails during development
|
||||
- Inspect email content, headers, and formatting
|
||||
- Search and filter test emails
|
||||
- Verify emails never leave your local environment
|
||||
|
||||
### Switching to Production
|
||||
|
||||
When ready to deploy to production:
|
||||
|
||||
1. Update `.env` with production SMTP settings:
|
||||
```env
|
||||
EMAIL_TEST_MODE=false
|
||||
NODE_ENV=production
|
||||
SMTP_HOST=smtp.your-provider.com
|
||||
SMTP_USER=your-real-email@domain.com
|
||||
SMTP_PASS=your-real-password
|
||||
```
|
||||
|
||||
2. Restart the application:
|
||||
```bash
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### Representative Lookup
|
||||
- Search by Alberta postal code (T prefix)
|
||||
- Display federal MPs, provincial MLAs, municipal representatives
|
||||
- Smart caching with NocoDB for fast performance
|
||||
- Graceful fallback to Represent API when cache unavailable
|
||||
|
||||
### Campaign System
|
||||
- Create unlimited advocacy campaigns
|
||||
- Upload cover photos for campaign pages
|
||||
- Customizable email templates
|
||||
- Optional user information collection
|
||||
- Toggle email count display for engagement metrics
|
||||
- Multi-level government targeting
|
||||
|
||||
### Email Integration
|
||||
- SMTP email sending with delivery confirmation
|
||||
- Mailto link support for browser-based email
|
||||
- Comprehensive email logging
|
||||
- Rate limiting for API protection
|
||||
- Test mode for safe development
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Public Endpoints
|
||||
- `GET /` - Homepage with representative lookup
|
||||
- `GET /campaign/:slug` - Individual campaign page
|
||||
- `GET /api/public/campaigns` - List active campaigns
|
||||
- `GET /api/representatives/by-postal/:postalCode` - Find representatives
|
||||
- `POST /api/emails/send` - Send campaign email
|
||||
|
||||
### Admin Endpoints (Authentication Required)
|
||||
- `GET /admin.html` - Campaign management dashboard
|
||||
- `GET /email-test.html` - Email testing interface
|
||||
- `POST /api/emails/preview` - Preview email without sending
|
||||
- `POST /api/emails/test` - Send test email
|
||||
- `GET /api/test-smtp` - Test SMTP connection
|
||||
|
||||
## Maintenance Commands
|
||||
|
||||
### Update Application
|
||||
```bash
|
||||
docker compose down
|
||||
git pull origin main
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Development Mode
|
||||
```bash
|
||||
cd app
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
# Follow application logs
|
||||
docker compose logs -f app
|
||||
|
||||
# View MailHog logs (development)
|
||||
docker compose logs -f mailhog
|
||||
```
|
||||
|
||||
### Database Backup
|
||||
```bash
|
||||
# Backup is handled through NocoDB
|
||||
# Access NocoDB admin panel to export tables
|
||||
```
|
||||
|
||||
### Health Check
|
||||
```bash
|
||||
curl http://localhost:3333/api/health
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### NocoDB Connection Issues
|
||||
- Verify `NOCODB_API_URL` and `NOCODB_API_TOKEN` in `.env`
|
||||
- Run `./scripts/build-nocodb.sh` to ensure tables exist
|
||||
- Application works without NocoDB (API fallback mode)
|
||||
|
||||
### Email Not Sending
|
||||
- In development: Check MailHog UI at http://localhost:8025
|
||||
- Verify SMTP credentials in `.env`
|
||||
- Use `/email-test.html` interface for diagnostics
|
||||
- Check email logs via admin panel
|
||||
- Review `docker compose logs -f app` for errors
|
||||
|
||||
### No Representatives Found
|
||||
- Ensure postal code starts with 'T' (Alberta only)
|
||||
- Try different postal code format (remove spaces)
|
||||
- Check Represent API status: `curl http://localhost:3333/api/test-represent`
|
||||
- Review application logs for API errors
|
||||
|
||||
### Campaign Not Appearing
|
||||
- Verify campaign status is set to "Active"
|
||||
- Check campaign configuration in admin panel
|
||||
- Clear browser cache and reload homepage
|
||||
- Review console for JavaScript errors
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Environment Configuration
|
||||
```env
|
||||
NODE_ENV=production
|
||||
EMAIL_TEST_MODE=false
|
||||
PORT=3333
|
||||
|
||||
# Use production SMTP settings
|
||||
SMTP_HOST=smtp.your-provider.com
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
SMTP_USER=your-production-email@domain.com
|
||||
SMTP_PASS=your-production-password
|
||||
```
|
||||
|
||||
### Docker Production
|
||||
```bash
|
||||
# Build and start in production mode
|
||||
docker compose -f docker-compose.yml up -d --build
|
||||
|
||||
# View logs
|
||||
docker compose logs -f app
|
||||
|
||||
# Monitor health
|
||||
watch curl http://localhost:3333/api/health
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
- Health check endpoint: `/api/health`
|
||||
- Email logs via admin panel
|
||||
- NocoDB integration status in logs
|
||||
- Rate limiting metrics in application logs
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- 🔒 Always use strong passwords for admin accounts
|
||||
- 🔒 Enable HTTPS in production (use reverse proxy)
|
||||
- 🔒 Rotate SMTP credentials regularly
|
||||
- 🔒 Monitor email logs for suspicious activity
|
||||
- 🔒 Set appropriate rate limits based on expected traffic
|
||||
- 🔒 Keep NocoDB API tokens secure and rotate periodically
|
||||
- 🔒 Use `EMAIL_TEST_MODE=false` only in production
|
||||
|
||||
## Support
|
||||
|
||||
For detailed configuration, troubleshooting, and usage instructions, see:
|
||||
- [Main Influence README](https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/influence/README.MD)
|
||||
- [Campaign Settings Guide](https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/influence/CAMPAIGN_SETTINGS_GUIDE.md)
|
||||
- [Files Explainer](https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/influence/files-explainer.md)
|
||||
@@ -1,217 +0,0 @@
|
||||
# Map Build Guide
|
||||
|
||||
Map is BNKops canvassing application built for community organizing and door-to-door canvassing.
|
||||
|
||||
!!! info "Complete Configuration"
|
||||
For detailed configuration, usage instructions, and troubleshooting, see the [Map Configuration Guide](../config/map.md).
|
||||
|
||||
!!! warning "Clean NocoDB"
|
||||
Currently the way to get a good result is to ensure the target nocodb database is empty. You can do this by deleting all bases. The script should still work with other volumes however may insert tables into odd locations; still debugging. Again, see config if needing to do manually.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker and Docker Compose installed
|
||||
- NocoDB instance with API access
|
||||
- Domain name (optional but recommended for production)
|
||||
|
||||
## Quick Build Process
|
||||
|
||||
### 1. Get NocoDB API Token
|
||||
|
||||
1. Login to your NocoDB instance
|
||||
2. Click user icon → **Account Settings** → **API Tokens**
|
||||
3. Create new token with read/write permissions
|
||||
4. Copy the token for the next step
|
||||
|
||||
### 2. Configure Environment
|
||||
|
||||
Edit the `.env` file in the `map/` directory:
|
||||
|
||||
```bash
|
||||
cd map
|
||||
```
|
||||
|
||||
Update your `.env` file with your NocoDB details, specifically the instance and api token:
|
||||
|
||||
```env
|
||||
NOCODB_API_URL=[change me]
|
||||
NOCODB_API_TOKEN=[change me]
|
||||
|
||||
# NocoDB View URL is the URL to your NocoDB view where the map data is stored.
|
||||
NOCODB_VIEW_URL=[change me]
|
||||
|
||||
# NOCODB_LOGIN_SHEET is the URL to your NocoDB login sheet.
|
||||
NOCODB_LOGIN_SHEET=[change me]
|
||||
|
||||
# NOCODB_SETTINGS_SHEET is the URL to your NocoDB settings sheet.
|
||||
NOCODB_SETTINGS_SHEET=[change me]
|
||||
|
||||
# NOCODB_SHIFTS_SHEET is the URL to your shifts sheet.
|
||||
NOCODB_SHIFTS_SHEET=[change me]
|
||||
|
||||
# NOCODB_SHIFT_SIGNUPS_SHEET is the URL to your NocoDB shift signups sheet where users can add their own shifts.
|
||||
NOCODB_SHIFT_SIGNUPS_SHEET=[change me]
|
||||
|
||||
# NOCODB_CUTS_SHEET is the URL to your NocoDB Cuts sheet.
|
||||
NOCODB_CUTS_SHEET=[change me]
|
||||
|
||||
DOMAIN=[change me]
|
||||
|
||||
# MkDocs Integration
|
||||
MKDOCS_URL=[change me]
|
||||
MKDOCS_SEARCH_URL=[change me]
|
||||
MKDOCS_SITE_SERVER_PORT=4002
|
||||
|
||||
# Server Configuration
|
||||
PORT=3000
|
||||
NODE_ENV=production
|
||||
|
||||
# Session Secret (IMPORTANT: Generate a secure random string for production)
|
||||
SESSION_SECRET=[change me]
|
||||
|
||||
# Map Defaults (Edmonton, Alberta, Canada)
|
||||
DEFAULT_LAT=53.5461
|
||||
DEFAULT_LNG=-113.4938
|
||||
DEFAULT_ZOOM=11
|
||||
|
||||
# Optional: Map Boundaries (prevents users from adding points outside area)
|
||||
# BOUND_NORTH=53.7
|
||||
# BOUND_SOUTH=53.4
|
||||
# BOUND_EAST=-113.3
|
||||
# BOUND_WEST=-113.7
|
||||
|
||||
# Cloudflare Settings
|
||||
TRUST_PROXY=true
|
||||
COOKIE_DOMAIN=[change me]
|
||||
|
||||
# Update NODE_ENV to production for HTTPS
|
||||
NODE_ENV=production
|
||||
|
||||
# Add allowed origin
|
||||
ALLOWED_ORIGINS=[change me]
|
||||
|
||||
# SMTP Configuration
|
||||
SMTP_HOST=[change me]
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
SMTP_USER=[change me]
|
||||
SMTP_PASS=[change me]
|
||||
EMAIL_FROM_NAME="[change me]"
|
||||
EMAIL_FROM_ADDRESS=[change me]
|
||||
|
||||
# App Configuration
|
||||
APP_NAME="[change me]"
|
||||
|
||||
# Listmonk Configuration
|
||||
LISTMONK_API_URL=[change me]
|
||||
LISTMONK_USERNAME=[change me]
|
||||
LISTMONK_PASSWORD=[change me]
|
||||
LISTMONK_SYNC_ENABLED=true
|
||||
LISTMONK_INITIAL_SYNC=false # Set to true only for first run to sync existing data
|
||||
```
|
||||
|
||||
### 3. Auto-Create Database Structure
|
||||
|
||||
Run the build script to create required tables:
|
||||
|
||||
```bash
|
||||
chmod +x build-nocodb.sh
|
||||
./build-nocodb.sh
|
||||
```
|
||||
|
||||
This creates three tables:
|
||||
- **Locations** - Main map data with geo-location, contact info, support levels
|
||||
- **Login** - User authentication (email, name, admin flag)
|
||||
- **Settings** - Admin configuration and QR codes
|
||||
|
||||
### 4. Get Table URLs
|
||||
|
||||
After the script completes:
|
||||
|
||||
1. Login to your NocoDB instance
|
||||
2. Navigate to your project ("Map Viewer Project")
|
||||
3. Copy the view URLs for each table from your browser address bar
|
||||
4. URLs should look like: `https://your-nocodb.com/dashboard/#/nc/project-id/table-id`
|
||||
|
||||
### 5. Update Environment with URLs
|
||||
|
||||
Edit your `.env` file and add the table URLs:
|
||||
|
||||
```env
|
||||
# NocoDB View URL is the URL to your NocoDB view where the map data is stored.
|
||||
NOCODB_VIEW_URL=[change me]
|
||||
|
||||
# NOCODB_LOGIN_SHEET is the URL to your NocoDB login sheet.
|
||||
NOCODB_LOGIN_SHEET=[change me]
|
||||
|
||||
# NOCODB_SETTINGS_SHEET is the URL to your NocoDB settings sheet.
|
||||
NOCODB_SETTINGS_SHEET=[change me]
|
||||
|
||||
# NOCODB_SHIFTS_SHEET is the URL to your shifts sheet.
|
||||
NOCODB_SHIFTS_SHEET=[change me]
|
||||
|
||||
# NOCODB_SHIFT_SIGNUPS_SHEET is the URL to your NocoDB shift signups sheet where users can add their own shifts.
|
||||
NOCODB_SHIFT_SIGNUPS_SHEET=[change me]
|
||||
|
||||
# NOCODB_CUTS_SHEET is the URL to your NocoDB Cuts sheet.
|
||||
NOCODB_CUTS_SHEET=[change me]
|
||||
```
|
||||
|
||||
### 6. Build and Deploy
|
||||
|
||||
Build the Docker image and start the application:
|
||||
|
||||
```bash
|
||||
# Build the Docker image
|
||||
docker-compose build
|
||||
|
||||
# Start the application
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## Verify Installation
|
||||
|
||||
1. Check container status:
|
||||
```bash
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
2. View logs:
|
||||
```bash
|
||||
docker-compose logs -f map-viewer
|
||||
```
|
||||
|
||||
3. Access the application at `http://localhost:3000`
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Login**: Use an email from your Login table
|
||||
2. **Add Locations**: Click on the map to add new locations
|
||||
3. **Admin Panel**: Admin users can access `/admin.html` for configuration
|
||||
4. **Walk Sheets**: Generate printable canvassing forms with QR codes
|
||||
|
||||
## Maintenance Commands
|
||||
|
||||
### Update Application
|
||||
```bash
|
||||
docker-compose down
|
||||
git pull origin main
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Development Mode
|
||||
```bash
|
||||
cd app
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Health Check
|
||||
```bash
|
||||
curl http://localhost:3000/health
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For detailed configuration, troubleshooting, and usage instructions, see the [Map Configuration Guide](../config/map.md).
|
||||
@@ -1,148 +0,0 @@
|
||||
# BNKops Server Build
|
||||
|
||||
Purpose: a Ubuntu server build-out for general application
|
||||
|
||||
---
|
||||
|
||||
|
||||
This documentation is a overview of the full build out for a server OS and baseline for running Changemaker-lite. It is a manual to re-install this server on any machine.
|
||||
|
||||
All of the following systems are free and the majority are open source.
|
||||
## [Ubuntu](https://ubuntu.com/) OS
|
||||
_Ubuntu_ is a Linux distribution derived from Debian and composed mostly of free and open-source software.
|
||||
### [Install Ubuntu](https://ubuntu.com/tutorials/install-ubuntu-desktop#1-overview)
|
||||
### Post Install
|
||||
Post installation, run update:
|
||||
```
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
```
|
||||
sudo apt upgrade
|
||||
```
|
||||
### Configuration
|
||||
Further configurations:
|
||||
|
||||
- User profile was updated to Automatically Login
|
||||
- Remote Desktop, Sharing, and Login have all been enabled.
|
||||
- Default system settings have been set to dark mode.
|
||||
|
||||
## [VSCode Insiders](https://code.visualstudio.com/insiders/)
|
||||
Visual Studio Code is a new choice of tool that combines the simplicity of a code editor with what developers need for the core edit-build-debug cycle.
|
||||
### Install Using App Centre
|
||||
|
||||
## [Obsidian](https://obsidian.md/)
|
||||
The free and flexible app for your private thoughts.
|
||||
### Install Using App Center
|
||||
|
||||
## [Curl](https://curl.se/)
|
||||
command line tool and library for transferring data with URLs (since 1998)
|
||||
### Install
|
||||
```
|
||||
sudo apt install curl
|
||||
```
|
||||
## [Glances](https://github.com/nicolargo/glances)
|
||||
Glances an Eye on your system. A top/htop alternative for GNU/Linux, BSD, Mac OS and Windows operating systems.
|
||||
### Install
|
||||
```
|
||||
sudo snap install glances
|
||||
```
|
||||
## [Syncthing](https://syncthing.net/)
|
||||
Syncthing is a continuous file synchronization program. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how it’s transmitted over the internet.
|
||||
### Install
|
||||
```
|
||||
# Add the release PGP keys:
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo curl -L -o /etc/apt/keyrings/syncthing-archive-keyring.gpg https://syncthing.net/release-key.gpg
|
||||
```
|
||||
|
||||
```
|
||||
# Add the "stable" channel to your APT sources:
|
||||
echo "deb [signed-by=/etc/apt/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable" | sudo tee /etc/apt/sources.list.d/syncthing.list
|
||||
```
|
||||
|
||||
```
|
||||
# Update and install syncthing:
|
||||
sudo apt-get update
|
||||
sudo apt-get install syncthing
|
||||
```
|
||||
### Post Install
|
||||
Run syncthing as a system service.
|
||||
```
|
||||
sudo systemctl start syncthing@yourusername
|
||||
```
|
||||
|
||||
```
|
||||
sudo systemctl enable syncthing@yourusername
|
||||
```
|
||||
## [Docker](https://www.docker.com/)
|
||||
Docker helps developers build, share, run, and verify applications anywhere — without tedious environment configuration or management.
|
||||
```
|
||||
# Add Docker's official GPG key:
|
||||
sudo apt-get update
|
||||
sudo apt-get install ca-certificates curl
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||||
|
||||
# Add the repository to Apt sources:
|
||||
echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
||||
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
```
|
||||
|
||||
```
|
||||
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
```
|
||||
|
||||
### Update Users
|
||||
```
|
||||
sudo groupadd docker
|
||||
```
|
||||
|
||||
```
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
```
|
||||
newgrp docker
|
||||
```
|
||||
|
||||
### Enable on Boot
|
||||
```
|
||||
sudo systemctl enable docker.service
|
||||
sudo systemctl enable containerd.service
|
||||
```
|
||||
## [Cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)
|
||||
Connect, protect, and build everywhere. We make websites, apps, and networks faster and more secure. Our developer platform is the best place to build modern apps and deliver AI initiatives.
|
||||
|
||||
```
|
||||
sudo mkdir -p --mode=0755 /usr/share/keyrings
|
||||
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
|
||||
```
|
||||
|
||||
```
|
||||
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
|
||||
```
|
||||
|
||||
```
|
||||
sudo apt-get update && sudo apt-get install cloudflared
|
||||
```
|
||||
### Post Install
|
||||
Login to Cloudflare
|
||||
```
|
||||
cloudflared login
|
||||
```
|
||||
### Configuration
|
||||
|
||||
The `./config.sh` and `./start-production.sh` scripts will properly configure a Cloudflare tunnel and service to put your system online. More info in the [Cloudflare Configuration.](../config/cloudflare-config.md)
|
||||
|
||||
## [Pandoc](https://pandoc.org/)
|
||||
If you need to convert files from one markup format into another, pandoc is your swiss-army knife.
|
||||
|
||||
```
|
||||
sudo apt install pandoc
|
||||
```
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
# Building the Site with MkDocs Material
|
||||
|
||||
Welcome! This guide will help you get started building and customizing your site using [MkDocs Material](https://squidfunk.github.io/mkdocs-material/).
|
||||
|
||||
---
|
||||
|
||||
## Reset Site
|
||||
|
||||
You can read through all the BNKops cmlite documentation already in your docs folder or you can reset your docs folder to a baseline to start and read more manuals here. To reset docs folder to baseline, run the following:
|
||||
|
||||
```bash
|
||||
./reset-site.sh
|
||||
```
|
||||
|
||||
|
||||
## 🚀 How to Build Your Site (Step by Step)
|
||||
|
||||
1. **Open your Coder instance.**
|
||||
For example: coder.yourdomain.com
|
||||
2. **Go to the mkdocs folder:**
|
||||
In the terminal (for a new terminal press Crtl - Shift - ~), type:
|
||||
```sh
|
||||
cd mkdocs
|
||||
```
|
||||
3. **Build the site:**
|
||||
Type:
|
||||
```sh
|
||||
mkdocs build
|
||||
```
|
||||
This creates the static website from your documents and places them in the `mkdocs/site` directory.
|
||||
|
||||
**Preview your site locally:**
|
||||
Visit [localhost:4000](localhost:4000) for local development or `live.youdomain.com` to see a public live load.
|
||||
|
||||
- All documentation in the `mkdocs/docs` folder is included automatically.
|
||||
- The site uses the beautiful and easy-to-use Material for MkDocs theme.
|
||||
|
||||
[Material for MkDocs Documentation :material-arrow-right:](https://squidfunk.github.io/mkdocs-material/){ .md-button }
|
||||
|
||||
!!! note "Build vs Serve"
|
||||
Your website is built in stages. Any edits to documents in the mkdocs directory are instantly served and visible at [localhost:4000](localhost:4000) or if in production mode live.yourdomain.com. **The live site is not meant as a public access point and will crash if too many requests are made to it**.
|
||||
|
||||
Running `mkdocs build` pushes any changes to the `site` directory, which then a ngnix server pushes them to the production server for public access at your root domain (yourdomain.com).
|
||||
|
||||
You can think of it as serve/live = draft for personal review and build = save/push to production for the public.
|
||||
|
||||
This combination allows for rapid development of documentation while ensuring your live site does not get updated until your content is ready.
|
||||
|
||||
---
|
||||
|
||||
## 🧹 Resetting the Site
|
||||
|
||||
If you want to start fresh:
|
||||
|
||||
1. **Delete all folders EXCEPT these folders:**
|
||||
- `/blog`
|
||||
- `/javascripts`
|
||||
- `/hooks`
|
||||
- `/assets`
|
||||
- `/stylesheets`
|
||||
- `/overrides`
|
||||
|
||||
2. **Reset the landing page:**
|
||||
- Open the main `index.md` file and remove everything at the very top (the "front matter").
|
||||
- *Or* edit `/overrides/home.html` to change the landing page.
|
||||
|
||||
3. **Reset the `mkdocs.yml`**
|
||||
- Open `mkdocs.yml` and delete the `nav` section entirely.
|
||||
- This action will enable mkdocs to build your site navigation based on file names in the root directory.
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Using AI to Help Build Your Site
|
||||
|
||||
- If you have a [claude.ai](https://claude.ai/) subscription, you can use powerful AI in your Coder terminal to write or rewrite pages, including a new `home.html`.
|
||||
- All you need to do is open the terminal and type:
|
||||
```sh
|
||||
claude
|
||||
```
|
||||
- You can also try local AI tools like [Ollama](https://ollama.com/) for on-demand help.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ First-Time Setup Tips
|
||||
|
||||
- **Navigation:**
|
||||
Open `mkdocs.yml` and remove the `nav` section to start with a blank menu. Add your own pages as you go.
|
||||
- **Customize the look:**
|
||||
Check out the [Material for MkDocs customization guide](https://squidfunk.github.io/mkdocs-material/setup/changing-the-theme/).
|
||||
- **Live preview:**
|
||||
Use `mkdocs serve` (see above) to see changes instantly as you edit.
|
||||
- **Custom files:**
|
||||
Put your own CSS, JavaScript, or HTML in `/assets`, `/stylesheets`, `/javascripts`, or `/overrides`.
|
||||
|
||||
[Quick Start Guide :material-arrow-right:](https://squidfunk.github.io/mkdocs-material/creating-your-site/){ .md-button }
|
||||
|
||||
---
|
||||
|
||||
## 📚 More Resources
|
||||
|
||||
- [MkDocs User Guide :material-arrow-right:](https://www.mkdocs.org/user-guide/){ .md-button }
|
||||
- [Material for MkDocs Features :material-arrow-right:](https://squidfunk.github.io/mkdocs-material/setup/){ .md-button }
|
||||
- [BNKops MKdocs Configuration & Customization](../config/mkdocs.md){ .md-button }
|
||||
|
||||
---
|
||||
|
||||
Happy building!
|
||||
@@ -1,61 +0,0 @@
|
||||
# Configure Cloudflare
|
||||
|
||||
Cloudflare is the largest DNS routing service on the planet. We use their free service tier to provide Changemaker users with a fast, secure, and reliable way to get online that blocks 99% of surface level attacks and has built in user authenticaion (if you so choose to use it)
|
||||
|
||||
## Credentials
|
||||
|
||||
The `config.sh` and `start-production.sh` scripts require the following Cloudflare credentials to function properly:
|
||||
|
||||
### 1. **Cloudflare API Token**
|
||||
|
||||
- **Purpose**: Used to authenticate API requests to Cloudflare for managing DNS records, tunnels, and access policies.
|
||||
- **Required Permissions**:
|
||||
- `Zone.DNS` (Read/Write)
|
||||
- `Account.Cloudflare Tunnel` (Read/Write)
|
||||
- `Access` (Read/Write)
|
||||
- **How to Obtain**:
|
||||
- Log in to your Cloudflare account.
|
||||
- Go to **My Profile** > **API Tokens** > **Create Token**.
|
||||
- Use the **Edit zone DNS** template and add **Cloudflare Tunnel** permissions.
|
||||
|
||||
### 2. **Cloudflare Zone ID**
|
||||
|
||||
- **Purpose**: Identifies the specific DNS zone (domain) in Cloudflare where DNS records will be created.
|
||||
- **How to Obtain**:
|
||||
- Log in to your Cloudflare account.
|
||||
- Select the domain you want to use.
|
||||
- The Zone ID is displayed in the **Overview** section under **API**.
|
||||
|
||||
### 3. **Cloudflare Account ID**
|
||||
|
||||
- **Purpose**: Identifies your Cloudflare account for tunnel creation and management.
|
||||
- **How to Obtain**:
|
||||
- Log in to your Cloudflare account.
|
||||
- Go to **My Profile** > **API Tokens**.
|
||||
- The Account ID is displayed at the top of the page.
|
||||
|
||||
### 4. **Cloudflare Tunnel ID** (Optional in config.sh, Required in start-production.sh)
|
||||
|
||||
!!! note "Automatic Configuration of Tunnel"
|
||||
The `start-production.sh` script will automatically create a tunnel and system service for Cloudflare.
|
||||
|
||||
- **Purpose**: Identifies the specific Cloudflare Tunnel that will be used to route traffic to your services.
|
||||
- **How to Obtain**:
|
||||
- This is automatically generated when you create a tunnel using `cloudflared tunnel create` or via the Cloudflare dashboard.
|
||||
- The start-production.sh script will create this for you if it doesn't exist.
|
||||
|
||||
### Summary of Required Credentials:
|
||||
|
||||
```bash
|
||||
# In .env file
|
||||
CF_API_TOKEN=your_cloudflare_api_token
|
||||
CF_ZONE_ID=your_cloudflare_zone_id
|
||||
CF_ACCOUNT_ID=your_cloudflare_account_id
|
||||
CF_TUNNEL_ID=will_be_set_by_start_production # This will be set by start-production.sh
|
||||
```
|
||||
|
||||
### Notes:
|
||||
|
||||
- The config.sh script will prompt you for these credentials during setup.
|
||||
- The start-production.sh script will verify these credentials and use them to configure DNS records, create tunnels, and set up access policies.
|
||||
- Ensure that the API token has the correct permissions, or the scripts will fail to configure Cloudflare services.
|
||||
@@ -1,215 +0,0 @@
|
||||
# Coder Server Configuration
|
||||
|
||||
This section describes the configuration and features of the code-server environment.
|
||||
|
||||
## Accessing Code Server
|
||||
|
||||
- **URL:** `http://localhost:8080`
|
||||
- **Authentication:** Password-based (see below for password retrieval)
|
||||
|
||||
### Retrieving the Code Server Password
|
||||
|
||||
After the first build, the code-server password is stored in:
|
||||
|
||||
```
|
||||
configs/code-server/.config/code-server/config.yaml
|
||||
```
|
||||
|
||||
Look for the `password:` field in that file. For example:
|
||||
|
||||
```yaml
|
||||
password: 0c0dca951a2d12eff1665817
|
||||
```
|
||||
|
||||
> **Note:** It is recommended **not** to change this password manually, as it is securely generated.
|
||||
|
||||
## Main Configuration Options
|
||||
|
||||
- `bind-addr`: The address and port code-server listens on (default: `127.0.0.1:8080`)
|
||||
- `auth`: Authentication method (default: `password`)
|
||||
- `password`: The login password (see above)
|
||||
- `cert`: Whether to use HTTPS (default: `false`)
|
||||
|
||||
## Installed Tools and Features
|
||||
|
||||
The code-server environment includes:
|
||||
|
||||
- **Node.js 18+** and **npm**
|
||||
- **Claude Code** (`@anthropic-ai/claude-code`) globally installed
|
||||
- **Python 3** and tools:
|
||||
- `python3-pip`, `python3-venv`, `python3-full`, `pipx`
|
||||
- **Image and PDF processing libraries**:
|
||||
- `CairoSVG`, `Pillow`, `libcairo2-dev`, `libfreetype6-dev`, `libjpeg-dev`, `libpng-dev`, `libwebp-dev`, `libtiff5-dev`, `libopenjp2-7-dev`, `liblcms2-dev`
|
||||
- `weasyprint`, `fonts-roboto`
|
||||
- **Git** for version control and plugin management
|
||||
- **Build tools**: `build-essential`, `pkg-config`, `python3-dev`, `zlib1g-dev`
|
||||
- **MkDocs Material** and a wide range of MkDocs plugins, installed in a dedicated Python virtual environment at `/home/coder/.venv/mkdocs`
|
||||
- **Convenience script**: `run-mkdocs` for running MkDocs commands easily
|
||||
|
||||
### Using MkDocs
|
||||
|
||||
The virtual environment for MkDocs is automatically added to your `PATH`. You can run MkDocs commands directly, or use the provided script. For example, to build the site, from a clean terminal we would rung:
|
||||
|
||||
```bash
|
||||
cd mkdocs
|
||||
mkdocs build
|
||||
```
|
||||
|
||||
## Claude Code Integration
|
||||
|
||||
<div class="github-widget" data-repo="anthropics/claude-code"></div>
|
||||
|
||||
The code-server environment comes with **Claude Code** (`@anthropic-ai/claude-code`) globally installed via npm.
|
||||
|
||||
### What is Claude Code?
|
||||
|
||||
Claude Code is an AI-powered coding assistant by Anthropic, designed to help you write, refactor, and understand code directly within your development environment.
|
||||
|
||||
### Usage
|
||||
|
||||
- Access Claude Code features through the command palette or sidebar in code-server.
|
||||
- Use Claude Code to generate code, explain code snippets, or assist with documentation and refactoring tasks.
|
||||
- For more information, refer to the [Claude Code documentation](https://docs.anthropic.com/claude/docs/claude-code).
|
||||
|
||||
> **Note:** Claude Code requires an API key or account with Anthropic for full functionality. Refer to the extension settings for configuration.
|
||||
|
||||
### Call Claude
|
||||
|
||||
To use claude simply type claude into the terminal and follow instructions.
|
||||
|
||||
```bash
|
||||
claude
|
||||
```
|
||||
|
||||
## Shell Environment
|
||||
|
||||
The `.bashrc` is configured to include the MkDocs virtual environment and user-local binaries in your `PATH` for convenience.
|
||||
|
||||
## Code Navigation and Editing Features
|
||||
|
||||
The code-server environment provides robust code navigation and editing features, including:
|
||||
|
||||
- **IntelliSense**: Smart code completions based on variable types, function definitions, and imported modules.
|
||||
- **Code Navigation**: Easily navigate to definitions, references, and symbol searches within your codebase.
|
||||
- **Debugging Support**: Integrated debugging support for Node.js and Python, with breakpoints, call stacks, and interactive consoles.
|
||||
- **Terminal Access**: Built-in terminal access to run commands, scripts, and version control operations.
|
||||
|
||||
## Collaboration Features
|
||||
|
||||
Code-server includes features to support collaboration:
|
||||
|
||||
- **Live Share**: Collaborate in real-time with others, sharing your code and terminal sessions.
|
||||
- **ChatGPT Integration**: AI-powered code assistance and chat-based collaboration.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
When using code-server, consider the following security aspects:
|
||||
|
||||
- **Password Management**: The default password is securely generated. Do not share it or expose it in public repositories.
|
||||
- **Network Security**: Ensure that your firewall settings allow access to the code-server port (default: 8080) only from trusted networks.
|
||||
- **Data Privacy**: Be cautious when uploading sensitive data or code to the server. Use environment variables or secure vaults for sensitive information.
|
||||
|
||||
## Ollama Integration
|
||||
|
||||
<div class="github-widget" data-repo="ollama/ollama"></div>
|
||||
|
||||
The code-server environment includes **Ollama**, a tool for running large language models locally on your machine.
|
||||
|
||||
### What is Ollama?
|
||||
|
||||
Ollama is a lightweight, extensible framework for building and running language models locally. It provides a simple API for creating, running, and managing models, making it easy to integrate AI capabilities into your development workflow without relying on external services.
|
||||
|
||||
### Getting Started with Ollama
|
||||
|
||||
#### Staring Ollama
|
||||
|
||||
For ollama to be available, you need to open a terminal and run:
|
||||
|
||||
```bash
|
||||
ollama serve
|
||||
```
|
||||
|
||||
This will start the ollama server and you can then proceed to pulling a model and chatting.
|
||||
|
||||
#### Pulling a Model
|
||||
|
||||
To get started, you'll need to pull a model. For development and testing, we recommend starting with a smaller model like Gemma 2B:
|
||||
|
||||
```bash
|
||||
ollama pull gemma2:2b
|
||||
```
|
||||
|
||||
For even lighter resource usage, you can use the 1B parameter version:
|
||||
|
||||
```bash
|
||||
ollama pull gemma2:1b
|
||||
```
|
||||
|
||||
#### Running a Model
|
||||
|
||||
Once you've pulled a model, you can start an interactive session:
|
||||
|
||||
```bash
|
||||
ollama run gemma2:2b
|
||||
```
|
||||
|
||||
#### Available Models
|
||||
|
||||
Popular models available through Ollama include:
|
||||
|
||||
- **Gemma 2** (1B, 2B, 9B, 27B): Google's efficient language models
|
||||
- **Llama 3.2** (1B, 3B, 11B, 90B): Meta's latest language models
|
||||
- **Qwen 2.5** (0.5B, 1.5B, 3B, 7B, 14B, 32B, 72B): Alibaba's multilingual models
|
||||
- **Phi 3.5** (3.8B): Microsoft's compact language model
|
||||
- **Code Llama** (7B, 13B, 34B): Specialized for code generation
|
||||
|
||||
### Using Ollama in Your Development Workflow
|
||||
|
||||
#### API Access
|
||||
|
||||
Ollama provides a REST API that runs on `http://localhost:11434` by default. You can integrate this into your applications:
|
||||
|
||||
```bash
|
||||
curl http://localhost:11434/api/generate -d '{
|
||||
"model": "gemma2:2b",
|
||||
"prompt": "Write a Python function to calculate fibonacci numbers",
|
||||
"stream": false
|
||||
}'
|
||||
```
|
||||
|
||||
#### Model Management
|
||||
|
||||
List installed models:
|
||||
```bash
|
||||
ollama list
|
||||
```
|
||||
|
||||
Remove a model:
|
||||
```bash
|
||||
ollama rm gemma2:2b
|
||||
```
|
||||
|
||||
Show model information:
|
||||
```bash
|
||||
ollama show gemma2:2b
|
||||
```
|
||||
|
||||
### Resource Considerations
|
||||
|
||||
- **1B models**: Require ~1GB RAM, suitable for basic tasks and resource-constrained environments
|
||||
- **2B models**: Require ~2GB RAM, good balance of capability and resource usage
|
||||
- **Larger models**: Provide better performance but require significantly more resources
|
||||
|
||||
### Integration with Development Tools
|
||||
|
||||
Ollama can be integrated with various development tools and editors through its API, enabling features like:
|
||||
|
||||
- Code completion and generation
|
||||
- Documentation writing assistance
|
||||
- Code review and explanation
|
||||
- Automated testing suggestions
|
||||
|
||||
For more information, visit the [Ollama documentation](https://ollama.ai/docs).
|
||||
|
||||
For more detailed information on configuring and using code-server, refer to the official [code-server documentation](https://coder.com/docs/).
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# Configuration
|
||||
|
||||
There are several configuration steps to building a production ready Changemaker-Lite.
|
||||
|
||||
In the order we suggest doing them:
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
# Map Configuration
|
||||
|
||||
The Map system is a containerized web application that visualizes geographic data from NocoDB on an interactive map using Leaflet.js. It's designed for canvassing applications and community organizing.
|
||||
|
||||
## Features
|
||||
|
||||
- 🗺️ Interactive map visualization with OpenStreetMap
|
||||
- 📍 Real-time geolocation support for adding locations
|
||||
- ➕ Add new locations directly from the map interface
|
||||
- 🔄 Auto-refresh every 30 seconds
|
||||
- 📱 Responsive design for mobile devices
|
||||
- 🔒 Secure API proxy to protect NocoDB credentials
|
||||
- 👤 User authentication with login system
|
||||
- ⚙️ Admin panel for system configuration
|
||||
- 🎯 Configurable map start location
|
||||
- 📄 Walk Sheet generator for door-to-door canvassing
|
||||
- 🔗 QR code integration for digital resources
|
||||
- 🐳 Docker containerization for easy deployment
|
||||
- 🆓 100% open source (no proprietary dependencies)
|
||||
|
||||
## Setup Process Overview
|
||||
|
||||
The setup process involves several steps that must be completed in order:
|
||||
|
||||
1. **Get NocoDB API Token** - Create an API token in your NocoDB instance
|
||||
2. **Configure Environment** - Update the `.env` file with your NocoDB details
|
||||
3. **Auto-Create Database Structure** - Run the build script to create required tables
|
||||
4. **Get Table URLs** - Find and copy the URLs for the newly created tables
|
||||
5. **Update Environment with URLs** - Add the table URLs to your `.env` file
|
||||
6. **Build and Deploy** - Build the Docker image and start the application
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker and Docker Compose installed
|
||||
- NocoDB instance with API access
|
||||
- Domain name (optional but recommended for production)
|
||||
|
||||
## Step 1: Get NocoDB API Token
|
||||
|
||||
1. Login to your NocoDB instance
|
||||
2. Click your user icon → **Account Settings**
|
||||
3. Go to the **API Tokens** tab
|
||||
4. Click **Create new token**
|
||||
5. Set the following permissions:
|
||||
- **Read**: Yes
|
||||
- **Write**: Yes
|
||||
- **Delete**: Yes (optional, for admin functions)
|
||||
6. Copy the generated token - you'll need it for the next step
|
||||
|
||||
!!! warning "Token Security"
|
||||
Keep your API token secure and never commit it to version control. The token provides full access to your NocoDB data.
|
||||
|
||||
## Step 2: Configure Environment
|
||||
|
||||
Edit the `.env` file in the `map/` directory:
|
||||
|
||||
```env
|
||||
# NocoDB API Configuration
|
||||
NOCODB_API_URL=https://your-nocodb-instance.com/api/v1
|
||||
NOCODB_API_TOKEN=your-api-token-here
|
||||
|
||||
# These URLs will be populated after running build-nocodb.sh
|
||||
NOCODB_VIEW_URL=
|
||||
NOCODB_LOGIN_SHEET=
|
||||
NOCODB_SETTINGS_SHEET=
|
||||
|
||||
# Server Configuration
|
||||
PORT=3000
|
||||
NODE_ENV=production
|
||||
|
||||
# Session Secret (generate with: openssl rand -hex 32)
|
||||
SESSION_SECRET=your-secure-random-string
|
||||
|
||||
# Map Defaults (Edmonton, Alberta, Canada)
|
||||
DEFAULT_LAT=53.5461
|
||||
DEFAULT_LNG=-113.4938
|
||||
DEFAULT_ZOOM=11
|
||||
|
||||
# Optional: Map Boundaries (prevents users from adding points outside area)
|
||||
# BOUND_NORTH=53.7
|
||||
# BOUND_SOUTH=53.4
|
||||
# BOUND_EAST=-113.3
|
||||
# BOUND_WEST=-113.7
|
||||
|
||||
# Production Settings
|
||||
TRUST_PROXY=true
|
||||
COOKIE_DOMAIN=.yourdomain.com
|
||||
ALLOWED_ORIGINS=https://map.yourdomain.com,http://localhost:3000
|
||||
```
|
||||
|
||||
### Required Configuration
|
||||
|
||||
- `NOCODB_API_URL`: Your NocoDB instance API URL (usually ends with `/api/v1`)
|
||||
- `NOCODB_API_TOKEN`: The token you created in Step 1
|
||||
- `SESSION_SECRET`: Generate a secure random string for session encryption
|
||||
|
||||
### Optional Configuration
|
||||
|
||||
- `DEFAULT_LAT/LNG/ZOOM`: Default map center and zoom level
|
||||
- `BOUND_*`: Map boundaries to restrict where users can add points
|
||||
- `COOKIE_DOMAIN`: Your domain for cookie security
|
||||
- `ALLOWED_ORIGINS`: Comma-separated list of allowed origins for CORS
|
||||
|
||||
## Step 3: Auto-Create Database Structure
|
||||
|
||||
The `build-nocodb.sh` script will automatically create the required tables in your NocoDB instance.
|
||||
|
||||
```bash
|
||||
cd map
|
||||
chmod +x build-nocodb.sh
|
||||
./build-nocodb.sh
|
||||
```
|
||||
|
||||
### What the Script Creates
|
||||
|
||||
The script creates three tables with the following structure:
|
||||
|
||||
#### 1. Locations Table
|
||||
Main table for storing map data:
|
||||
|
||||
- `Geo-Location` (Geo-Data): Format "latitude;longitude"
|
||||
- `latitude` (Decimal): Precision 10, Scale 8
|
||||
- `longitude` (Decimal): Precision 11, Scale 8
|
||||
- `First Name` (Single Line Text): Person's first name
|
||||
- `Last Name` (Single Line Text): Person's last name
|
||||
- `Email` (Email): Email address
|
||||
- `Phone` (Single Line Text): Phone number
|
||||
- `Unit Number` (Single Line Text): Unit or apartment number
|
||||
- `Address` (Single Line Text): Street address
|
||||
- `Support Level` (Single Select): Options: "1", "2", "3", "4"
|
||||
- 1 = Strong Support (Green)
|
||||
- 2 = Moderate Support (Yellow)
|
||||
- 3 = Low Support (Orange)
|
||||
- 4 = No Support (Red)
|
||||
- `Sign` (Checkbox): Has campaign sign
|
||||
- `Sign Size` (Single Select): Options: "Regular", "Large", "Unsure"
|
||||
- `Notes` (Long Text): Additional details and comments
|
||||
|
||||
#### 2. Login Table
|
||||
User authentication table:
|
||||
|
||||
- `Email` (Email): User email address (Primary)
|
||||
- `Name` (Single Line Text): User display name
|
||||
- `Admin` (Checkbox): Admin privileges
|
||||
|
||||
#### 3. Settings Table
|
||||
Admin configuration table:
|
||||
|
||||
- `key` (Single Line Text): Setting identifier
|
||||
- `title` (Single Line Text): Display name
|
||||
- `value` (Long Text): Setting value
|
||||
- `Geo-Location` (Text): Format "latitude;longitude"
|
||||
- `latitude` (Decimal): Precision 10, Scale 8
|
||||
- `longitude` (Decimal): Precision 11, Scale 8
|
||||
- `zoom` (Number): Map zoom level
|
||||
- `category` (Single Select): Setting category
|
||||
- `updated_by` (Single Line Text): Last updater email
|
||||
- `updated_at` (DateTime): Last update time
|
||||
- `qr_code_1_image` (Attachment): QR code 1 image
|
||||
- `qr_code_2_image` (Attachment): QR code 2 image
|
||||
- `qr_code_3_image` (Attachment): QR code 3 image
|
||||
|
||||
### Default Data
|
||||
|
||||
The script also creates:
|
||||
- A default admin user (admin@example.com)
|
||||
- A default start location setting
|
||||
|
||||
## Step 4: Get Table URLs
|
||||
|
||||
After the script completes successfully:
|
||||
|
||||
1. Login to your NocoDB instance
|
||||
2. Navigate to your project (should be named "Map Viewer Project")
|
||||
3. For each table, get the view URL:
|
||||
- Click on the table name
|
||||
- Copy the URL from your browser's address bar
|
||||
- The URL should look like: `https://your-nocodb.com/dashboard/#/nc/project-id/table-id`
|
||||
|
||||
You need URLs for:
|
||||
- **Locations table** → `NOCODB_VIEW_URL`
|
||||
- **Login table** → `NOCODB_LOGIN_SHEET`
|
||||
- **Settings table** → `NOCODB_SETTINGS_SHEET`
|
||||
|
||||
## Step 5: Update Environment with URLs
|
||||
|
||||
Edit your `.env` file and add the table URLs:
|
||||
|
||||
```env
|
||||
# Update these with the actual URLs from your NocoDB instance
|
||||
NOCODB_VIEW_URL=https://your-nocodb.com/dashboard/#/nc/project-id/locations-table-id
|
||||
NOCODB_LOGIN_SHEET=https://your-nocodb.com/dashboard/#/nc/project-id/login-table-id
|
||||
NOCODB_SETTINGS_SHEET=https://your-nocodb.com/dashboard/#/nc/project-id/settings-table-id
|
||||
```
|
||||
|
||||
!!! warning "URL Format"
|
||||
Make sure to use the complete dashboard URLs, not the API URLs. The application will automatically extract the project and table IDs from these URLs.
|
||||
|
||||
## Step 6: Build and Deploy
|
||||
|
||||
Build the Docker image and start the application:
|
||||
|
||||
```bash
|
||||
# Build the Docker image
|
||||
docker-compose build
|
||||
|
||||
# Start the application
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Verify Deployment
|
||||
|
||||
1. Check that the container is running:
|
||||
```bash
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
2. Check the logs:
|
||||
```bash
|
||||
docker-compose logs -f map-viewer
|
||||
```
|
||||
|
||||
3. Access the application at `http://localhost:3000` (or your configured domain)
|
||||
|
||||
## Using the Map System
|
||||
|
||||
### User Interface
|
||||
|
||||
#### Main Map View
|
||||
- **Interactive Map**: Click and drag to navigate
|
||||
- **Add Location**: Click on the map to add a new location
|
||||
- **Search**: Use the search bar to find addresses
|
||||
- **Refresh**: Data refreshes automatically every 30 seconds
|
||||
|
||||
#### Location Markers
|
||||
- **Green**: Strong Support (Level 1)
|
||||
- **Yellow**: Moderate Support (Level 2)
|
||||
- **Orange**: Low Support (Level 3)
|
||||
- **Red**: No Support (Level 4)
|
||||
|
||||
#### Adding Locations
|
||||
1. Click on the map where you want to add a location
|
||||
2. Fill out the form with contact information
|
||||
3. Select support level and sign information
|
||||
4. Add any relevant notes
|
||||
5. Click "Save Location"
|
||||
|
||||
### Authentication
|
||||
|
||||
#### User Login
|
||||
- Users must be added to the Login table in NocoDB
|
||||
- Login with email address (no password required for simplified setup)
|
||||
- Admin users have additional privileges
|
||||
|
||||
#### Admin Access
|
||||
- Admin users can access `/admin.html`
|
||||
- Configure map start location
|
||||
- Set up walk sheet generator
|
||||
- Manage QR codes and settings
|
||||
|
||||
### Admin Panel Features
|
||||
|
||||
#### Start Location Configuration
|
||||
- **Interactive Map**: Visual interface for selecting coordinates
|
||||
- **Real-time Preview**: See changes immediately
|
||||
- **Validation**: Built-in coordinate and zoom level validation
|
||||
|
||||
#### Walk Sheet Generator
|
||||
- **Printable Forms**: Generate 8.5x11 walk sheets for door-to-door canvassing
|
||||
- **QR Code Integration**: Add up to 3 QR codes with custom URLs and labels
|
||||
- **Form Field Matching**: Automatically matches fields from the main location form
|
||||
- **Live Preview**: See changes as you type
|
||||
- **Print Optimization**: Proper formatting for printing or PDF export
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Public Endpoints
|
||||
- `GET /api/locations` - Fetch all locations (requires auth)
|
||||
- `POST /api/locations` - Create new location (requires auth)
|
||||
- `GET /api/locations/:id` - Get single location (requires auth)
|
||||
- `PUT /api/locations/:id` - Update location (requires auth)
|
||||
- `DELETE /api/locations/:id` - Delete location (requires auth)
|
||||
- `GET /api/config/start-location` - Get map start location
|
||||
- `GET /health` - Health check
|
||||
|
||||
### Authentication Endpoints
|
||||
- `POST /api/auth/login` - User login
|
||||
- `GET /api/auth/check` - Check authentication status
|
||||
- `POST /api/auth/logout` - User logout
|
||||
|
||||
### Admin Endpoints (requires admin privileges)
|
||||
- `GET /api/admin/start-location` - Get start location with source info
|
||||
- `POST /api/admin/start-location` - Update map start location
|
||||
- `GET /api/admin/walk-sheet-config` - Get walk sheet configuration
|
||||
- `POST /api/admin/walk-sheet-config` - Save walk sheet configuration
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Locations not showing
|
||||
- Verify table has required columns (`Geo-Location`, `latitude`, `longitude`)
|
||||
- Check that coordinates are valid numbers
|
||||
- Ensure API token has read permissions
|
||||
- Verify `NOCODB_VIEW_URL` is correct
|
||||
|
||||
#### Cannot add locations
|
||||
- Verify API token has write permissions
|
||||
- Check browser console for errors
|
||||
- Ensure coordinates are within valid ranges
|
||||
- Verify user is authenticated
|
||||
|
||||
#### Authentication issues
|
||||
- Verify login table is properly configured
|
||||
- Check that user email exists in Login table
|
||||
- Ensure `NOCODB_LOGIN_SHEET` URL is correct
|
||||
|
||||
#### Build script failures
|
||||
- Check that `NOCODB_API_URL` and `NOCODB_API_TOKEN` are correct
|
||||
- Verify NocoDB instance is accessible
|
||||
- Check network connectivity
|
||||
- Review script output for specific error messages
|
||||
|
||||
### Development Mode
|
||||
|
||||
For development and debugging:
|
||||
|
||||
```bash
|
||||
cd map/app
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This will start the application with hot reload and detailed logging.
|
||||
|
||||
### Logs and Monitoring
|
||||
|
||||
View application logs:
|
||||
```bash
|
||||
docker-compose logs -f map-viewer
|
||||
```
|
||||
|
||||
Check health status:
|
||||
```bash
|
||||
curl http://localhost:3000/health
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **API Token Security**: Keep tokens secure and rotate regularly
|
||||
2. **HTTPS**: Use HTTPS in production
|
||||
3. **CORS Configuration**: Set appropriate `ALLOWED_ORIGINS`
|
||||
4. **Cookie Security**: Configure `COOKIE_DOMAIN` properly
|
||||
5. **Input Validation**: All inputs are validated server-side
|
||||
6. **Rate Limiting**: API endpoints have rate limiting
|
||||
7. **Session Security**: Use a strong `SESSION_SECRET`
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Regular Updates
|
||||
```bash
|
||||
# Stop the application
|
||||
docker-compose down
|
||||
|
||||
# Pull updates (if using git)
|
||||
git pull origin main
|
||||
|
||||
# Rebuild and restart
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Backup Considerations
|
||||
- NocoDB data is stored in your NocoDB instance
|
||||
- Back up your `.env` file securely
|
||||
- Consider backing up QR code images from the Settings table
|
||||
|
||||
### Performance Tips
|
||||
- Monitor NocoDB performance and scaling
|
||||
- Consider enabling caching for high-traffic deployments
|
||||
- Use CDN for static assets if needed
|
||||
- Monitor Docker container resource usage
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check the troubleshooting section above
|
||||
2. Review NocoDB documentation
|
||||
3. Check Docker and Docker Compose documentation
|
||||
4. Open an issue on GitHub
|
||||
@@ -1,143 +0,0 @@
|
||||
# MkDocs Customization & Features Overview
|
||||
|
||||
BNKops has been building our own features, widgets, and css styles for MKdocs material theme.
|
||||
|
||||
This document explains the custom styling, repository widgets, and key features enabled in this MkDocs site.
|
||||
|
||||
For more info on how to build your site see [Site Build](../build/site.md)
|
||||
|
||||
---
|
||||
|
||||
## Using the Repository Widget in Documentation
|
||||
|
||||
You can embed repository widgets directly in your Markdown documentation to display live repository stats and metadata.
|
||||
To do this, add a `div` with the appropriate class and `data-repo` attribute for the repository you want to display.
|
||||
|
||||
**Example (for a Gitea repository):**
|
||||
```html
|
||||
<div class="gitea-widget" data-repo="admin/changemaker.lite"></div>
|
||||
```
|
||||
|
||||
This will render a styled card with information about the `admin/changemaker.lite` repository:
|
||||
|
||||
<div class="gitea-widget" data-repo="admin/changemaker.lite"></div>
|
||||
|
||||
**Options:**
|
||||
You can control the widget display with additional data attributes:
|
||||
- `data-show-description="false"` — Hide the description
|
||||
- `data-show-language="false"` — Hide the language
|
||||
- `data-show-last-update="false"` — Hide the last update date
|
||||
|
||||
**Example with options:**
|
||||
```html
|
||||
<div class="gitea-widget" data-repo="admin/changemaker.lite" data-show-description="false"></div>
|
||||
```
|
||||
|
||||
For GitHub repositories, use the `github-widget` class:
|
||||
```html
|
||||
<div class="github-widget" data-repo="lyqht/mini-qr"></div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom CSS Styling (`stylesheets/extra.css`)
|
||||
|
||||
The `extra.css` file provides extensive custom styling for the site, including:
|
||||
|
||||
- **Login and Git Code Buttons**:
|
||||
Custom styles for `.login-button` and `.git-code-button` to create visually distinct, modern buttons with hover effects.
|
||||
|
||||
- **Code Block Improvements**:
|
||||
Forces code blocks to wrap text (`white-space: pre-wrap`) and ensures inline code and tables with code display correctly on all devices.
|
||||
|
||||
- **GitHub Widget Styles**:
|
||||
Styles for `.github-widget` and its subcomponents, including:
|
||||
- Card-like container with gradient backgrounds and subtle box-shadows.
|
||||
- Header with icon, repo link, and stats (stars, forks, issues).
|
||||
- Description area with accent border.
|
||||
- Footer with language, last update, and license info.
|
||||
- Loading and error states with spinners and error messages.
|
||||
- Responsive grid layout for multiple widgets.
|
||||
- Compact variant for smaller displays.
|
||||
- Dark mode adjustments.
|
||||
|
||||
- **Gitea Widget Styles**:
|
||||
Similar to GitHub widget, but with Gitea branding (green accents).
|
||||
Includes `.gitea-widget`, `.gitea-widget-container`, and related classes for header, stats, description, footer, loading, and error states.
|
||||
|
||||
- **Responsive Design**:
|
||||
Media queries ensure widgets and tables look good on mobile devices.
|
||||
|
||||
---
|
||||
|
||||
## Repository Widgets
|
||||
|
||||
### Data Generation (`hooks/repo_widget_hook.py`)
|
||||
|
||||
- **Purpose**:
|
||||
During the MkDocs build, this hook fetches metadata for a list of GitHub and Gitea repositories and writes JSON files to `docs/assets/repo-data/`.
|
||||
- **How it works**:
|
||||
- Runs before build (unless in `serve` mode).
|
||||
- Fetches repo data (stars, forks, issues, language, etc.) via GitHub/Gitea APIs.
|
||||
- Outputs a JSON file per repo (e.g., `lyqht-mini-qr.json`).
|
||||
- Used by frontend widgets for fast, client-side rendering.
|
||||
|
||||
### GitHub Widget (`javascripts/github-widget.js`)
|
||||
|
||||
- **Purpose**:
|
||||
Renders a card for each GitHub repository using the pre-generated JSON data.
|
||||
- **Features**:
|
||||
- Displays repo name, link, stars, forks, open issues, language, last update, and license.
|
||||
- Shows loading spinner while fetching data.
|
||||
- Handles errors gracefully.
|
||||
- Supports dynamic content (re-initializes on DOM changes).
|
||||
- Language color coding for popular languages.
|
||||
|
||||
### Gitea Widget (`javascripts/gitea-widget.js`)
|
||||
|
||||
- **Purpose**:
|
||||
Renders a card for each Gitea repository using the pre-generated JSON data.
|
||||
- **Features**:
|
||||
- Similar to GitHub widget, but styled for Gitea.
|
||||
- Shows repo name, link, stars, forks, open issues, language, last update.
|
||||
- Loading and error states.
|
||||
- Language color coding.
|
||||
|
||||
---
|
||||
|
||||
## MkDocs Features (`mkdocs.yml`)
|
||||
|
||||
Key features and plugins enabled:
|
||||
|
||||
- **Material Theme**:
|
||||
Modern, responsive UI with dark/light mode toggle, custom fonts, and accent colors.
|
||||
|
||||
- **Navigation Enhancements**:
|
||||
- Tabs, sticky navigation, instant loading, breadcrumbs, and sectioned navigation.
|
||||
- Table of contents with permalinks.
|
||||
|
||||
- **Content Features**:
|
||||
- Code annotation, copy buttons, tooltips, and improved code highlighting.
|
||||
- Admonitions, tabbed content, task lists, and emoji support.
|
||||
|
||||
- **Plugins**:
|
||||
- **Search**: Advanced search with custom tokenization.
|
||||
- **Social**: OpenGraph/social card generation.
|
||||
- **Blog**: Blogging support with archives and categories.
|
||||
- **Tags**: Tagging for content organization.
|
||||
|
||||
- **Custom Hooks**:
|
||||
- `repo_widget_hook.py` for repository widget data.
|
||||
|
||||
- **Extra CSS/JS**:
|
||||
- Custom styles and scripts for widgets and homepage.
|
||||
|
||||
- **Extra Configuration**:
|
||||
- Social links, copyright.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This MkDocs site is highly customized for developer documentation, with visually rich repository widgets, improved code and table rendering, and a modern, responsive UI.
|
||||
All repository stats are fetched at build time for performance and reliability.
|
||||
@@ -1,129 +0,0 @@
|
||||
# V1 Documentation (Deprecated)
|
||||
|
||||
!!! warning "V1 is Legacy"
|
||||
Changemaker Lite V1 is deprecated and no longer actively maintained. These docs are preserved for reference only.
|
||||
|
||||
## Migrating to V2
|
||||
|
||||
Changemaker Lite V2 is a complete architectural rebuild with significant improvements:
|
||||
|
||||
A quick test because why not.
|
||||
|
||||
### Why Upgrade to V2?
|
||||
|
||||
**Modern Stack**
|
||||
- TypeScript throughout (V1 was JavaScript)
|
||||
- Prisma ORM (V1 used NocoDB REST API)
|
||||
- JWT auth (V1 used session cookies)
|
||||
- React admin (V1 used server-rendered HTML)
|
||||
|
||||
**Better Performance**
|
||||
- Direct database access (no NocoDB middleware)
|
||||
- Redis-backed caching and rate limiting
|
||||
- BullMQ job queues for async operations
|
||||
- Optimized queries with Prisma
|
||||
|
||||
**Enhanced Security**
|
||||
- Security audit completed (Feb 2026)
|
||||
- Password policy enforcement (12+ chars, complexity)
|
||||
- Refresh token rotation in transactions
|
||||
- XSS/injection prevention throughout
|
||||
- Rate limiting on all sensitive endpoints
|
||||
|
||||
**New Features**
|
||||
- Volunteer canvassing system with GPS tracking
|
||||
- Landing page builder (GrapesJS)
|
||||
- Email template management
|
||||
- Media library (video management)
|
||||
- Observability dashboard (Prometheus + Grafana)
|
||||
- NAR 2025 data import (Canadian electoral data)
|
||||
|
||||
[View complete V2 documentation →](../v2/index.md){ .md-button .md-button--primary }
|
||||
|
||||
## Migration Guide
|
||||
|
||||
Ready to migrate? Follow our step-by-step guide:
|
||||
|
||||
[→ V1 to V2 Migration Guide](../v2/migration/index.md){ .md-button }
|
||||
|
||||
The migration guide covers:
|
||||
|
||||
1. **Breaking Changes** - NocoDB → Prisma, API endpoint changes
|
||||
2. **Data Migration** - Export V1 data, transform, import to V2
|
||||
3. **Configuration Changes** - Environment variables, service names
|
||||
4. **Feature Parity** - V1 vs V2 feature comparison
|
||||
|
||||
## V1 Documentation Archive
|
||||
|
||||
These docs are preserved for existing V1 installations:
|
||||
|
||||
### Build Guides
|
||||
- [Build Overview](build/index.md)
|
||||
- [Build Server](build/server.md)
|
||||
- [Build Map](build/map.md)
|
||||
- [Build Influence](build/influence.md)
|
||||
- [Build Site](build/site.md)
|
||||
|
||||
### Services
|
||||
- [Services Overview](services/index.md)
|
||||
- [Homepage](services/homepage.md)
|
||||
- [Code Server](services/code-server.md)
|
||||
- [MKDocs](services/mkdocs.md)
|
||||
- [Listmonk](services/listmonk.md)
|
||||
- [PostgreSQL](services/postgresql.md)
|
||||
- [n8n](services/n8n.md)
|
||||
- [NocoDB](services/nocodb.md)
|
||||
- [Gitea](services/gitea.md)
|
||||
- [Map](services/map.md)
|
||||
- [Mini QR](services/mini-qr.md)
|
||||
|
||||
### Configuration
|
||||
- [Config Overview](config/index.md)
|
||||
- [Cloudflare](config/cloudflare-config.md)
|
||||
- [MKdocs](config/mkdocs.md)
|
||||
- [Code Server](config/coder.md)
|
||||
- [Map](config/map.md)
|
||||
|
||||
### Manuals
|
||||
- [Manual Overview](manual/index.md)
|
||||
- [Map Manual](manual/map.md)
|
||||
|
||||
### Advanced
|
||||
- [Advanced Overview](adv/index.md)
|
||||
- [SSH + Tailscale + Ansible](adv/ansible.md)
|
||||
- [SSH + VScode](adv/vscode-ssh.md)
|
||||
|
||||
## V1 Architecture (Reference)
|
||||
|
||||
V1 used a two-app architecture with NocoDB as the data layer:
|
||||
|
||||
**Influence App** (port 3333)
|
||||
- Express.js server with server-rendered HTML
|
||||
- NocoDB REST API for database operations
|
||||
- Session-based authentication (Redis)
|
||||
- Bull job queues for emails
|
||||
|
||||
**Map App** (port 3000)
|
||||
- Express.js server with Leaflet.js maps
|
||||
- NocoDB REST API for database operations
|
||||
- QR code generation
|
||||
- Volunteer shift management
|
||||
|
||||
**Shared Infrastructure**
|
||||
- NocoDB (data layer)
|
||||
- Redis (sessions, cache, queues)
|
||||
- PostgreSQL (via NocoDB)
|
||||
- Cloudflare tunnels
|
||||
|
||||
## Support for V1
|
||||
|
||||
V1 is no longer under active development. We recommend migrating to V2.
|
||||
|
||||
For critical V1 issues:
|
||||
- Check existing V1 documentation
|
||||
- Review V1 code in `/influence` and `/map` directories
|
||||
- Consider migrating to V2
|
||||
|
||||
---
|
||||
|
||||
**Ready to upgrade?** [Start with the V2 Quick Start Guide →](../v2/getting-started/quick-start.md)
|
||||
@@ -1,3 +0,0 @@
|
||||
# Manuals
|
||||
|
||||
The following are manuals, some accompanied by videos, on the use of the system.
|
||||
@@ -1,407 +0,0 @@
|
||||
|
||||
# Map System Manual
|
||||
|
||||
This comprehensive manual covers all features of the Map System - a powerful campaign management platform with interactive mapping, volunteer coordination, data management, and communication tools. *(Insert screenshot - feature overview)*
|
||||
|
||||
---
|
||||
|
||||
## 1. Getting Started
|
||||
|
||||
### Logging In
|
||||
1. Go to your map site URL (e.g., `https://yoursite.com` or `http://localhost:3000`).
|
||||
2. Enter your email and password on the login page.
|
||||
3. Click **Login**.
|
||||
4. If you forget your password, use the **Reset Password** link or contact an admin.
|
||||
5. **Password Recovery**: Check your email for reset instructions if SMTP is configured. *(Insert screenshot - login page)*
|
||||
|
||||
### User Types & Permissions
|
||||
- **Admin**: Full access to all features, user management, and system configuration
|
||||
- **User**: Access to map, shifts, profile management, and location data
|
||||
- **Temp**: Limited access (add/edit locations only, expires automatically after shift date)
|
||||
|
||||
---
|
||||
|
||||
## 2. Interactive Map Features
|
||||
|
||||
### Basic Map Navigation
|
||||
1. After login, you'll see the interactive map with location markers.
|
||||
2. Use mouse or touch to pan and zoom around the map.
|
||||
3. Your current location may appear as a blue dot (if location services enabled).
|
||||
4. Use the zoom controls (+/-) or mouse wheel to adjust map scale. *(Insert screenshot - main map view)*
|
||||
|
||||
### Advanced Search (Ctrl+K)
|
||||
1. Press **Ctrl+K** anywhere on the site to open the universal search.
|
||||
2. Search for:
|
||||
- **Addresses**: Find and navigate to specific locations
|
||||
- **Documentation**: Search help articles and guides
|
||||
- **Locations**: Find existing data points by name or details
|
||||
3. Click results to navigate directly to locations on the map.
|
||||
4. **QR Code Generation**: Search results include QR codes for easy mobile sharing. *(Insert screenshot - search interface)*
|
||||
|
||||
### Map Overlays (Cuts)
|
||||
1. **Public Cuts**: Geographic overlays (wards, neighborhoods, districts) are automatically displayed.
|
||||
2. **Cut Selector**: Use the multi-select dropdown to show/hide different cuts.
|
||||
3. **Mobile Interface**: On mobile, tap the 🗺️ button to manage overlays.
|
||||
4. **Legend**: View active cuts with color coding and labels.
|
||||
5. Cuts help organize and filter location data by geographic regions. *(Insert screenshot - cuts interface)*
|
||||
|
||||
---
|
||||
|
||||
## 3. Location Management
|
||||
|
||||
### Adding New Locations
|
||||
1. Click the **Add Location** button (+ icon) on the map.
|
||||
2. Click on the map where you want to place the new location.
|
||||
3. Fill out the comprehensive form:
|
||||
- **Personal**: First Name, Last Name, Email, Phone, Unit Number
|
||||
- **Political**: Support Level (1-4 scale), Party Affiliation
|
||||
- **Address**: Street Address (auto-geocoded when possible)
|
||||
- **Campaign**: Lawn Sign (Yes/No/Maybe), Sign Size, Volunteer Interest
|
||||
- **Notes**: Additional information and comments
|
||||
4. **Address Confirmation**: System validates and confirms addresses when possible.
|
||||
5. Click **Save** to add the location marker. *(Insert screenshot - add location form)*
|
||||
|
||||
### Editing and Managing Locations
|
||||
1. Click on any location marker to view details.
|
||||
2. **Popup Actions**:
|
||||
- **Edit**: Modify all location details
|
||||
- **Move**: Drag marker to new position (admin/user only)
|
||||
- **Delete**: Remove location (admin/user only - hidden for temp users)
|
||||
3. **Quick Actions**: Email, phone, or text contact directly from popup.
|
||||
4. **Support Level Color Coding**: Markers change color based on support level.
|
||||
5. **Apartment View**: Special clustering for apartment buildings. *(Insert screenshot - location popup)*
|
||||
|
||||
### Bulk Data Import
|
||||
1. **Admin Panel** → **Data Converter** → **Upload CSV**
|
||||
2. **Supported Formats**: CSV files with address data
|
||||
3. **Batch Geocoding**: Automatically converts addresses to coordinates
|
||||
4. **Progress Tracking**: Visual progress bar with success/failure reporting
|
||||
5. **Error Handling**: Downloadable error reports for failed geocoding
|
||||
6. **Validation**: Preview and verify data before final import
|
||||
7. **Edmonton Data**: Pre-configured for City of Edmonton neighborhood data. *(Insert screenshot - data import interface)*
|
||||
|
||||
---
|
||||
|
||||
## 4. Volunteer Shift Management
|
||||
|
||||
### Public Shift Signup (No Login Required)
|
||||
1. Visit the **Public Shifts** page (accessible without account).
|
||||
2. Browse available volunteer opportunities with:
|
||||
- Date, time, and location information
|
||||
- Available spots and current signups
|
||||
- Detailed shift descriptions
|
||||
3. **One-Click Signup**:
|
||||
- Enter name, email, and phone number
|
||||
- Automatic temporary account creation
|
||||
- Instant email confirmation with login details
|
||||
4. **Account Expiration**: Temp accounts automatically expire after shift date. *(Insert screenshot - public shifts page)*
|
||||
|
||||
### Authenticated User Shift Management
|
||||
1. Go to **Shifts** from the main navigation.
|
||||
2. **View Options**:
|
||||
- **Grid View**: List format with detailed information
|
||||
- **Calendar View**: Monthly calendar with shift visualization
|
||||
3. **Filter Options**: Date range, shift type, and availability status.
|
||||
4. **My Signups**: View your confirmed shifts at the top of the page.
|
||||
|
||||
### Shift Actions
|
||||
- **Sign Up**: Join available shifts (if spots remain)
|
||||
- **Cancel**: Remove yourself from shifts you've joined
|
||||
- **Calendar Export**: Add shifts to Google Calendar, Outlook, or Apple Calendar
|
||||
- **Shift Details**: View full descriptions, requirements, and coordinator info. *(Insert screenshot - shifts interface)*
|
||||
|
||||
---
|
||||
|
||||
## 5. Advanced Map Features
|
||||
|
||||
### Geographic Cuts System
|
||||
**What are Cuts?**: Polygon overlays that define geographic regions like wards, neighborhoods, or custom areas.
|
||||
|
||||
#### Viewing Cuts (All Users)
|
||||
1. **Auto-Display**: Public cuts appear automatically when map loads.
|
||||
2. **Multi-Select Control**: Desktop users see dropdown with checkboxes for each cut.
|
||||
3. **Mobile Modal**: Touch the 🗺️ button for full-screen cut management.
|
||||
4. **Quick Actions**: "Show All" / "Hide All" buttons for easy control.
|
||||
5. **Color Coding**: Each cut has unique colors and opacity settings. *(Insert screenshot - cuts display)*
|
||||
|
||||
#### Admin Cut Management
|
||||
1. **Admin Panel** → **Map Cuts** for full management interface.
|
||||
2. **Drawing Tools**: Click-to-add-points polygon creation system.
|
||||
3. **Cut Properties**:
|
||||
- Name, description, and category
|
||||
- Color and opacity customization
|
||||
- Public visibility settings
|
||||
- Official designation markers
|
||||
4. **Cut Operations**:
|
||||
- Create, edit, duplicate, and delete cuts
|
||||
- Import/export cut data as JSON
|
||||
- Location filtering within cut boundaries
|
||||
5. **Statistics Dashboard**: Analyze location data within cut boundaries.
|
||||
6. **Print Functionality**: Generate professional reports with maps and data tables. *(Insert screenshot - cut management)*
|
||||
|
||||
### Location Filtering within Cuts
|
||||
1. **View Cut**: Select a cut from the admin interface.
|
||||
2. **Filter Locations**: Automatically shows only locations within cut boundaries.
|
||||
3. **Statistics Panel**: Real-time counts of:
|
||||
- Total locations within cut
|
||||
- Support level breakdown (Strong/Lean/Undecided/Opposition)
|
||||
- Contact information availability (email/phone)
|
||||
- Lawn sign placements
|
||||
4. **Export Options**: Download filtered location data as CSV.
|
||||
5. **Print Reports**: Generate professional cut reports with statistics and location tables. *(Insert screenshot - cut filtering)*
|
||||
|
||||
---
|
||||
|
||||
## 6. Communication Tools
|
||||
|
||||
### Universal Search & Contact
|
||||
1. **Ctrl+K Search**: Find and contact anyone in your database instantly.
|
||||
2. **Direct Contact Links**: Email and phone links throughout the interface.
|
||||
3. **QR Code Generation**: Share contact information via QR codes.
|
||||
|
||||
### Admin Communication Features
|
||||
1. **Bulk Email System**:
|
||||
- Rich HTML email composer with formatting toolbar
|
||||
- Live email preview before sending
|
||||
- Broadcast to all users with progress tracking
|
||||
- Individual delivery status for each recipient
|
||||
2. **One-Click Communication Buttons**:
|
||||
- **📧 Email**: Launch email client with pre-filled recipient
|
||||
- **📞 Call**: Open phone dialer with contact's number
|
||||
- **💬 SMS**: Launch text messaging with contact's number
|
||||
3. **Shift Communication**:
|
||||
- Email shift details to all volunteers
|
||||
- Individual volunteer contact from shift management
|
||||
- Automated signup confirmations and reminders. *(Insert screenshot - communication tools)*
|
||||
|
||||
---
|
||||
|
||||
## 7. Walk Sheet Generator
|
||||
|
||||
### Creating Walk Sheets
|
||||
1. **Admin Panel** → **Walk Sheet Generator**
|
||||
2. **Configuration Options**:
|
||||
- Title, subtitle, and footer text
|
||||
- Contact information and instructions
|
||||
- QR codes for digital resources
|
||||
- Logo and branding elements
|
||||
3. **Location Selection**: Choose specific areas or use cut boundaries.
|
||||
4. **Print Options**: Multiple layout formats for different campaign needs.
|
||||
5. **QR Integration**: Add QR codes linking to:
|
||||
- Digital surveys or forms
|
||||
- Contact information
|
||||
- Campaign websites or resources. *(Insert screenshot - walk sheet generator)*
|
||||
|
||||
### Mobile-Optimized Walk Sheets
|
||||
1. **Responsive Design**: Optimized for viewing on phones and tablets.
|
||||
2. **QR Code Scanner Integration**: Quick scanning for volunteer check-ins.
|
||||
3. **Offline Capability**: Download for use without internet connection.
|
||||
|
||||
---
|
||||
|
||||
## 8. User Profile Management
|
||||
|
||||
### Personal Settings
|
||||
1. **User Menu** → **Profile** to access personal settings.
|
||||
2. **Account Information**:
|
||||
- Update name, email, and phone number
|
||||
- Change password
|
||||
- Communication preferences
|
||||
3. **Activity History**: View your shift signups and location contributions.
|
||||
4. **Privacy Settings**: Control data sharing and communication preferences. *(Insert screenshot - user profile)*
|
||||
|
||||
### Password Recovery
|
||||
1. **Forgot Password** link on login page.
|
||||
2. **Email Reset**: Automated password reset via SMTP (if configured).
|
||||
3. **Admin Assistance**: Contact administrators for manual password resets.
|
||||
|
||||
---
|
||||
|
||||
## 9. Admin Panel Features
|
||||
|
||||
### Dashboard Overview
|
||||
1. **System Statistics**: User counts, recent activity, and system health.
|
||||
2. **Quick Actions**: Direct access to common administrative tasks.
|
||||
3. **NocoDB Integration**: Direct links to database management interface. *(Insert screenshot - admin dashboard)*
|
||||
|
||||
### User Management
|
||||
1. **Create Users**: Add new accounts with role assignments:
|
||||
- **Regular Users**: Full access to mapping and shifts
|
||||
- **Temporary Users**: Limited access with automatic expiration
|
||||
- **Admin Users**: Full system administration privileges
|
||||
2. **User Communication**:
|
||||
- Send login details to new users
|
||||
- Bulk email all users with rich HTML composer
|
||||
- Individual user contact (email, call, text)
|
||||
3. **User Types & Expiration**:
|
||||
- Set expiration dates for temporary accounts
|
||||
- Visual indicators for user types and status
|
||||
- Automatic cleanup of expired accounts. *(Insert screenshot - user management)*
|
||||
|
||||
### Shift Administration
|
||||
1. **Create & Manage Shifts**:
|
||||
- Set dates, times, locations, and volunteer limits
|
||||
- Public/private visibility settings
|
||||
- Detailed descriptions and requirements
|
||||
2. **Volunteer Management**:
|
||||
- Add users directly to shifts
|
||||
- Remove volunteers when needed
|
||||
- Email shift details to all participants
|
||||
- Generate public signup links
|
||||
3. **Volunteer Communication**:
|
||||
- Individual contact buttons (email, call, text) for each volunteer
|
||||
- Bulk shift detail emails with delivery tracking
|
||||
- Automated confirmation and reminder systems. *(Insert screenshot - shift management)*
|
||||
|
||||
### System Configuration
|
||||
1. **Map Settings**:
|
||||
- Set default start location and zoom level
|
||||
- Configure map boundaries and restrictions
|
||||
- Customize marker styles and colors
|
||||
2. **Integration Management**:
|
||||
- NocoDB database connections
|
||||
- Listmonk email list synchronization
|
||||
- SMTP configuration for automated emails
|
||||
3. **Security Settings**:
|
||||
- User permissions and role management
|
||||
- API access controls
|
||||
- Session management. *(Insert screenshot - system config)*
|
||||
|
||||
---
|
||||
|
||||
## 10. Data Management & Integration
|
||||
|
||||
### NocoDB Database Integration
|
||||
1. **Direct Database Access**: Admin links to NocoDB sheets for advanced data management.
|
||||
2. **Automated Sync**: Real-time synchronization between map interface and database.
|
||||
3. **Backup & Migration**: Built-in tools for data backup and system migration.
|
||||
4. **Custom Fields**: Add custom data fields through NocoDB interface.
|
||||
|
||||
### Listmonk Email Marketing Integration
|
||||
1. **Automatic List Sync**: Map data automatically syncs to Listmonk email lists.
|
||||
2. **Segmentation**: Create targeted lists based on:
|
||||
- Geographic location (cuts/neighborhoods)
|
||||
- Support levels and volunteer interest
|
||||
- Contact preferences and activity
|
||||
3. **One-Direction Sync**: Maintains data integrity while allowing email unsubscribes.
|
||||
4. **Compliance**: Newsletter legislation compliance with opt-out capabilities. *(Insert screenshot - integration settings)*
|
||||
|
||||
### Data Export & Reporting
|
||||
1. **CSV Export**: Download location data, user lists, and shift reports.
|
||||
2. **Cut Reports**: Professional reports with statistics and location breakdowns.
|
||||
3. **Print-Ready Formats**: Optimized layouts for physical distribution.
|
||||
4. **Analytics Dashboard**: Track user engagement and system usage.
|
||||
|
||||
---
|
||||
|
||||
## 11. Mobile & Accessibility Features
|
||||
|
||||
### Mobile-Optimized Interface
|
||||
1. **Responsive Design**: Fully functional on phones and tablets.
|
||||
2. **Touch Navigation**: Optimized touch controls for map interaction.
|
||||
3. **Mobile-Specific Features**:
|
||||
- Cut management modal for overlay control
|
||||
- Simplified navigation and larger touch targets
|
||||
- Offline capability for basic functions
|
||||
|
||||
### Accessibility
|
||||
1. **Keyboard Navigation**: Full keyboard support throughout the interface.
|
||||
2. **Screen Reader Compatibility**: ARIA labels and semantic markup.
|
||||
3. **High Contrast Support**: Compatible with accessibility themes.
|
||||
4. **Text Scaling**: Responsive to browser zoom and text size settings.
|
||||
|
||||
---
|
||||
|
||||
## 12. Security & Privacy
|
||||
|
||||
### Data Protection
|
||||
1. **Server-Side Security**: All API tokens and credentials kept server-side only.
|
||||
2. **Input Validation**: Comprehensive validation and sanitization of all user inputs.
|
||||
3. **CORS Protection**: Cross-origin request security measures.
|
||||
4. **Rate Limiting**: Protection against abuse and automated attacks.
|
||||
|
||||
### User Privacy
|
||||
1. **Role-Based Access**: Users only see data appropriate to their permission level.
|
||||
2. **Temporary Account Expiration**: Automatic cleanup of temporary user data.
|
||||
3. **Audit Trails**: Logging of administrative actions and data changes.
|
||||
4. **Data Retention**: Configurable retention policies for different data types. *(Insert screenshot - security settings)*
|
||||
|
||||
### Authentication
|
||||
1. **Secure Login**: Password-based authentication with optional 2FA.
|
||||
2. **Session Management**: Automatic logout for expired sessions.
|
||||
3. **Password Policies**: Configurable password strength requirements.
|
||||
4. **Account Lockout**: Protection against brute force attacks.
|
||||
|
||||
---
|
||||
|
||||
## 13. Performance & System Requirements
|
||||
|
||||
### System Performance
|
||||
1. **Optimized Database Queries**: Reduced API calls by over 5000% for better performance.
|
||||
2. **Smart Caching**: Intelligent caching of frequently accessed data.
|
||||
3. **Progressive Loading**: Map data loads incrementally for faster initial page loads.
|
||||
4. **Background Sync**: Automatic data synchronization without blocking user interface.
|
||||
|
||||
### Browser Requirements
|
||||
1. **Modern Browsers**: Chrome, Firefox, Safari, Edge (recent versions).
|
||||
2. **JavaScript Required**: Full functionality requires JavaScript enabled.
|
||||
3. **Local Storage**: Uses browser storage for session management and caching.
|
||||
4. **Geolocation**: Optional location services for enhanced functionality.
|
||||
|
||||
---
|
||||
|
||||
## 14. Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
- **Locations not showing**: Check database connectivity, verify coordinates are valid, ensure API permissions allow read access.
|
||||
- **Cannot add locations**: Verify API write permissions, check coordinate bounds, ensure all required fields completed.
|
||||
- **Login problems**: Verify email/password, check account expiration (for temp users), contact admin for password reset.
|
||||
- **Map not loading**: Check internet connection, verify site URL, clear browser cache and cookies.
|
||||
- **Permission denied**: Confirm user role and permissions, check account expiration status, contact administrator.
|
||||
|
||||
### Performance Issues
|
||||
- **Slow loading**: Check internet connection, try refreshing the page, contact admin if problems persist.
|
||||
- **Database errors**: Contact system administrator, check NocoDB service status.
|
||||
- **Email not working**: Verify SMTP configuration (admin), check spam/junk folders.
|
||||
|
||||
### Mobile Issues
|
||||
- **Touch problems**: Ensure touch targets are accessible, try refreshing page, check for browser compatibility.
|
||||
- **Display issues**: Try rotating device, check browser zoom level, update to latest browser version.
|
||||
|
||||
---
|
||||
|
||||
## 15. Advanced Features
|
||||
|
||||
### API Access
|
||||
1. **RESTful API**: Programmatic access to map data and functionality.
|
||||
2. **Authentication**: Token-based API authentication for external integrations.
|
||||
3. **Rate Limiting**: API usage limits to ensure system stability.
|
||||
4. **Documentation**: Complete API documentation for developers.
|
||||
|
||||
### Customization Options
|
||||
1. **Theming**: Customizable color schemes and branding.
|
||||
2. **Field Configuration**: Add custom data fields through admin interface.
|
||||
3. **Workflow Customization**: Configurable user workflows and permissions.
|
||||
4. **Integration Hooks**: Webhook support for external system integration.
|
||||
|
||||
---
|
||||
|
||||
## 16. Getting Help & Support
|
||||
|
||||
### Built-in Help
|
||||
1. **Context Help**: Tooltips and help text throughout the interface.
|
||||
2. **Search Documentation**: Use Ctrl+K to search help articles and guides.
|
||||
3. **Status Messages**: Clear feedback for all user actions and system status.
|
||||
|
||||
### Administrator Support
|
||||
1. **Contact Admin**: Use the contact information provided during setup.
|
||||
2. **System Logs**: Administrators have access to detailed system logs for troubleshooting.
|
||||
3. **Database Direct Access**: Admins can access NocoDB directly for advanced data management.
|
||||
|
||||
### Community Resources
|
||||
1. **Documentation**: Comprehensive online documentation and guides.
|
||||
2. **GitHub Repository**: Access to source code and issue tracking.
|
||||
3. **Developer Community**: Active community for advanced customization and development.
|
||||
|
||||
For technical support, contact your system administrator or refer to the comprehensive documentation available through the help system. *(Insert screenshot - help resources)*
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# Code Server
|
||||
|
||||

|
||||
|
||||
<div class="github-widget" data-repo="coder/code-server"></div>
|
||||
|
||||
## Overview
|
||||
|
||||
Code Server provides a full Visual Studio Code experience in your web browser, allowing you to develop from any device. It runs on your server and provides access to your development environment through a web interface.
|
||||
|
||||
## Features
|
||||
|
||||
- Full VS Code experience in the browser
|
||||
- Extensions support
|
||||
- Terminal access
|
||||
- Git integration
|
||||
- File editing and management
|
||||
- Multi-language support
|
||||
|
||||
## Access
|
||||
|
||||
- **Default Port**: 8888
|
||||
- **URL**: `http://localhost:8888`
|
||||
- **Default Workspace**: `/home/coder/mkdocs/`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `DOCKER_USER`: The user to run code-server as (default: `coder`)
|
||||
- `DEFAULT_WORKSPACE`: Default workspace directory
|
||||
- `USER_ID`: User ID for file permissions
|
||||
- `GROUP_ID`: Group ID for file permissions
|
||||
|
||||
### Volumes
|
||||
|
||||
- `./configs/code-server/.config`: VS Code configuration
|
||||
- `./configs/code-server/.local`: Local data
|
||||
- `./mkdocs`: Main workspace directory
|
||||
|
||||
## Usage
|
||||
|
||||
1. Access Code Server at `http://localhost:8888`
|
||||
2. Open the `/home/coder/mkdocs/` workspace
|
||||
3. Start editing your documentation files
|
||||
4. Install extensions as needed
|
||||
5. Use the integrated terminal for commands
|
||||
|
||||
## Useful Extensions
|
||||
|
||||
Consider installing these extensions for better documentation work:
|
||||
|
||||
- Markdown All in One
|
||||
- Material Design Icons
|
||||
- GitLens
|
||||
- Docker
|
||||
- YAML
|
||||
|
||||
## Official Documentation
|
||||
|
||||
For more detailed information, visit the [official Code Server documentation](https://coder.com/docs/code-server).
|
||||
|
Before Width: | Height: | Size: 368 KiB |
|
Before Width: | Height: | Size: 252 KiB |
|
Before Width: | Height: | Size: 382 KiB |
@@ -1,57 +0,0 @@
|
||||
# Gitea
|
||||
|
||||

|
||||
|
||||
<div class="github-widget" data-repo="go-gitea/gitea"></div>
|
||||
|
||||
Self-hosted Git service for collaborative development.
|
||||
|
||||
## Overview
|
||||
|
||||
Gitea is a lightweight, self-hosted Git service similar to GitHub, GitLab, and Bitbucket. It provides a web interface for managing repositories, issues, pull requests, and more.
|
||||
|
||||
## Features
|
||||
|
||||
- Git repository hosting
|
||||
- Web-based interface
|
||||
- Issue tracking
|
||||
- Pull requests
|
||||
- Wiki and code review
|
||||
- Lightweight and easy to deploy
|
||||
|
||||
## Access
|
||||
|
||||
- **Default Web Port**: `${GITEA_WEB_PORT:-3030}` (default: 3030)
|
||||
- **Default SSH Port**: `${GITEA_SSH_PORT:-2222}` (default: 2222)
|
||||
- **URL**: `http://localhost:${GITEA_WEB_PORT:-3030}`
|
||||
- **Default Data Directory**: `/data/gitea`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `GITEA__database__DB_TYPE`: Database type (e.g., `sqlite3`, `mysql`, `postgres`)
|
||||
- `GITEA__database__HOST`: Database host (default: `${GITEA_DB_HOST:-gitea-db:3306}`)
|
||||
- `GITEA__database__NAME`: Database name (default: `${GITEA_DB_NAME:-gitea}`)
|
||||
- `GITEA__database__USER`: Database user (default: `${GITEA_DB_USER:-gitea}`)
|
||||
- `GITEA__database__PASSWD`: Database password (from `.env`)
|
||||
- `GITEA__server__ROOT_URL`: Root URL (e.g., `${GITEA_ROOT_URL}`)
|
||||
- `GITEA__server__HTTP_PORT`: Web port (default: 3000 inside container)
|
||||
- `GITEA__server__DOMAIN`: Domain (e.g., `${GITEA_DOMAIN}`)
|
||||
|
||||
### Volumes
|
||||
|
||||
- `gitea_data:/data`: Gitea configuration and data
|
||||
- `/etc/timezone:/etc/timezone:ro`
|
||||
- `/etc/localtime:/etc/localtime:ro`
|
||||
|
||||
## Usage
|
||||
|
||||
1. Access Gitea at `http://localhost:${GITEA_WEB_PORT:-3030}`
|
||||
2. Register or log in as an admin user
|
||||
3. Create or import repositories
|
||||
4. Collaborate with your team
|
||||
|
||||
## Official Documentation
|
||||
|
||||
For more details, visit the [official Gitea documentation](https://docs.gitea.com/).
|
||||
@@ -1,211 +0,0 @@
|
||||
# Homepage
|
||||
|
||||

|
||||
|
||||
<div class="github-widget" data-repo="gethomepage/homepage"></div>
|
||||
|
||||
Modern dashboard for accessing all your self-hosted services.
|
||||
|
||||
## Overview
|
||||
|
||||
Homepage is a modern, fully static, fast, secure fully configurable application dashboard with integrations for over 100 services. It provides a beautiful and customizable interface to access all your Changemaker Lite services from a single location.
|
||||
|
||||
## Features
|
||||
|
||||
- **Service Dashboard**: Central hub for all your applications
|
||||
- **Docker Integration**: Automatic service discovery and monitoring
|
||||
- **Customizable Layout**: Flexible grid-based layout system
|
||||
- **Service Widgets**: Live status and metrics for services
|
||||
- **Quick Search**: Fast navigation with built-in search
|
||||
- **Bookmarks**: Organize frequently used links
|
||||
- **Dark/Light Themes**: Multiple color schemes available
|
||||
- **Responsive Design**: Works on desktop and mobile devices
|
||||
|
||||
## Access
|
||||
|
||||
- **Default Port**: 3010
|
||||
- **URL**: `http://localhost:3010`
|
||||
- **Configuration**: YAML-based configuration files
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `HOMEPAGE_PORT`: External port mapping (default: 3010)
|
||||
- `PUID`: User ID for file permissions (default: 1000)
|
||||
- `PGID`: Group ID for file permissions (default: 1000)
|
||||
- `TZ`: Timezone setting (default: Etc/UTC)
|
||||
- `HOMEPAGE_ALLOWED_HOSTS`: Allowed hosts for the dashboard
|
||||
|
||||
### Configuration Files
|
||||
|
||||
Homepage uses YAML configuration files located in `./configs/homepage/`:
|
||||
|
||||
- `settings.yaml`: Global settings and theme configuration
|
||||
- `services.yaml`: Service definitions and widgets
|
||||
- `bookmarks.yaml`: Bookmark categories and links
|
||||
- `widgets.yaml`: Dashboard widgets configuration
|
||||
- `docker.yaml`: Docker integration settings
|
||||
|
||||
### Volumes
|
||||
|
||||
- `./configs/homepage:/app/config`: Configuration files
|
||||
- `./assets/icons:/app/public/icons`: Custom service icons
|
||||
- `./assets/images:/app/public/images`: Background images and assets
|
||||
- `/var/run/docker.sock:/var/run/docker.sock`: Docker socket for container monitoring
|
||||
|
||||
## Changemaker Lite Services
|
||||
|
||||
Homepage is pre-configured with all Changemaker Lite services:
|
||||
|
||||
### Essential Tools
|
||||
|
||||
- **Code Server** (Port 8888): VS Code in the browser
|
||||
- **Listmonk** (Port 9000): Newsletter & mailing list manager
|
||||
- **NocoDB** (Port 8090): No-code database platform
|
||||
|
||||
### Content & Documentation
|
||||
|
||||
- **MkDocs** (Port 4000): Live documentation server
|
||||
- **Static Site** (Port 4001): Built documentation hosting
|
||||
|
||||
### Automation & Data
|
||||
|
||||
- **n8n** (Port 5678): Workflow automation platform
|
||||
- **PostgreSQL** (Port 5432): Database backends
|
||||
|
||||
## Customization
|
||||
|
||||
### Adding Custom Services
|
||||
|
||||
Edit `configs/homepage/services.yaml` to add new services:
|
||||
|
||||
```yaml
|
||||
- Custom Category:
|
||||
- My Service:
|
||||
href: http://localhost:8080
|
||||
description: Custom service description
|
||||
icon: mdi-application
|
||||
widget:
|
||||
type: ping
|
||||
url: http://localhost:8080
|
||||
```
|
||||
|
||||
### Custom Icons
|
||||
|
||||
Add custom icons to `./assets/icons/` directory and reference them in services.yaml:
|
||||
|
||||
```yaml
|
||||
icon: /icons/my-custom-icon.png
|
||||
```
|
||||
|
||||
### Themes and Styling
|
||||
|
||||
Modify `configs/homepage/settings.yaml` to customize appearance:
|
||||
|
||||
```yaml
|
||||
theme: dark # or light
|
||||
color: purple # slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose
|
||||
```
|
||||
|
||||
### Widgets
|
||||
|
||||
Enable live monitoring widgets in `configs/homepage/services.yaml`:
|
||||
|
||||
```yaml
|
||||
- Service Name:
|
||||
widget:
|
||||
type: docker
|
||||
container: container-name
|
||||
server: my-docker
|
||||
```
|
||||
|
||||
## Service Monitoring
|
||||
|
||||
Homepage can display real-time status information for your services:
|
||||
|
||||
- **Docker Integration**: Container status and resource usage
|
||||
- **HTTP Ping**: Service availability monitoring
|
||||
- **Custom APIs**: Integration with service-specific APIs
|
||||
|
||||
## Docker Integration
|
||||
|
||||
Homepage monitors Docker containers automatically when configured:
|
||||
|
||||
1. Ensure Docker socket is mounted (`/var/run/docker.sock`)
|
||||
2. Configure container mappings in `docker.yaml`
|
||||
3. Add widget configurations to `services.yaml`
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Homepage runs with limited privileges
|
||||
- Configuration files should have appropriate permissions
|
||||
- Consider network isolation for production deployments
|
||||
- Use HTTPS for external access
|
||||
- Regularly update the Homepage image
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Configuration not loading**: Check YAML syntax in configuration files
|
||||
|
||||
```bash
|
||||
docker logs homepage-changemaker
|
||||
```
|
||||
|
||||
**Icons not displaying**: Verify icon paths and file permissions
|
||||
|
||||
```bash
|
||||
ls -la ./assets/icons/
|
||||
```
|
||||
|
||||
**Services not reachable**: Verify network connectivity between containers
|
||||
|
||||
```bash
|
||||
docker exec homepage-changemaker ping service-name
|
||||
```
|
||||
|
||||
**Widget data not updating**: Check Docker socket permissions and container access
|
||||
|
||||
```bash
|
||||
docker exec homepage-changemaker ls -la /var/run/docker.sock
|
||||
```
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Basic Service Widget
|
||||
|
||||
```yaml
|
||||
- Code Server:
|
||||
href: http://localhost:8888
|
||||
description: VS Code in the browser
|
||||
icon: code-server
|
||||
widget:
|
||||
type: docker
|
||||
container: code-server-changemaker
|
||||
```
|
||||
|
||||
### Custom Dashboard Layout
|
||||
|
||||
```yaml
|
||||
# settings.yaml
|
||||
layout:
|
||||
style: columns
|
||||
columns: 3
|
||||
|
||||
# Responsive breakpoints
|
||||
responsive:
|
||||
mobile: 1
|
||||
tablet: 2
|
||||
desktop: 3
|
||||
```
|
||||
|
||||
## Official Documentation
|
||||
|
||||
For comprehensive configuration guides and advanced features:
|
||||
|
||||
- [Homepage Documentation](https://gethomepage.dev/)
|
||||
- [GitHub Repository](https://github.com/gethomepage/homepage)
|
||||
- [Configuration Examples](https://gethomepage.dev/configs/)
|
||||
- [Widget Integrations](https://gethomepage.dev/widgets/)
|
||||
@@ -1,118 +0,0 @@
|
||||
# Services
|
||||
Changemaker Lite includes several powerful services that work together to provide a complete documentation and development platform. Each service is containerized and can be accessed through its dedicated port.
|
||||
|
||||
## Available Services
|
||||
|
||||
### [Code Server](code-server.md)
|
||||
**Port: 8888** | Visual Studio Code in your browser for remote development
|
||||
<div class="github-widget" data-repo="coder/code-server"></div>
|
||||
- Full IDE experience
|
||||
- Extensions support
|
||||
- Git integration
|
||||
- Terminal access
|
||||
|
||||
### [Listmonk](listmonk.md)
|
||||
**Port: 9000** | Self-hosted newsletter and mailing list manager
|
||||
<div class="github-widget" data-repo="knadh/listmonk"></div>
|
||||
- Email campaigns
|
||||
- Subscriber management
|
||||
- Analytics
|
||||
- Template system
|
||||
|
||||
### [PostgreSQL](postgresql.md)
|
||||
**Port: 5432** | Reliable database backend
|
||||
- Data persistence for Listmonk
|
||||
- ACID compliance
|
||||
- High performance
|
||||
- Backup and restore capabilities
|
||||
|
||||
### [MkDocs Material](mkdocs.md)
|
||||
**Port: 4000** | Documentation site generator with live preview
|
||||
<div class="github-widget" data-repo="squidfunk/mkdocs-material"></div>
|
||||
- Material Design theme
|
||||
- Live reload
|
||||
- Search functionality
|
||||
- Markdown support
|
||||
|
||||
### [Static Site Server](static-server.md)
|
||||
**Port: 4001** | Nginx-powered static site hosting
|
||||
- High-performance serving
|
||||
- Built documentation hosting
|
||||
- Caching and compression
|
||||
- Security headers
|
||||
|
||||
### [n8n](n8n.md)
|
||||
**Port: 5678** | Workflow automation tool
|
||||
<div class="github-widget" data-repo="n8n-io/n8n"></div>
|
||||
- Visual workflow editor
|
||||
- 400+ integrations
|
||||
- Custom code execution
|
||||
- Webhook support
|
||||
|
||||
### [NocoDB](nocodb.md)
|
||||
**Port: 8090** | No-code database platform
|
||||
<div class="github-widget" data-repo="nocodb/nocodb"></div>
|
||||
- Smart spreadsheet interface
|
||||
- Form builder and API generation
|
||||
- Real-time collaboration
|
||||
- Multi-database support
|
||||
|
||||
### [Homepage](homepage.md)
|
||||
**Port: 3010** | Modern dashboard for all services
|
||||
<div class="github-widget" data-repo="gethomepage/homepage"></div>
|
||||
- Service dashboard and monitoring
|
||||
- Docker integration
|
||||
- Customizable layout
|
||||
- Quick search and bookmarks
|
||||
|
||||
### [Gitea](gitea.md)
|
||||
**Port: 3030** | Self-hosted Git service
|
||||
<div class="github-widget" data-repo="go-gitea/gitea"></div>
|
||||
- Git repository hosting
|
||||
- Web-based interface
|
||||
- Issue tracking
|
||||
- Pull requests
|
||||
- Wiki and code review
|
||||
- Lightweight and easy to deploy
|
||||
|
||||
### [Mini QR](mini-qr.md)
|
||||
**Port: 8089** | Simple QR code generator service
|
||||
<div class="github-widget" data-repo="lyqht/mini-qr"></div>
|
||||
- Generate QR codes for text or URLs
|
||||
- Download QR codes as images
|
||||
- Simple and fast interface
|
||||
- No user registration required
|
||||
|
||||
### [Map](map.md)
|
||||
**Port: 3000** | Canvassing and community organizing application
|
||||
<div class="gitea-widget" data-repo="admin/changemaker.lite"></div>
|
||||
- Interactive map for door-to-door canvassing
|
||||
- Location and contact management
|
||||
- Admin panel and QR code walk sheets
|
||||
- NocoDB integration for data storage
|
||||
- User authentication and access control
|
||||
|
||||
## Service Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Homepage │ │ Code Server │ │ MkDocs │
|
||||
│ :3010 │ │ :8888 │ │ :4000 │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Static Server │ │ Listmonk │ │ n8n │
|
||||
│ :4001 │ │ :9000 │ │ :5678 │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ NocoDB │ │ PostgreSQL │ │ PostgreSQL │
|
||||
│ :8090 │ │ (listmonk-db) │ │ (root_db) │
|
||||
└─────────────────┘ │ :5432 │ │ :5432 │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
|
||||
┌─────────────────┐
|
||||
│ Map │
|
||||
│ :3000 │
|
||||
└─────────────────┘
|
||||
```
|
||||
@@ -1,68 +0,0 @@
|
||||
# Listmonk
|
||||
|
||||
<div class="github-widget" data-repo="knadh/listmonk"></div>
|
||||
|
||||
Self-hosted newsletter and mailing list manager.
|
||||
|
||||
## Overview
|
||||
|
||||
Listmonk is a modern, feature-rich newsletter and mailing list manager designed for high performance and easy management. It provides a complete solution for email campaigns, subscriber management, and analytics.
|
||||
|
||||
## Features
|
||||
|
||||
- Newsletter and email campaign management
|
||||
- Subscriber list management
|
||||
- Template system with HTML/markdown support
|
||||
- Campaign analytics and tracking
|
||||
- API for integration
|
||||
- Multi-list support
|
||||
- Bounce handling
|
||||
- Privacy-focused design
|
||||
|
||||
## Access
|
||||
|
||||
- **Default Port**: 9000
|
||||
- **URL**: `http://localhost:9000`
|
||||
- **Admin User**: Set via `LISTMONK_ADMIN_USER` environment variable
|
||||
- **Admin Password**: Set via `LISTMONK_ADMIN_PASSWORD` environment variable
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `LISTMONK_ADMIN_USER`: Admin username
|
||||
- `LISTMONK_ADMIN_PASSWORD`: Admin password
|
||||
- `POSTGRES_USER`: Database username
|
||||
- `POSTGRES_PASSWORD`: Database password
|
||||
- `POSTGRES_DB`: Database name
|
||||
|
||||
### Database
|
||||
|
||||
Listmonk uses PostgreSQL as its backend database. The database is automatically configured through the docker-compose setup.
|
||||
|
||||
### Uploads
|
||||
|
||||
- Upload directory: `./assets/uploads`
|
||||
- Used for media files, templates, and attachments
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Access Listmonk at `http://localhost:9000`
|
||||
2. Log in with your admin credentials
|
||||
3. Set up your first mailing list
|
||||
4. Configure SMTP settings for sending emails
|
||||
5. Import subscribers or create subscription forms
|
||||
6. Create your first campaign
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Configure SMTP settings before sending emails
|
||||
- Set up proper domain authentication (SPF, DKIM) for better deliverability
|
||||
- Regularly backup your subscriber data and campaigns
|
||||
- Monitor bounce rates and maintain list hygiene
|
||||
|
||||
## Official Documentation
|
||||
|
||||
For comprehensive guides and API documentation, visit:
|
||||
- [Listmonk Documentation](https://listmonk.app/docs/)
|
||||
- [GitHub Repository](https://github.com/knadh/listmonk)
|
||||
@@ -1,96 +0,0 @@
|
||||
# Map
|
||||
|
||||

|
||||
|
||||
Interactive map service for geospatial data visualization, powered by NocoDB and Leaflet.js.
|
||||
|
||||
## Overview
|
||||
|
||||
The Map service provides an interactive web-based map for displaying, searching, and analyzing geospatial data from a NocoDB backend. It supports real-time geolocation, adding new locations, and is optimized for both desktop and mobile use.
|
||||
|
||||
## Features
|
||||
|
||||
- Interactive map visualization with OpenStreetMap
|
||||
- Real-time geolocation support
|
||||
- Add new locations directly from the map
|
||||
- Auto-refresh every 30 seconds
|
||||
- Responsive design for mobile devices
|
||||
- Secure API proxy to protect credentials
|
||||
- Docker containerization for easy deployment
|
||||
|
||||
## Access
|
||||
|
||||
- **Default Port**: `${MAP_PORT:-3000}` (default: 3000)
|
||||
- **URL**: `http://localhost:${MAP_PORT:-3000}`
|
||||
- **Default Workspace**: `/app/public/`
|
||||
|
||||
## Configuration
|
||||
|
||||
All configuration is done via environment variables:
|
||||
|
||||
| Variable | Description | Default |
|
||||
|---------------------|------------------------------------|--------------|
|
||||
| `NOCODB_API_URL` | NocoDB API base URL | Required |
|
||||
| `NOCODB_API_TOKEN` | API authentication token | Required |
|
||||
| `NOCODB_VIEW_URL` | Full NocoDB view URL | Required |
|
||||
| `PORT` | Server port | 3000 |
|
||||
| `DEFAULT_LAT` | Default map latitude | 53.5461 |
|
||||
| `DEFAULT_LNG` | Default map longitude | -113.4938 |
|
||||
| `DEFAULT_ZOOM` | Default map zoom level | 11 |
|
||||
|
||||
### Volumes
|
||||
|
||||
- `./map/app/public`: Map public assets
|
||||
|
||||
## Usage
|
||||
|
||||
1. Access the map at `http://localhost:${MAP_PORT:-3000}`
|
||||
2. Search for locations or addresses
|
||||
3. Add or view custom markers
|
||||
4. Analyze geospatial data as needed
|
||||
|
||||
## NocoDB Table Setup
|
||||
|
||||
### Required Columns
|
||||
|
||||
- `geodata` (Text): Format "latitude;longitude"
|
||||
- `latitude` (Decimal): Precision 10, Scale 8
|
||||
- `longitude` (Decimal): Precision 11, Scale 8
|
||||
|
||||
### Form Fields (as seen in the interface)
|
||||
|
||||
- `First Name` (Text): Person's first name
|
||||
- `Last Name` (Text): Person's last name
|
||||
- `Email` (Email): Contact email address
|
||||
- `Unit Number` (Text): Apartment/unit number
|
||||
- `Support Level` (Single Select):
|
||||
- 1 - Strong Support (Green)
|
||||
- 2 - Moderate Support (Yellow)
|
||||
- 3 - Low Support (Orange)
|
||||
- 4 - No Support (Red)
|
||||
- `Address` (Text): Full street address
|
||||
- `Sign` (Checkbox): Has campaign sign (true/false)
|
||||
- `Sign Size` (Single Select): Small, Medium, Large
|
||||
- `Geo-Location` (Text): Formatted as "latitude;longitude"
|
||||
|
||||
## API Endpoints
|
||||
|
||||
- `GET /api/locations` - Fetch all locations
|
||||
- `POST /api/locations` - Create new location
|
||||
- `GET /api/locations/:id` - Get single location
|
||||
- `PUT /api/locations/:id` - Update location
|
||||
- `DELETE /api/locations/:id` - Delete location
|
||||
- `GET /health` - Health check
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- API tokens are kept server-side only
|
||||
- CORS is configured for security
|
||||
- Rate limiting prevents abuse
|
||||
- Input validation on all endpoints
|
||||
- Helmet.js for security headers
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Ensure NocoDB table has required columns and valid coordinates
|
||||
- Check API token permissions and network connectivity
|
||||
|
Before Width: | Height: | Size: 2.3 MiB |
@@ -1,38 +0,0 @@
|
||||
# Mini QR
|
||||
|
||||
<div class="github-widget" data-repo="lyqht/mini-qr"></div>
|
||||
|
||||
Simple QR code generator service.
|
||||
|
||||
## Overview
|
||||
|
||||
Mini QR is a lightweight service for generating QR codes for URLs, text, or other data. It provides a web interface for quick QR code creation and download.
|
||||
|
||||
## Features
|
||||
|
||||
- Generate QR codes for text or URLs
|
||||
- Download QR codes as images
|
||||
- Simple and fast interface
|
||||
- No user registration required
|
||||
|
||||
## Access
|
||||
|
||||
- **Default Port**: `${MINI_QR_PORT:-8089}` (default: 8089)
|
||||
- **URL**: `http://localhost:${MINI_QR_PORT:-8089}`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `QR_DEFAULT_SIZE`: Default size of generated QR codes
|
||||
- `QR_IMAGE_FORMAT`: Image format (e.g., `png`, `svg`)
|
||||
|
||||
### Volumes
|
||||
|
||||
- `./configs/mini-qr`: QR code service configuration
|
||||
|
||||
## Usage
|
||||
|
||||
1. Access Mini QR at `http://localhost:${MINI_QR_PORT:-8089}`
|
||||
2. Enter the text or URL to encode
|
||||
3. Download or share the generated QR code
|
||||
@@ -1,132 +0,0 @@
|
||||
# MkDocs Material
|
||||
|
||||
|
||||
|
||||
<div class="github-widget" data-repo="squidfunk/mkdocs-material"></div>
|
||||
|
||||
Modern documentation site generator with live preview.
|
||||
|
||||
Looking for more info on BNKops code-server integration?
|
||||
|
||||
[→ Code Server Configuration](../config/coder.md)
|
||||
|
||||
## Overview
|
||||
|
||||
MkDocs Material is a powerful documentation framework built on top of MkDocs, providing a beautiful Material Design theme and advanced features for creating professional documentation sites.
|
||||
|
||||
## Features
|
||||
|
||||
- Material Design theme
|
||||
- Live preview during development
|
||||
- Search functionality
|
||||
- Navigation and organization
|
||||
- Code syntax highlighting
|
||||
- Mathematical expressions support
|
||||
- Responsive design
|
||||
- Customizable themes and colors
|
||||
|
||||
## Access
|
||||
|
||||
- **Development Port**: 4000
|
||||
- **Development URL**: `http://localhost:4000`
|
||||
- **Live Reload**: Automatically refreshes on file changes
|
||||
|
||||
## Configuration
|
||||
|
||||
### Main Configuration
|
||||
|
||||
Configuration is managed through `mkdocs.yml` in the project root.
|
||||
|
||||
### Volumes
|
||||
|
||||
- `./mkdocs`: Documentation source files
|
||||
- `./assets/images`: Shared images directory
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `SITE_URL`: Base domain for the site
|
||||
- `USER_ID`: User ID for file permissions
|
||||
- `GROUP_ID`: Group ID for file permissions
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
mkdocs/
|
||||
├── mkdocs.yml # Configuration file
|
||||
├── docs/ # Documentation source
|
||||
│ ├── index.md # Homepage
|
||||
│ ├── services/ # Service documentation
|
||||
│ ├── blog/ # Blog posts
|
||||
│ └── overrides/ # Template overrides
|
||||
└── site/ # Built static site
|
||||
```
|
||||
|
||||
## Writing Documentation
|
||||
|
||||
### Markdown Basics
|
||||
|
||||
- Use standard Markdown syntax
|
||||
- Support for tables, code blocks, and links
|
||||
- Mathematical expressions with MathJax
|
||||
- Admonitions for notes and warnings
|
||||
|
||||
### Example Page
|
||||
|
||||
```markdown
|
||||
# Page Title
|
||||
|
||||
This is a sample documentation page.
|
||||
|
||||
## Section
|
||||
|
||||
Content goes here with **bold** and *italic* text.
|
||||
|
||||
### Code Example
|
||||
|
||||
```python
|
||||
def hello_world():
|
||||
print("Hello, World!")
|
||||
```
|
||||
|
||||
!!! note
|
||||
This is an informational note.
|
||||
```
|
||||
|
||||
## Building and Deployment
|
||||
|
||||
### Development
|
||||
|
||||
The development server runs automatically with live reload.
|
||||
|
||||
### Building Static Site
|
||||
|
||||
```bash
|
||||
docker exec mkdocs-changemaker mkdocs build
|
||||
```
|
||||
|
||||
The built site will be available in the `mkdocs/site/` directory.
|
||||
|
||||
## Customization
|
||||
|
||||
### Themes and Colors
|
||||
|
||||
Customize appearance in `mkdocs.yml`:
|
||||
|
||||
```yaml
|
||||
theme:
|
||||
name: material
|
||||
palette:
|
||||
primary: blue
|
||||
accent: indigo
|
||||
```
|
||||
|
||||
### Custom CSS
|
||||
|
||||
Add custom styles in `docs/stylesheets/extra.css`.
|
||||
|
||||
## Official Documentation
|
||||
|
||||
For comprehensive MkDocs Material documentation:
|
||||
- [MkDocs Material](https://squidfunk.github.io/mkdocs-material/)
|
||||
- [MkDocs Documentation](https://www.mkdocs.org/)
|
||||
- [Markdown Guide](https://www.markdownguide.org/)
|
||||
@@ -1,157 +0,0 @@
|
||||
# n8n
|
||||
|
||||
<div class="github-widget" data-repo="n8n-io/n8n"></div>
|
||||
|
||||
Workflow automation tool for connecting services and automating tasks.
|
||||
|
||||
## Overview
|
||||
|
||||
n8n is a powerful workflow automation tool that allows you to connect various apps and services together. It provides a visual interface for creating automated workflows, making it easy to integrate different systems and automate repetitive tasks.
|
||||
|
||||
## Features
|
||||
|
||||
- Visual workflow editor
|
||||
- 400+ integrations
|
||||
- Custom code execution (JavaScript/Python)
|
||||
- Webhook support
|
||||
- Scheduled workflows
|
||||
- Error handling and retries
|
||||
- User management
|
||||
- API access
|
||||
- Self-hosted and privacy-focused
|
||||
|
||||
## Access
|
||||
|
||||
- **Default Port**: 5678
|
||||
- **URL**: `http://localhost:5678`
|
||||
- **Default User Email**: Set via `N8N_DEFAULT_USER_EMAIL`
|
||||
- **Default User Password**: Set via `N8N_DEFAULT_USER_PASSWORD`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `N8N_HOST`: Hostname for n8n (default: `n8n.${DOMAIN}`)
|
||||
- `N8N_PORT`: Internal port (5678)
|
||||
- `N8N_PROTOCOL`: Protocol for webhooks (https)
|
||||
- `NODE_ENV`: Environment (production)
|
||||
- `WEBHOOK_URL`: Base URL for webhooks
|
||||
- `GENERIC_TIMEZONE`: Timezone setting
|
||||
- `N8N_ENCRYPTION_KEY`: Encryption key for credentials
|
||||
- `N8N_USER_MANAGEMENT_DISABLED`: Enable/disable user management
|
||||
- `N8N_DEFAULT_USER_EMAIL`: Default admin email
|
||||
- `N8N_DEFAULT_USER_PASSWORD`: Default admin password
|
||||
|
||||
### Volumes
|
||||
|
||||
- `n8n_data`: Persistent data storage
|
||||
- `./local-files`: Local file access for workflows
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Access n8n at `http://localhost:5678`
|
||||
2. Log in with your admin credentials
|
||||
3. Create your first workflow
|
||||
4. Add nodes for different services
|
||||
5. Configure connections between nodes
|
||||
6. Test and activate your workflow
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Documentation Automation
|
||||
|
||||
- Auto-generate documentation from code comments
|
||||
- Sync documentation between different platforms
|
||||
- Notify team when documentation is updated
|
||||
|
||||
### Email Campaign Integration
|
||||
|
||||
- Connect Listmonk with external data sources
|
||||
- Automate subscriber management
|
||||
- Trigger campaigns based on events
|
||||
|
||||
### Database Management with NocoDB
|
||||
|
||||
- Sync data between NocoDB and external APIs
|
||||
- Automate data entry and validation
|
||||
- Create backup workflows for database content
|
||||
- Generate reports from NocoDB data
|
||||
|
||||
### Development Workflows
|
||||
|
||||
- Auto-deploy documentation on git push
|
||||
- Sync code changes with documentation
|
||||
- Backup automation
|
||||
|
||||
### Data Processing
|
||||
|
||||
- Process CSV files and import to databases
|
||||
- Transform data between different formats
|
||||
- Schedule regular data updates
|
||||
|
||||
## Example Workflows
|
||||
|
||||
### Simple Webhook to Email
|
||||
|
||||
```
|
||||
Webhook → Email
|
||||
```
|
||||
|
||||
### Scheduled Documentation Backup
|
||||
|
||||
```
|
||||
Schedule → Read Files → Compress → Upload to Storage
|
||||
```
|
||||
|
||||
### Git Integration
|
||||
|
||||
```
|
||||
Git Webhook → Process Changes → Update Documentation → Notify Team
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Use strong encryption keys
|
||||
- Secure webhook URLs
|
||||
- Regularly update credentials
|
||||
- Monitor workflow executions
|
||||
- Implement proper error handling
|
||||
|
||||
## Integration with Other Services
|
||||
|
||||
n8n can integrate with all services in your Changemaker Lite setup:
|
||||
|
||||
- **Listmonk**: Manage subscribers and campaigns
|
||||
- **PostgreSQL**: Read/write database operations
|
||||
- **Code Server**: File operations and git integration
|
||||
- **MkDocs**: Documentation generation and updates
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
- **Workflow Execution Errors**: Check node configurations and credentials
|
||||
- **Webhook Issues**: Verify URLs and authentication
|
||||
- **Connection Problems**: Check network connectivity between services
|
||||
|
||||
### Debugging
|
||||
|
||||
```bash
|
||||
# Check container logs
|
||||
docker logs n8n-changemaker
|
||||
|
||||
# Access container shell
|
||||
docker exec -it n8n-changemaker sh
|
||||
|
||||
# Check workflow executions in the UI
|
||||
# Visit http://localhost:5678 → Executions
|
||||
```
|
||||
|
||||
## Official Documentation
|
||||
|
||||
For comprehensive n8n documentation:
|
||||
|
||||
- [n8n Documentation](https://docs.n8n.io/)
|
||||
- [Community Workflows](https://n8n.io/workflows/)
|
||||
- [Node Reference](https://docs.n8n.io/integrations/builtin/)
|
||||
- [GitHub Repository](https://github.com/n8n-io/n8n)
|
||||
@@ -1,163 +0,0 @@
|
||||
# NocoDB
|
||||
|
||||
<div class="github-widget" data-repo="nocodb/nocodb"></div>
|
||||
|
||||
No-code database platform that turns any database into a smart spreadsheet.
|
||||
|
||||
## Overview
|
||||
|
||||
NocoDB is an open-source no-code platform that transforms any database into a smart spreadsheet interface. It provides a user-friendly way to manage data, create forms, build APIs, and collaborate on database operations without requiring extensive technical knowledge.
|
||||
|
||||
## Features
|
||||
|
||||
- **Smart Spreadsheet Interface**: Transform databases into intuitive spreadsheets
|
||||
- **Form Builder**: Create custom forms for data entry
|
||||
- **API Generation**: Auto-generated REST APIs for all tables
|
||||
- **Collaboration**: Real-time collaboration with team members
|
||||
- **Access Control**: Role-based permissions and sharing
|
||||
- **Data Visualization**: Charts and dashboard creation
|
||||
- **Webhooks**: Integration with external services
|
||||
- **Import/Export**: Support for CSV, Excel, and other formats
|
||||
- **Multi-Database Support**: Works with PostgreSQL, MySQL, SQLite, and more
|
||||
|
||||
## Access
|
||||
|
||||
- **Default Port**: 8090
|
||||
- **URL**: `http://localhost:8090`
|
||||
- **Database**: PostgreSQL (dedicated `root_db` instance)
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `NOCODB_PORT`: External port mapping (default: 8090)
|
||||
- `NC_DB`: Database connection string for PostgreSQL backend
|
||||
|
||||
### Database Backend
|
||||
|
||||
NocoDB uses a dedicated PostgreSQL instance (`root_db`) with the following configuration:
|
||||
|
||||
- **Database Name**: `root_db`
|
||||
- **Username**: `postgres`
|
||||
- **Password**: `password`
|
||||
- **Host**: `root_db` (internal container name)
|
||||
|
||||
### Volumes
|
||||
|
||||
- `nc_data`: Application data and configuration storage
|
||||
- `db_data`: PostgreSQL database files
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. **Access NocoDB**: Navigate to `http://localhost:8090`
|
||||
2. **Initial Setup**: Complete the onboarding process
|
||||
3. **Create Project**: Start with a new project or connect existing databases
|
||||
4. **Add Tables**: Import data or create new tables
|
||||
5. **Configure Views**: Set up different views (Grid, Form, Gallery, etc.)
|
||||
6. **Set Permissions**: Configure user access and sharing settings
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Content Management
|
||||
|
||||
- Create content databases for blogs and websites
|
||||
- Manage product catalogs and inventories
|
||||
- Track customer information and interactions
|
||||
|
||||
### Project Management
|
||||
|
||||
- Task and project tracking systems
|
||||
- Team collaboration workspaces
|
||||
- Resource and timeline management
|
||||
|
||||
### Data Collection
|
||||
|
||||
- Custom forms for surveys and feedback
|
||||
- Event registration and management
|
||||
- Lead capture and CRM systems
|
||||
|
||||
### Integration with Other Services
|
||||
|
||||
NocoDB can integrate well with other Changemaker Lite services:
|
||||
|
||||
- **n8n Integration**: Use NocoDB as a data source/destination in automation workflows
|
||||
- **Listmonk Integration**: Manage subscriber lists and campaign data
|
||||
- **Documentation**: Store and manage documentation metadata
|
||||
|
||||
## API Usage
|
||||
|
||||
NocoDB automatically generates REST APIs for all your tables:
|
||||
|
||||
```bash
|
||||
# Get all records from a table
|
||||
GET http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}
|
||||
|
||||
# Create a new record
|
||||
POST http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}
|
||||
|
||||
# Update a record
|
||||
PATCH http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}/{id}
|
||||
```
|
||||
|
||||
## Backup and Data Management
|
||||
|
||||
### Database Backup
|
||||
|
||||
Since NocoDB uses PostgreSQL, you can backup the database:
|
||||
|
||||
```bash
|
||||
# Backup NocoDB database
|
||||
docker exec root_db pg_dump -U postgres root_db > nocodb_backup.sql
|
||||
|
||||
# Restore from backup
|
||||
docker exec -i root_db psql -U postgres root_db < nocodb_backup.sql
|
||||
```
|
||||
|
||||
### Application Data
|
||||
|
||||
Application settings and metadata are stored in the `nc_data` volume.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Change default database credentials in production
|
||||
- Configure proper access controls within NocoDB
|
||||
- Use HTTPS for production deployments
|
||||
- Regularly backup both database and application data
|
||||
- Monitor access logs and user activities
|
||||
|
||||
## Performance Tips
|
||||
|
||||
- Regular database maintenance and optimization
|
||||
- Monitor memory usage for large datasets
|
||||
- Use appropriate indexing for frequently queried fields
|
||||
- Consider database connection pooling for high-traffic scenarios
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Service won't start**: Check if the PostgreSQL database is healthy
|
||||
|
||||
```bash
|
||||
docker logs root_db
|
||||
```
|
||||
|
||||
**Database connection errors**: Verify database credentials and network connectivity
|
||||
|
||||
```bash
|
||||
docker exec nocodb nc_data nc
|
||||
```
|
||||
|
||||
**Performance issues**: Monitor resource usage and optimize queries
|
||||
|
||||
```bash
|
||||
docker stats nocodb root_db
|
||||
```
|
||||
|
||||
## Official Documentation
|
||||
|
||||
For comprehensive guides and advanced features:
|
||||
|
||||
- [NocoDB Documentation](https://docs.nocodb.com/)
|
||||
- [GitHub Repository](https://github.com/nocodb/nocodb)
|
||||
- [Community Forum](https://community.nocodb.com/)
|
||||
@@ -1,90 +0,0 @@
|
||||
# PostgreSQL Database
|
||||
|
||||
Reliable database backend for applications.
|
||||
|
||||
## Overview
|
||||
|
||||
PostgreSQL is a powerful, open-source relational database system. In Changemaker Lite, it serves as the backend database for Listmonk and can be used by other applications requiring persistent data storage.
|
||||
|
||||
## Features
|
||||
|
||||
- ACID compliance
|
||||
- Advanced SQL features
|
||||
- JSON/JSONB support
|
||||
- Full-text search
|
||||
- Extensibility
|
||||
- High performance
|
||||
- Reliability and data integrity
|
||||
|
||||
## Access
|
||||
|
||||
- **Default Port**: 5432
|
||||
- **Host**: `listmonk-db` (internal container name)
|
||||
- **Database**: Set via `POSTGRES_DB` environment variable
|
||||
- **Username**: Set via `POSTGRES_USER` environment variable
|
||||
- **Password**: Set via `POSTGRES_PASSWORD` environment variable
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `POSTGRES_USER`: Database username
|
||||
- `POSTGRES_PASSWORD`: Database password
|
||||
- `POSTGRES_DB`: Database name
|
||||
|
||||
### Health Checks
|
||||
|
||||
The PostgreSQL container includes health checks to ensure the database is ready before dependent services start.
|
||||
|
||||
### Data Persistence
|
||||
|
||||
Database data is stored in a Docker volume (`listmonk-data`) to ensure persistence across container restarts.
|
||||
|
||||
## Connecting to the Database
|
||||
|
||||
### From Host Machine
|
||||
|
||||
You can connect to PostgreSQL from your host machine using:
|
||||
|
||||
```bash
|
||||
psql -h localhost -p 5432 -U [username] -d [database]
|
||||
```
|
||||
|
||||
### From Other Containers
|
||||
|
||||
Other containers can connect using the internal hostname `listmonk-db` on port 5432.
|
||||
|
||||
## Backup and Restore
|
||||
|
||||
### Backup
|
||||
|
||||
```bash
|
||||
docker exec listmonk-db pg_dump -U [username] [database] > backup.sql
|
||||
```
|
||||
|
||||
### Restore
|
||||
|
||||
```bash
|
||||
docker exec -i listmonk-db psql -U [username] [database] < backup.sql
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
Monitor database health and performance through:
|
||||
- Container logs: `docker logs listmonk-db`
|
||||
- Database metrics and queries
|
||||
- Connection monitoring
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Use strong passwords
|
||||
- Regularly update PostgreSQL version
|
||||
- Monitor access logs
|
||||
- Implement regular backups
|
||||
- Consider network isolation
|
||||
|
||||
## Official Documentation
|
||||
|
||||
For comprehensive PostgreSQL documentation:
|
||||
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
|
||||
- [Docker PostgreSQL Image](https://hub.docker.com/_/postgres)
|
||||
@@ -1,100 +0,0 @@
|
||||
# Static Site Server
|
||||
|
||||
Nginx-powered static site server for hosting built documentation and websites.
|
||||
|
||||
## Overview
|
||||
|
||||
The Static Site Server uses Nginx to serve your built documentation and static websites. It's configured to serve the built MkDocs site and other static content with high performance and reliability.
|
||||
|
||||
## Features
|
||||
|
||||
- High-performance static file serving
|
||||
- Automatic index file handling
|
||||
- Gzip compression
|
||||
- Caching headers
|
||||
- Security headers
|
||||
- Custom error pages
|
||||
- URL rewriting support
|
||||
|
||||
## Access
|
||||
|
||||
- **Default Port**: 4001
|
||||
- **URL**: `http://localhost:4001`
|
||||
- **Document Root**: `/config/www` (mounted from `./mkdocs/site`)
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `PUID`: User ID for file permissions (default: 1000)
|
||||
- `PGID`: Group ID for file permissions (default: 1000)
|
||||
- `TZ`: Timezone setting (default: Etc/UTC)
|
||||
|
||||
### Volumes
|
||||
|
||||
- `./mkdocs/site:/config/www`: Static site files
|
||||
- Built MkDocs site is automatically served
|
||||
|
||||
## Usage
|
||||
|
||||
1. Build your MkDocs site: `docker exec mkdocs-changemaker mkdocs build`
|
||||
2. The built site is automatically available at `http://localhost:4001`
|
||||
3. Any files in `./mkdocs/site/` will be served statically
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
mkdocs/site/ # Served at /
|
||||
├── index.html # Homepage
|
||||
├── assets/ # CSS, JS, images
|
||||
├── services/ # Service documentation
|
||||
└── search/ # Search functionality
|
||||
```
|
||||
|
||||
## Performance Features
|
||||
|
||||
- **Gzip Compression**: Automatic compression for text files
|
||||
- **Browser Caching**: Optimized cache headers
|
||||
- **Fast Static Serving**: Nginx optimized for static content
|
||||
- **Security Headers**: Basic security header configuration
|
||||
|
||||
## Custom Configuration
|
||||
|
||||
For advanced Nginx configuration, you can:
|
||||
1. Create custom Nginx config files
|
||||
2. Mount them as volumes
|
||||
3. Restart the container
|
||||
|
||||
## Monitoring
|
||||
|
||||
Monitor the static site server through:
|
||||
- Container logs: `docker logs mkdocs-site-server-changemaker`
|
||||
- Access logs for traffic analysis
|
||||
- Performance metrics
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
- **404 Errors**: Ensure MkDocs site is built and files exist in `./mkdocs/site/`
|
||||
- **Permission Issues**: Check `PUID` and `PGID` settings
|
||||
- **File Not Found**: Verify file paths and case sensitivity
|
||||
|
||||
### Debugging
|
||||
|
||||
```bash
|
||||
# Check container logs
|
||||
docker logs mkdocs-site-server-changemaker
|
||||
|
||||
# Verify files are present
|
||||
docker exec mkdocs-site-server-changemaker ls -la /config/www
|
||||
|
||||
# Test file serving
|
||||
curl -I http://localhost:4001
|
||||
```
|
||||
|
||||
## Official Documentation
|
||||
|
||||
For more information about the underlying Nginx server:
|
||||
- [LinuxServer.io Nginx](https://docs.linuxserver.io/images/docker-nginx)
|
||||
- [Nginx Documentation](https://nginx.org/en/docs/)
|
||||
@@ -1,383 +0,0 @@
|
||||
# API Reference
|
||||
|
||||
Complete REST API reference for Changemaker Lite V2. This section documents all API endpoints, request/response formats, authentication, and error handling.
|
||||
|
||||
## Overview
|
||||
|
||||
Changemaker Lite V2 provides two REST APIs:
|
||||
|
||||
- **Express API** (Port 4000) - Main application API
|
||||
- **Fastify Media API** (Port 4100) - Media library operations
|
||||
|
||||
Both APIs use JSON for request/response bodies and follow RESTful conventions.
|
||||
|
||||
## API Documentation
|
||||
|
||||
API reference documentation will be added as the API stabilizes. Planned documentation includes:
|
||||
|
||||
### Authentication Endpoints
|
||||
|
||||
- `POST /api/auth/register` - User registration
|
||||
- `POST /api/auth/login` - User login
|
||||
- `POST /api/auth/refresh` - Refresh access token
|
||||
- `POST /api/auth/logout` - User logout
|
||||
- `GET /api/auth/me` - Get current user
|
||||
|
||||
### User Endpoints
|
||||
|
||||
- `GET /api/users` - List users
|
||||
- `POST /api/users` - Create user
|
||||
- `GET /api/users/:id` - Get user
|
||||
- `PATCH /api/users/:id` - Update user
|
||||
- `DELETE /api/users/:id` - Delete user
|
||||
|
||||
### Campaign Endpoints
|
||||
|
||||
- `GET /api/campaigns` - List campaigns
|
||||
- `POST /api/campaigns` - Create campaign
|
||||
- `GET /api/campaigns/:id` - Get campaign
|
||||
- `PATCH /api/campaigns/:id` - Update campaign
|
||||
- `DELETE /api/campaigns/:id` - Delete campaign
|
||||
- `GET /api/campaigns/public` - List public campaigns
|
||||
- `POST /api/campaigns/:id/send-email` - Send campaign email
|
||||
|
||||
### Location Endpoints
|
||||
|
||||
- `GET /api/locations` - List locations
|
||||
- `POST /api/locations` - Create location
|
||||
- `GET /api/locations/:id` - Get location
|
||||
- `PATCH /api/locations/:id` - Update location
|
||||
- `DELETE /api/locations/:id` - Delete location
|
||||
- `POST /api/locations/import` - CSV import
|
||||
- `GET /api/locations/export` - CSV export
|
||||
- `POST /api/locations/geocode` - Bulk geocode
|
||||
|
||||
### Map Endpoints
|
||||
|
||||
- `GET /api/cuts` - List cuts
|
||||
- `POST /api/cuts` - Create cut
|
||||
- `GET /api/shifts` - List shifts
|
||||
- `POST /api/shifts` - Create shift
|
||||
- `GET /api/canvass/session` - Get active session
|
||||
- `POST /api/canvass/session/start` - Start session
|
||||
- `POST /api/canvass/visit` - Record visit
|
||||
|
||||
### Content Endpoints
|
||||
|
||||
- `GET /api/pages` - List pages
|
||||
- `POST /api/pages` - Create page
|
||||
- `GET /api/pages/public/:slug` - Get published page
|
||||
- `GET /api/email-templates` - List templates
|
||||
- `POST /api/email-templates` - Create template
|
||||
|
||||
### Media Endpoints (Port 4100)
|
||||
|
||||
- `GET /media-api/videos` - List videos
|
||||
- `POST /media-api/upload` - Upload video
|
||||
- `GET /media-api/public/videos` - List public videos
|
||||
- `POST /media-api/reactions` - Add reaction
|
||||
|
||||
## Authentication
|
||||
|
||||
All authenticated endpoints require a valid JWT access token in the Authorization header:
|
||||
|
||||
```
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
### Token Lifecycle
|
||||
|
||||
1. **Login** - POST `/api/auth/login`
|
||||
- Returns: `accessToken` (15min) + `refreshToken` (7 days)
|
||||
|
||||
2. **Access Protected Resource** - Include token in header
|
||||
- Token verified by `authenticate` middleware
|
||||
|
||||
3. **Refresh Token** - POST `/api/auth/refresh`
|
||||
- Provide: `refreshToken`
|
||||
- Returns: New `accessToken` + `refreshToken`
|
||||
|
||||
4. **Logout** - POST `/api/auth/logout`
|
||||
- Invalidates refresh token
|
||||
|
||||
### Role-Based Access
|
||||
|
||||
Endpoints are protected by role requirements:
|
||||
|
||||
- **Public** - No authentication required
|
||||
- **Authenticated** - Any logged-in user
|
||||
- **Admin** - SUPER_ADMIN, INFLUENCE_ADMIN, or MAP_ADMIN
|
||||
- **Role-Specific** - Specific role required
|
||||
|
||||
## Request Format
|
||||
|
||||
### JSON Body
|
||||
|
||||
```json
|
||||
POST /api/campaigns
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <token>
|
||||
|
||||
{
|
||||
"name": "Save the Parks",
|
||||
"description": "Campaign description",
|
||||
"published": true
|
||||
}
|
||||
```
|
||||
|
||||
### Query Parameters
|
||||
|
||||
```
|
||||
GET /api/campaigns?page=1&limit=20&search=parks
|
||||
```
|
||||
|
||||
### Path Parameters
|
||||
|
||||
```
|
||||
GET /api/campaigns/:id
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
### Success Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Save the Parks",
|
||||
"description": "Campaign description",
|
||||
"published": true,
|
||||
"createdAt": "2026-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2026-01-01T00:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Paginated Response
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [...],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"total": 100,
|
||||
"totalPages": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Validation error",
|
||||
"details": "Invalid email format",
|
||||
"statusCode": 400
|
||||
}
|
||||
```
|
||||
|
||||
## Status Codes
|
||||
|
||||
- **200 OK** - Success
|
||||
- **201 Created** - Resource created
|
||||
- **204 No Content** - Success with no body
|
||||
- **400 Bad Request** - Validation error
|
||||
- **401 Unauthorized** - Authentication required
|
||||
- **403 Forbidden** - Insufficient permissions
|
||||
- **404 Not Found** - Resource not found
|
||||
- **429 Too Many Requests** - Rate limit exceeded
|
||||
- **500 Internal Server Error** - Server error
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
Rate limits vary by endpoint:
|
||||
|
||||
- **Auth endpoints** - 10 requests/minute per IP
|
||||
- **Canvass visits** - 30 requests/minute per IP
|
||||
- **Public endpoints** - 60 requests/minute per IP
|
||||
- **Authenticated endpoints** - 120 requests/minute per user
|
||||
|
||||
Rate limit headers:
|
||||
|
||||
```
|
||||
X-RateLimit-Limit: 60
|
||||
X-RateLimit-Remaining: 59
|
||||
X-RateLimit-Reset: 1640995200
|
||||
```
|
||||
|
||||
## CORS
|
||||
|
||||
CORS is enabled for all origins in development:
|
||||
|
||||
```javascript
|
||||
app.use(cors({
|
||||
origin: '*',
|
||||
credentials: true,
|
||||
}));
|
||||
```
|
||||
|
||||
Production should restrict to known domains.
|
||||
|
||||
## Validation
|
||||
|
||||
Request bodies are validated using Zod schemas. Validation errors return 400 with details:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Validation error",
|
||||
"details": {
|
||||
"email": "Invalid email format",
|
||||
"password": "Password must be at least 12 characters"
|
||||
},
|
||||
"statusCode": 400
|
||||
}
|
||||
```
|
||||
|
||||
## Pagination
|
||||
|
||||
List endpoints support pagination:
|
||||
|
||||
- **page** - Page number (default: 1)
|
||||
- **limit** - Items per page (default: 20, max: 100)
|
||||
|
||||
Example:
|
||||
```
|
||||
GET /api/campaigns?page=2&limit=50
|
||||
```
|
||||
|
||||
## Search & Filtering
|
||||
|
||||
List endpoints support search and filtering:
|
||||
|
||||
- **search** - Text search (varies by endpoint)
|
||||
- **filter** - Field-specific filters
|
||||
|
||||
Example:
|
||||
```
|
||||
GET /api/campaigns?search=parks&published=true
|
||||
```
|
||||
|
||||
## Sorting
|
||||
|
||||
List endpoints support sorting:
|
||||
|
||||
- **sort** - Field to sort by
|
||||
- **order** - Sort direction (asc/desc)
|
||||
|
||||
Example:
|
||||
```
|
||||
GET /api/campaigns?sort=createdAt&order=desc
|
||||
```
|
||||
|
||||
## API Endpoints by Module
|
||||
|
||||
### Authentication
|
||||
- Login, register, refresh, logout, current user
|
||||
|
||||
### Users
|
||||
- CRUD operations, pagination, search, role management
|
||||
|
||||
### Settings
|
||||
- Site settings singleton
|
||||
|
||||
### Campaigns
|
||||
- CRUD, public listing, email sending
|
||||
|
||||
### Representatives
|
||||
- Postal code lookup, cache management
|
||||
|
||||
### Responses
|
||||
- CRUD, verification, upvoting, moderation
|
||||
|
||||
### Postal Codes
|
||||
- Cache service
|
||||
|
||||
### Campaign Emails
|
||||
- Email tracking, statistics
|
||||
|
||||
### Email Queue
|
||||
- Queue monitoring, pause/resume, cleanup
|
||||
|
||||
### Locations
|
||||
- CRUD, CSV import/export, geocoding, NAR import
|
||||
|
||||
### Cuts
|
||||
- CRUD, spatial queries, location assignment
|
||||
|
||||
### Shifts
|
||||
- CRUD, signups, email notifications
|
||||
|
||||
### Canvass
|
||||
- Sessions, visits, routes, dashboard
|
||||
|
||||
### Tracking
|
||||
- GPS tracking (future)
|
||||
|
||||
### Map Settings
|
||||
- Map configuration
|
||||
|
||||
### Pages
|
||||
- CRUD, block library, MkDocs export, public rendering
|
||||
|
||||
### Email Templates
|
||||
- CRUD, versioning (future)
|
||||
|
||||
### Media (Port 4100)
|
||||
- Videos, upload, shared media, reactions, jobs
|
||||
|
||||
### Listmonk
|
||||
- Status, sync, test connection
|
||||
|
||||
### Pangolin
|
||||
- Tunnel management, setup, configuration
|
||||
|
||||
### Docs
|
||||
- MkDocs/Code Server status
|
||||
|
||||
### QR
|
||||
- QR code generation
|
||||
|
||||
### Observability
|
||||
- Prometheus/Grafana/Alertmanager integration
|
||||
|
||||
### Services
|
||||
- Health checks
|
||||
|
||||
## OpenAPI Specification
|
||||
|
||||
OpenAPI/Swagger documentation is planned for future releases. This will provide:
|
||||
|
||||
- Interactive API explorer
|
||||
- Auto-generated client libraries
|
||||
- Comprehensive endpoint documentation
|
||||
- Request/response examples
|
||||
|
||||
## Testing
|
||||
|
||||
Test API endpoints using:
|
||||
|
||||
- **curl** - Command-line HTTP client
|
||||
- **Postman** - GUI API client
|
||||
- **HTTPie** - User-friendly CLI
|
||||
- **Insomnia** - API design/testing tool
|
||||
|
||||
Example with curl:
|
||||
|
||||
```bash
|
||||
# Login
|
||||
curl -X POST http://localhost:4000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"admin@example.com","password":"Admin123!"}'
|
||||
|
||||
# Get campaigns (with token)
|
||||
curl http://localhost:4000/api/campaigns \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Backend Modules](../backend/modules/index.md)
|
||||
- [Authentication](../backend/modules/auth.md)
|
||||
- [Middleware](../backend/middleware/index.md)
|
||||
- [Development Guide](../development/index.md)
|
||||
- [Troubleshooting](../troubleshooting/index.md)
|
||||
@@ -1,800 +0,0 @@
|
||||
# Authentication Flow
|
||||
|
||||
Changemaker Lite V2 uses JWT-based authentication with access and refresh tokens for stateless, scalable authentication.
|
||||
|
||||
## Overview
|
||||
|
||||
**Key Features:**
|
||||
|
||||
- **JWT Tokens** - Stateless authentication (no session storage)
|
||||
- **Dual Token System** - Short-lived access tokens (15min) + long-lived refresh tokens (7 days)
|
||||
- **Refresh Token Rotation** - Atomic transaction prevents race conditions
|
||||
- **Password Policy** - Enforced 12+ characters with complexity requirements
|
||||
- **Rate Limiting** - 10 requests/min on auth endpoints
|
||||
- **User Enumeration Prevention** - Consistent 401 responses
|
||||
- **RBAC** - Role-based access control with 5 roles
|
||||
|
||||
## Authentication Architecture
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Client Layer"
|
||||
Browser[Web Browser]
|
||||
Storage[LocalStorage<br/>Zustand Persist]
|
||||
end
|
||||
|
||||
subgraph "API Layer"
|
||||
AuthRoutes[Auth Routes<br/>/api/auth/*]
|
||||
AuthMiddleware[Auth Middleware<br/>JWT Verification]
|
||||
RBACMiddleware[RBAC Middleware<br/>Role Check]
|
||||
end
|
||||
|
||||
subgraph "Data Layer"
|
||||
PG[(PostgreSQL<br/>User + RefreshToken)]
|
||||
Redis[(Redis<br/>Rate Limiting)]
|
||||
end
|
||||
|
||||
Browser -->|POST /auth/login| AuthRoutes
|
||||
AuthRoutes -->|Check rate limit| Redis
|
||||
AuthRoutes -->|Verify credentials| PG
|
||||
AuthRoutes -->|Generate tokens| AuthRoutes
|
||||
AuthRoutes -->|Store refresh token| PG
|
||||
AuthRoutes -->|Return tokens| Browser
|
||||
Browser -->|Store| Storage
|
||||
|
||||
Browser -->|API requests| AuthMiddleware
|
||||
AuthMiddleware -->|Verify JWT| AuthMiddleware
|
||||
AuthMiddleware -->|Check role| RBACMiddleware
|
||||
RBACMiddleware -->|Authorized| Handler[Route Handler]
|
||||
|
||||
style AuthRoutes fill:#61dafb,stroke:#333,stroke-width:2px
|
||||
style AuthMiddleware fill:#ffd700,stroke:#333,stroke-width:2px
|
||||
style RBACMiddleware fill:#ff6b6b,stroke:#333,stroke-width:2px
|
||||
```
|
||||
|
||||
## User Roles
|
||||
|
||||
### Role Hierarchy
|
||||
|
||||
```typescript
|
||||
enum UserRole {
|
||||
SUPER_ADMIN = 'SUPER_ADMIN', // Full system access
|
||||
INFLUENCE_ADMIN = 'INFLUENCE_ADMIN', // Campaign management
|
||||
MAP_ADMIN = 'MAP_ADMIN', // Location + canvassing management
|
||||
USER = 'USER', // Standard user (limited access)
|
||||
TEMP = 'TEMP' // Temporary user (public signups, auto-expires)
|
||||
}
|
||||
```
|
||||
|
||||
### Role Permissions
|
||||
|
||||
| Role | Campaign CRUD | Response Moderation | Location Management | User Management | System Settings |
|
||||
|------|---------------|---------------------|---------------------|-----------------|-----------------|
|
||||
| **SUPER_ADMIN** | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **INFLUENCE_ADMIN** | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| **MAP_ADMIN** | ❌ | ❌ | ✅ | ❌ | ❌ |
|
||||
| **USER** | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **TEMP** | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
|
||||
**TEMP User Behavior:**
|
||||
- Created automatically for public shift signups
|
||||
- Auto-expires after configured days (`expiresAt`, `expireDays` fields)
|
||||
- Limited to volunteer canvassing features
|
||||
- Cannot access admin pages
|
||||
|
||||
## Login Flow
|
||||
|
||||
### Sequence Diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant React as Admin GUI
|
||||
participant Nginx
|
||||
participant API as Express API
|
||||
participant Redis
|
||||
participant PG as PostgreSQL
|
||||
|
||||
User->>React: Enter email + password
|
||||
React->>Nginx: POST /api/auth/login
|
||||
Nginx->>API: Forward request
|
||||
|
||||
API->>Redis: Rate limit check (10/min)
|
||||
alt Rate limit exceeded
|
||||
Redis-->>API: Too many requests
|
||||
API-->>React: 429 Too Many Requests
|
||||
React-->>User: "Try again later"
|
||||
else Rate limit OK
|
||||
API->>PG: SELECT * FROM User WHERE email = ?
|
||||
alt User not found
|
||||
PG-->>API: null
|
||||
API-->>React: 401 Unauthorized
|
||||
React-->>User: "Invalid credentials"
|
||||
else User found
|
||||
PG-->>API: User record
|
||||
API->>API: bcrypt.compare(password, hash)
|
||||
alt Password invalid
|
||||
API-->>React: 401 Unauthorized
|
||||
React-->>User: "Invalid credentials"
|
||||
else Password valid
|
||||
API->>API: Check user status
|
||||
alt Status SUSPENDED
|
||||
API-->>React: 403 Forbidden
|
||||
React-->>User: "Account suspended"
|
||||
else Status ACTIVE
|
||||
API->>API: jwt.sign(accessPayload, 15min)
|
||||
API->>API: jwt.sign(refreshPayload, 7d)
|
||||
API->>PG: INSERT RefreshToken
|
||||
API->>PG: UPDATE lastLoginAt
|
||||
API-->>React: { user, accessToken, refreshToken }
|
||||
React->>React: Store in Zustand + localStorage
|
||||
React-->>User: Redirect to dashboard
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
**File:** `api/src/modules/auth/auth.service.ts` (lines 22-56)
|
||||
|
||||
```typescript
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { prisma } from '../../config/database';
|
||||
import { loginSchema } from './auth.schemas';
|
||||
import { incrementMetric } from '../../utils/metrics';
|
||||
|
||||
export async function login(credentials: { email: string; password: string }) {
|
||||
// Validate input
|
||||
const { email, password } = loginSchema.parse(credentials);
|
||||
|
||||
// Find user
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
password: true,
|
||||
name: true,
|
||||
role: true,
|
||||
status: true,
|
||||
emailVerified: true,
|
||||
expiresAt: true
|
||||
}
|
||||
});
|
||||
|
||||
// User enumeration prevention: consistent 401 response
|
||||
if (!user) {
|
||||
throw new Error('Invalid credentials'); // Returns 401
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isValid = await bcrypt.compare(password, user.password);
|
||||
if (!isValid) {
|
||||
throw new Error('Invalid credentials'); // Returns 401
|
||||
}
|
||||
|
||||
// Check user status
|
||||
if (user.status === 'SUSPENDED') {
|
||||
throw new Error('Account suspended'); // Returns 403
|
||||
}
|
||||
if (user.status === 'INACTIVE') {
|
||||
throw new Error('Account inactive'); // Returns 403
|
||||
}
|
||||
|
||||
// Check TEMP user expiration
|
||||
if (user.expiresAt && new Date() > user.expiresAt) {
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { status: 'EXPIRED' }
|
||||
});
|
||||
throw new Error('Account expired'); // Returns 403
|
||||
}
|
||||
|
||||
// Generate access token (15 minutes)
|
||||
const accessToken = jwt.sign(
|
||||
{ id: user.id, email: user.email, role: user.role },
|
||||
process.env.JWT_ACCESS_SECRET!,
|
||||
{ expiresIn: '15m' as const }
|
||||
);
|
||||
|
||||
// Generate refresh token (7 days)
|
||||
const refreshToken = jwt.sign(
|
||||
{ id: user.id, type: 'refresh' },
|
||||
process.env.JWT_REFRESH_SECRET!,
|
||||
{ expiresIn: '7d' as const }
|
||||
);
|
||||
|
||||
// Store refresh token in database
|
||||
await prisma.refreshToken.create({
|
||||
data: {
|
||||
token: refreshToken,
|
||||
userId: user.id,
|
||||
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
|
||||
}
|
||||
});
|
||||
|
||||
// Update last login timestamp
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { lastLoginAt: new Date() }
|
||||
});
|
||||
|
||||
// Increment metrics
|
||||
incrementMetric('cm_login_attempts_total', { status: 'success', role: user.role });
|
||||
|
||||
// Return user (no password) + tokens
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
return {
|
||||
user: userWithoutPassword,
|
||||
accessToken,
|
||||
refreshToken
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Password Policy
|
||||
|
||||
**Enforced at Zod schema level:**
|
||||
|
||||
**File:** `api/src/modules/auth/auth.schemas.ts` (lines 9-16)
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod';
|
||||
|
||||
export const passwordSchema = z
|
||||
.string()
|
||||
.min(12, 'Password must be at least 12 characters')
|
||||
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
|
||||
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
|
||||
.regex(/[0-9]/, 'Password must contain at least one digit');
|
||||
|
||||
export const registerSchema = z.object({
|
||||
email: z.string().email('Invalid email address'),
|
||||
password: passwordSchema,
|
||||
name: z.string().min(2, 'Name must be at least 2 characters')
|
||||
});
|
||||
|
||||
export const loginSchema = z.object({
|
||||
email: z.string().email('Invalid email address'),
|
||||
password: z.string().min(1, 'Password is required')
|
||||
});
|
||||
```
|
||||
|
||||
**Policy Requirements:**
|
||||
- Minimum 12 characters
|
||||
- At least one uppercase letter (A-Z)
|
||||
- At least one lowercase letter (a-z)
|
||||
- At least one digit (0-9)
|
||||
|
||||
**Note:** Policy is NOT enforced on login (only on registration/password change) to avoid breaking existing accounts.
|
||||
|
||||
## Refresh Token Flow
|
||||
|
||||
### Sequence Diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant React as Admin GUI
|
||||
participant API as Express API
|
||||
participant PG as PostgreSQL
|
||||
|
||||
Note over React: Access token expires (15min)
|
||||
React->>React: Detect 401 Unauthorized
|
||||
React->>API: POST /api/auth/refresh
|
||||
Note right of React: Send refresh token
|
||||
|
||||
API->>API: jwt.verify(refreshToken)
|
||||
alt Token invalid/expired
|
||||
API-->>React: 401 Unauthorized
|
||||
React->>React: Clear auth state
|
||||
React-->>User: Redirect to login
|
||||
else Token valid
|
||||
API->>PG: BEGIN TRANSACTION
|
||||
API->>PG: SELECT RefreshToken WHERE token = ?
|
||||
alt Token not in database
|
||||
API->>PG: ROLLBACK
|
||||
API-->>React: 401 Unauthorized
|
||||
else Token found
|
||||
API->>PG: DELETE FROM RefreshToken WHERE token = ?
|
||||
API->>API: Generate new access token (15min)
|
||||
API->>API: Generate new refresh token (7d)
|
||||
API->>PG: INSERT new RefreshToken
|
||||
API->>PG: COMMIT TRANSACTION
|
||||
API-->>React: { accessToken, refreshToken }
|
||||
React->>React: Update stored tokens
|
||||
React->>React: Retry original request
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
**File:** `api/src/modules/auth/auth.service.ts` (lines 82-130)
|
||||
|
||||
```typescript
|
||||
export async function refreshTokens(refreshToken: string) {
|
||||
// Verify refresh token signature
|
||||
let payload: any;
|
||||
try {
|
||||
payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!);
|
||||
} catch (err) {
|
||||
throw new Error('Invalid refresh token'); // Returns 401
|
||||
}
|
||||
|
||||
// Atomic transaction for token rotation
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
// Check if refresh token exists in database
|
||||
const storedToken = await tx.refreshToken.findUnique({
|
||||
where: { token: refreshToken },
|
||||
include: { user: true }
|
||||
});
|
||||
|
||||
if (!storedToken) {
|
||||
throw new Error('Refresh token not found'); // Returns 401
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
if (new Date() > storedToken.expiresAt) {
|
||||
// Delete expired token
|
||||
await tx.refreshToken.delete({
|
||||
where: { token: refreshToken }
|
||||
});
|
||||
throw new Error('Refresh token expired'); // Returns 401
|
||||
}
|
||||
|
||||
// Check user status
|
||||
if (storedToken.user.status !== 'ACTIVE') {
|
||||
throw new Error('User account not active'); // Returns 403
|
||||
}
|
||||
|
||||
// Delete old refresh token (rotation)
|
||||
await tx.refreshToken.delete({
|
||||
where: { token: refreshToken }
|
||||
});
|
||||
|
||||
// Generate new access token
|
||||
const newAccessToken = jwt.sign(
|
||||
{ id: storedToken.user.id, email: storedToken.user.email, role: storedToken.user.role },
|
||||
process.env.JWT_ACCESS_SECRET!,
|
||||
{ expiresIn: '15m' as const }
|
||||
);
|
||||
|
||||
// Generate new refresh token
|
||||
const newRefreshToken = jwt.sign(
|
||||
{ id: storedToken.user.id, type: 'refresh' },
|
||||
process.env.JWT_REFRESH_SECRET!,
|
||||
{ expiresIn: '7d' as const }
|
||||
);
|
||||
|
||||
// Store new refresh token
|
||||
await tx.refreshToken.create({
|
||||
data: {
|
||||
token: newRefreshToken,
|
||||
userId: storedToken.user.id,
|
||||
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
accessToken: newAccessToken,
|
||||
refreshToken: newRefreshToken
|
||||
};
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**Critical:** Refresh token rotation happens in a **single database transaction** to prevent race conditions (e.g., multiple refresh attempts).
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### Zustand Auth Store
|
||||
|
||||
**File:** `admin/src/stores/auth.store.ts` (lines 1-100)
|
||||
|
||||
```typescript
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
role: string;
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
user: User | null;
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
isAuthenticated: boolean;
|
||||
|
||||
login: (user: User, accessToken: string, refreshToken: string) => void;
|
||||
logout: () => void;
|
||||
updateTokens: (accessToken: string, refreshToken: string) => void;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
user: null,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
|
||||
login: (user, accessToken, refreshToken) => {
|
||||
set({
|
||||
user,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
isAuthenticated: true
|
||||
});
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
set({
|
||||
user: null,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false
|
||||
});
|
||||
},
|
||||
|
||||
updateTokens: (accessToken, refreshToken) => {
|
||||
set({ accessToken, refreshToken });
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: 'auth-storage', // LocalStorage key
|
||||
partialize: (state) => ({
|
||||
user: state.user,
|
||||
accessToken: state.accessToken,
|
||||
refreshToken: state.refreshToken,
|
||||
isAuthenticated: state.isAuthenticated
|
||||
})
|
||||
}
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
### Axios 401 Interceptor
|
||||
|
||||
**File:** `admin/src/lib/api.ts` (lines 34-78)
|
||||
|
||||
```typescript
|
||||
import axios from 'axios';
|
||||
import { useAuthStore } from '../stores/auth.store';
|
||||
|
||||
export const api = axios.create({
|
||||
baseURL: '/api',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
// Request interceptor: Add access token to all requests
|
||||
api.interceptors.request.use((config) => {
|
||||
const { accessToken } = useAuthStore.getState();
|
||||
if (accessToken) {
|
||||
config.headers.Authorization = `Bearer ${accessToken}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// Response interceptor: Handle 401 with token refresh
|
||||
let isRefreshing = false;
|
||||
let refreshCallbacks: ((token: string) => void)[] = [];
|
||||
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
// If 401 and we haven't tried refreshing yet
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
const { refreshToken, updateTokens, logout } = useAuthStore.getState();
|
||||
|
||||
if (!refreshToken) {
|
||||
logout();
|
||||
window.location.href = '/login';
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
// Deduplicate refresh requests (only one refresh at a time)
|
||||
if (isRefreshing) {
|
||||
// Wait for ongoing refresh to complete
|
||||
return new Promise((resolve) => {
|
||||
refreshCallbacks.push((token: string) => {
|
||||
originalRequest.headers.Authorization = `Bearer ${token}`;
|
||||
resolve(api(originalRequest));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isRefreshing = true;
|
||||
|
||||
try {
|
||||
// Refresh tokens
|
||||
const { data } = await axios.post('/api/auth/refresh', { refreshToken });
|
||||
|
||||
// Update stored tokens
|
||||
updateTokens(data.accessToken, data.refreshToken);
|
||||
|
||||
// Retry original request with new token
|
||||
originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
|
||||
|
||||
// Resolve queued requests
|
||||
refreshCallbacks.forEach((callback) => callback(data.accessToken));
|
||||
refreshCallbacks = [];
|
||||
|
||||
return api(originalRequest);
|
||||
} catch (refreshError) {
|
||||
// Refresh failed, logout
|
||||
logout();
|
||||
window.location.href = '/login';
|
||||
return Promise.reject(refreshError);
|
||||
} finally {
|
||||
isRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Automatic token refresh on 401
|
||||
- Deduplicates concurrent refresh requests (callback queue)
|
||||
- Retries original request after refresh
|
||||
- Logs out on refresh failure
|
||||
|
||||
## Middleware
|
||||
|
||||
### JWT Verification
|
||||
|
||||
**File:** `api/src/middleware/auth.ts` (lines 1-35)
|
||||
|
||||
```typescript
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export interface AuthUser {
|
||||
id: string;
|
||||
email: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: AuthUser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const authenticate = (req: Request, res: Response, next: NextFunction) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'No token provided' });
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
|
||||
try {
|
||||
const payload = jwt.verify(token, process.env.JWT_ACCESS_SECRET!) as AuthUser;
|
||||
req.user = payload; // Attach user to request
|
||||
next();
|
||||
} catch (err) {
|
||||
return res.status(401).json({ error: 'Invalid or expired token' });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Role-Based Access Control (RBAC)
|
||||
|
||||
**File:** `api/src/middleware/auth.ts` (lines 37-55)
|
||||
|
||||
```typescript
|
||||
export const requireRole = (...allowedRoles: string[]) => {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
if (!allowedRoles.includes(req.user.role)) {
|
||||
return res.status(403).json({
|
||||
error: 'Insufficient permissions',
|
||||
required: allowedRoles,
|
||||
current: req.user.role
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
// Block TEMP users from specific routes
|
||||
export const requireNonTemp = (req: Request, res: Response, next: NextFunction) => {
|
||||
if (req.user?.role === 'TEMP') {
|
||||
return res.status(403).json({ error: 'Temporary users cannot access this resource' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
import { authenticate, requireRole, requireNonTemp } from './middleware/auth';
|
||||
|
||||
// Require authentication
|
||||
router.get('/profile', authenticate, getProfile);
|
||||
|
||||
// Require specific role
|
||||
router.post('/campaigns', authenticate, requireRole('SUPER_ADMIN', 'INFLUENCE_ADMIN'), createCampaign);
|
||||
|
||||
// Block TEMP users
|
||||
router.post('/users', authenticate, requireNonTemp, createUser);
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
**File:** `api/src/middleware/rate-limit.ts` (lines 1-45)
|
||||
|
||||
```typescript
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import RedisStore from 'rate-limit-redis';
|
||||
import { redis } from '../config/redis';
|
||||
|
||||
// Auth endpoints: 10 requests per minute
|
||||
export const authRateLimit = rateLimit({
|
||||
store: new RedisStore({
|
||||
client: redis,
|
||||
prefix: 'rl:auth:',
|
||||
sendCommand: (...args: string[]) => redis.call(...args)
|
||||
}),
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
max: 10,
|
||||
message: 'Too many auth requests, please try again later',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false
|
||||
});
|
||||
|
||||
// Apply to auth routes
|
||||
import authRoutes from './modules/auth/auth.routes';
|
||||
app.use('/api/auth/login', authRateLimit);
|
||||
app.use('/api/auth/register', authRateLimit);
|
||||
app.use('/api/auth/refresh', authRateLimit);
|
||||
```
|
||||
|
||||
## Security Features
|
||||
|
||||
### 1. User Enumeration Prevention
|
||||
|
||||
**Problem:** Attackers can enumerate valid emails by observing different error messages.
|
||||
|
||||
**Solution:** Consistent 401 response for both "user not found" and "invalid password":
|
||||
|
||||
```typescript
|
||||
if (!user) {
|
||||
throw new Error('Invalid credentials'); // Same message
|
||||
}
|
||||
|
||||
if (!isValidPassword) {
|
||||
throw new Error('Invalid credentials'); // Same message
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Password Hashing
|
||||
|
||||
**bcryptjs with automatic salt generation:**
|
||||
|
||||
```typescript
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
// Registration
|
||||
const hashedPassword = await bcrypt.hash(password, 10); // 10 rounds
|
||||
await prisma.user.create({
|
||||
data: { email, password: hashedPassword, name, role: 'USER' }
|
||||
});
|
||||
|
||||
// Login
|
||||
const isValid = await bcrypt.compare(password, user.password);
|
||||
```
|
||||
|
||||
**Rounds:** 10 (balanced between security and performance)
|
||||
|
||||
### 3. Refresh Token Rotation
|
||||
|
||||
**Prevents replay attacks:**
|
||||
|
||||
- Old refresh token deleted immediately after use (atomic transaction)
|
||||
- New refresh token issued with each refresh
|
||||
- If old token reused → 401 error
|
||||
|
||||
### 4. Token Expiration
|
||||
|
||||
| Token Type | Lifetime | Storage | Purpose |
|
||||
|------------|----------|---------|---------|
|
||||
| **Access** | 15 minutes | Not stored (JWT only) | API authentication |
|
||||
| **Refresh** | 7 days | Database + localStorage | Token renewal |
|
||||
|
||||
**Short access token lifetime** limits damage if token is stolen.
|
||||
|
||||
### 5. Redis Authentication
|
||||
|
||||
Redis requires password authentication:
|
||||
|
||||
```bash
|
||||
# .env
|
||||
REDIS_PASSWORD=strong_password_here
|
||||
|
||||
# Redis connection
|
||||
REDIS_URL=redis://:${REDIS_PASSWORD}@redis-changemaker:6379
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Login Fails with Correct Password
|
||||
|
||||
**Cause:** User status not ACTIVE, or TEMP user expired.
|
||||
|
||||
**Solution:**
|
||||
|
||||
```sql
|
||||
-- Check user status
|
||||
SELECT email, status, expiresAt FROM "User" WHERE email = 'user@example.com';
|
||||
|
||||
-- Activate user
|
||||
UPDATE "User" SET status = 'ACTIVE' WHERE email = 'user@example.com';
|
||||
```
|
||||
|
||||
### Token Refresh Fails
|
||||
|
||||
**Cause:** Refresh token not in database (deleted or expired).
|
||||
|
||||
**Solution:**
|
||||
|
||||
```sql
|
||||
-- Check if refresh token exists
|
||||
SELECT * FROM "RefreshToken" WHERE token = 'token_here';
|
||||
|
||||
-- Delete all expired tokens
|
||||
DELETE FROM "RefreshToken" WHERE "expiresAt" < NOW();
|
||||
```
|
||||
|
||||
### 401 on All Requests
|
||||
|
||||
**Cause:** Access token missing, invalid, or expired.
|
||||
|
||||
**Debug:**
|
||||
|
||||
```bash
|
||||
# Decode JWT (without verifying signature)
|
||||
echo "eyJhbG..." | cut -d'.' -f2 | base64 -d | jq
|
||||
|
||||
# Check expiration
|
||||
# Look for "exp" field (Unix timestamp)
|
||||
```
|
||||
|
||||
### Circular Dependency (auth.store ↔ api.ts)
|
||||
|
||||
**Problem:** auth.store imports api.ts, api.ts imports auth.store (circular).
|
||||
|
||||
**Solution:** Callback registration pattern (already implemented in api.ts lines 34-78).
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [RBAC Patterns](../backend/middleware/rbac.md) - Advanced role checks
|
||||
- [Security Model](security.md) - Comprehensive security audit
|
||||
- [Database Schema](database.md) - User and RefreshToken models
|
||||
- [Frontend State Management](frontend.md) - Zustand auth store
|
||||
- [API Reference: Auth](../api-reference/auth.md) - Complete endpoint docs
|
||||
@@ -1,751 +0,0 @@
|
||||
# Dual API Architecture
|
||||
|
||||
Changemaker Lite V2 uses a dual API architecture with Express.js for main features and Fastify for the media library microservice.
|
||||
|
||||
## Why Dual API?
|
||||
|
||||
### Performance Isolation
|
||||
|
||||
Media operations (video processing, large uploads) are isolated from core platform features:
|
||||
|
||||
- **Video uploads** don't block campaign email sending
|
||||
- **Media job processing** doesn't affect map rendering
|
||||
- **Large file transfers** have separate connection pools
|
||||
|
||||
### Technology Evaluation
|
||||
|
||||
V2 evaluates two popular Node.js frameworks side-by-side:
|
||||
|
||||
| Feature | Express.js | Fastify |
|
||||
|---------|-----------|---------|
|
||||
| **Ecosystem** | Massive (15+ years) | Growing (7+ years) |
|
||||
| **Performance** | Good | Excellent (2-3x faster) |
|
||||
| **TypeScript** | Requires @types/* | Native support |
|
||||
| **Middleware** | Industry standard | Plugin system |
|
||||
| **Use Case** | General purpose | High-throughput APIs |
|
||||
|
||||
### Independent Scaling
|
||||
|
||||
Each API can scale independently:
|
||||
|
||||
- **Express API** scales with user activity (campaigns, canvassing)
|
||||
- **Media API** scales with video library size
|
||||
- Horizontal scaling: run multiple instances behind nginx load balancer
|
||||
|
||||
### Clear Service Boundaries
|
||||
|
||||
Microservice preparation without full microservices complexity:
|
||||
|
||||
- Shared database (PostgreSQL 16)
|
||||
- Shared cache (Redis)
|
||||
- Separate codebases (`api/src/server.ts` vs `api/src/media-server.ts`)
|
||||
- Future: Could split into separate repositories/deployments
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Client Layer"
|
||||
Browser[Web Browser]
|
||||
Mobile[Mobile App]
|
||||
end
|
||||
|
||||
subgraph "Proxy Layer"
|
||||
Nginx[Nginx Reverse Proxy<br/>Port 80/443]
|
||||
end
|
||||
|
||||
subgraph "API Layer"
|
||||
Express[Express API<br/>Port 4000<br/>Prisma ORM<br/>27+ Models]
|
||||
Fastify[Fastify Media API<br/>Port 4100<br/>Drizzle ORM<br/>Media Tables]
|
||||
end
|
||||
|
||||
subgraph "Data Layer"
|
||||
PG[(PostgreSQL 16<br/>changemaker_v2 DB)]
|
||||
Redis[(Redis 7<br/>Cache + Queues)]
|
||||
end
|
||||
|
||||
subgraph "External Services"
|
||||
SMTP[SMTP Server]
|
||||
Represent[Represent API]
|
||||
Geocoding[Geocoding APIs]
|
||||
Listmonk[Listmonk]
|
||||
end
|
||||
|
||||
Browser --> Nginx
|
||||
Mobile --> Nginx
|
||||
|
||||
Nginx -->|/api/* except /api/media/*| Express
|
||||
Nginx -->|/api/media/*| Fastify
|
||||
|
||||
Express --> PG
|
||||
Express --> Redis
|
||||
Express --> SMTP
|
||||
Express --> Represent
|
||||
Express --> Geocoding
|
||||
Express --> Listmonk
|
||||
|
||||
Fastify --> PG
|
||||
Fastify --> Redis
|
||||
|
||||
style Express fill:#61dafb,stroke:#333,stroke-width:2px
|
||||
style Fastify fill:#00d562,stroke:#333,stroke-width:2px
|
||||
style PG fill:#336791,stroke:#333,stroke-width:2px
|
||||
style Redis fill:#dc382d,stroke:#333,stroke-width:2px
|
||||
```
|
||||
|
||||
## Express API (Main Features)
|
||||
|
||||
### Entry Point
|
||||
|
||||
**File:** `api/src/server.ts` (234 lines)
|
||||
|
||||
```typescript
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import { errorHandler } from './middleware/error-handler';
|
||||
import { authenticate } from './middleware/auth';
|
||||
import { metricsMiddleware } from './utils/metrics';
|
||||
|
||||
const app = express();
|
||||
|
||||
// Global middleware
|
||||
app.use(helmet());
|
||||
app.use(cors({ origin: process.env.CORS_ORIGIN, credentials: true }));
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
app.use(metricsMiddleware);
|
||||
|
||||
// Health check (no auth)
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// Metrics endpoint (no auth, for Prometheus)
|
||||
app.get('/api/metrics', async (req, res) => {
|
||||
res.set('Content-Type', register.contentType);
|
||||
res.end(await register.metrics());
|
||||
});
|
||||
|
||||
// Route registration (40+ route groups)
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/users', authenticate, usersRoutes);
|
||||
app.use('/api/settings', authenticate, settingsRoutes);
|
||||
app.use('/api/campaigns', campaignsRoutes); // Public + admin routes
|
||||
app.use('/api/representatives', representativesRoutes);
|
||||
app.use('/api/responses', responsesRoutes); // Public + admin + moderation
|
||||
// ... 35+ more route groups
|
||||
|
||||
// Global error handler (must be last)
|
||||
app.use(errorHandler);
|
||||
|
||||
const PORT = process.env.API_PORT || 4000;
|
||||
app.listen(PORT, () => {
|
||||
logger.info(`Express API listening on port ${PORT}`);
|
||||
});
|
||||
```
|
||||
|
||||
### Key Features
|
||||
|
||||
**14 Feature Modules:**
|
||||
|
||||
1. **auth** - JWT login, register, refresh, logout
|
||||
2. **users** - User CRUD with pagination + search
|
||||
3. **settings** - Site settings singleton
|
||||
4. **campaigns** - Campaign CRUD + public routes
|
||||
5. **representatives** - Represent API integration
|
||||
6. **responses** - Response wall + moderation + upvoting
|
||||
7. **email-queue** - BullMQ queue admin
|
||||
8. **campaign-emails** - Email tracking + stats
|
||||
9. **postal-codes** - Postal code cache
|
||||
10. **locations** - Location CRUD + geocoding + NAR import
|
||||
11. **cuts** - Cut (polygon) CRUD + spatial queries
|
||||
12. **shifts** - Shift CRUD + signups
|
||||
13. **canvass** - Volunteer canvassing (sessions, visits, routes)
|
||||
14. **pages** - Landing page builder (GrapesJS)
|
||||
|
||||
**Plus:** email-templates, listmonk, pangolin, docs, qr, services, observability
|
||||
|
||||
### Architecture Pattern
|
||||
|
||||
**Layered Structure:**
|
||||
|
||||
```
|
||||
api/src/modules/{module}/
|
||||
├── {module}.routes.ts # Express router + middleware
|
||||
├── {module}.service.ts # Business logic + database queries
|
||||
├── {module}.schemas.ts # Zod validation schemas
|
||||
└── {module}.types.ts # TypeScript interfaces (optional)
|
||||
```
|
||||
|
||||
**Example: Campaign Module**
|
||||
|
||||
```typescript
|
||||
// campaigns.routes.ts
|
||||
import { Router } from 'express';
|
||||
import { validate } from '../../middleware/validate';
|
||||
import { authenticate, requireRole } from '../../middleware/auth';
|
||||
import { createCampaignSchema, updateCampaignSchema } from './campaigns.schemas';
|
||||
import * as campaignService from './campaigns.service';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Admin routes (auth required)
|
||||
router.post('/',
|
||||
authenticate,
|
||||
requireRole('SUPER_ADMIN', 'INFLUENCE_ADMIN'),
|
||||
validate(createCampaignSchema),
|
||||
async (req, res) => {
|
||||
const campaign = await campaignService.createCampaign(req.body, req.user!.id);
|
||||
res.status(201).json(campaign);
|
||||
}
|
||||
);
|
||||
|
||||
// Public routes (no auth)
|
||||
router.get('/:id', async (req, res) => {
|
||||
const campaign = await campaignService.getCampaignById(req.params.id);
|
||||
res.json(campaign);
|
||||
});
|
||||
|
||||
export default router;
|
||||
```
|
||||
|
||||
### ORM: Prisma
|
||||
|
||||
**27+ Models** in `api/prisma/schema.prisma`:
|
||||
|
||||
```typescript
|
||||
model Campaign {
|
||||
id String @id @default(cuid())
|
||||
slug String @unique
|
||||
title String
|
||||
description String? @db.Text
|
||||
emailSubject String
|
||||
emailBody String @db.Text
|
||||
status CampaignStatus @default(DRAFT)
|
||||
|
||||
// Feature flags
|
||||
allowSmtpEmail Boolean @default(true)
|
||||
showResponseWall Boolean @default(true)
|
||||
|
||||
// Audit fields
|
||||
createdByUserId String?
|
||||
createdByUser User? @relation(fields: [createdByUserId], references: [id], onDelete: SetNull)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
emails CampaignEmail[]
|
||||
responses RepresentativeResponse[]
|
||||
customRecipients CustomRecipient[]
|
||||
}
|
||||
```
|
||||
|
||||
**Connection Pooling:**
|
||||
|
||||
Prisma manages connection pool automatically:
|
||||
|
||||
```typescript
|
||||
// prisma/schema.prisma
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// Default pool size: 10 connections per instance
|
||||
// Configure via DATABASE_URL: ?connection_limit=20
|
||||
```
|
||||
|
||||
## Fastify API (Media Library)
|
||||
|
||||
### Entry Point
|
||||
|
||||
**File:** `api/src/media-server.ts` (104 lines)
|
||||
|
||||
```typescript
|
||||
import Fastify from 'fastify';
|
||||
import cors from '@fastify/cors';
|
||||
import helmet from '@fastify/helmet';
|
||||
import { videosRoutes } from './modules/media/videos/videos.routes';
|
||||
import { sharedMediaRoutes } from './modules/media/shared-media/shared-media.routes';
|
||||
import { jobsRoutes } from './modules/media/jobs/jobs.routes';
|
||||
import { reactionsRoutes } from './modules/media/reactions/reactions.routes';
|
||||
|
||||
const fastify = Fastify({
|
||||
logger: {
|
||||
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug'
|
||||
}
|
||||
});
|
||||
|
||||
// Plugins
|
||||
await fastify.register(cors, {
|
||||
origin: process.env.CORS_ORIGIN,
|
||||
credentials: true
|
||||
});
|
||||
await fastify.register(helmet);
|
||||
|
||||
// Health check
|
||||
fastify.get('/health', async (request, reply) => {
|
||||
return { status: 'healthy', timestamp: new Date().toISOString() };
|
||||
});
|
||||
|
||||
// Route registration
|
||||
fastify.register(videosRoutes, { prefix: '/api/media/videos' });
|
||||
fastify.register(sharedMediaRoutes, { prefix: '/api/media/shared' });
|
||||
fastify.register(jobsRoutes, { prefix: '/api/media/jobs' });
|
||||
fastify.register(reactionsRoutes, { prefix: '/api/media/reactions' });
|
||||
|
||||
const PORT = Number(process.env.MEDIA_API_PORT) || 4100;
|
||||
await fastify.listen({ port: PORT, host: '0.0.0.0' });
|
||||
fastify.log.info(`Fastify Media API listening on port ${PORT}`);
|
||||
```
|
||||
|
||||
### Key Features
|
||||
|
||||
**4 Feature Modules:**
|
||||
|
||||
1. **videos** - Video CRUD, metadata, tags, deduplication
|
||||
2. **shared-media** - Public gallery categories (videos, curated, compilations, etc.)
|
||||
3. **jobs** - Job queue monitoring (pending, running, completed, failed)
|
||||
4. **reactions** - Reaction system (6 standard emojis: like, love, laugh, wow, sad, angry)
|
||||
|
||||
### Architecture Pattern
|
||||
|
||||
**Plugin-Based:**
|
||||
|
||||
```typescript
|
||||
// videos.routes.ts
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { verifyJWT } from '../../middleware/auth';
|
||||
import { getVideosSchema, createVideoSchema } from './videos.schemas';
|
||||
|
||||
export const videosRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
// Middleware: JWT verification
|
||||
fastify.addHook('onRequest', verifyJWT);
|
||||
|
||||
// GET /api/media/videos
|
||||
fastify.get('/', {
|
||||
schema: getVideosSchema,
|
||||
handler: async (request, reply) => {
|
||||
const videos = await getVideos(request.query);
|
||||
return videos;
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/media/videos
|
||||
fastify.post('/', {
|
||||
schema: createVideoSchema,
|
||||
handler: async (request, reply) => {
|
||||
const video = await createVideo(request.body);
|
||||
return reply.status(201).send(video);
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### ORM: Drizzle
|
||||
|
||||
**Media Tables** in `api/src/modules/media/db/schema.ts`:
|
||||
|
||||
```typescript
|
||||
import { pgTable, serial, text, integer, boolean, timestamp, jsonb } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const videos = pgTable('videos', {
|
||||
id: serial('id').primaryKey(),
|
||||
path: text('path').unique().notNull(),
|
||||
filename: text('filename').notNull(),
|
||||
producer: text('producer'),
|
||||
creator: text('creator'),
|
||||
title: text('title'),
|
||||
durationSeconds: integer('duration_seconds'),
|
||||
width: integer('width'),
|
||||
height: integer('height'),
|
||||
orientation: text('orientation'), // 'landscape' | 'portrait' | 'square'
|
||||
hasAudio: boolean('has_audio').default(true),
|
||||
fileSize: integer('file_size'),
|
||||
thumbnailPath: text('thumbnail_path'),
|
||||
tags: jsonb('tags').$type<string[]>(),
|
||||
isValid: boolean('is_valid').default(true),
|
||||
createdAt: timestamp('created_at').defaultNow(),
|
||||
}, (table) => ({
|
||||
orientationIdx: index('idx_orientation').on(table.orientation),
|
||||
producerIdx: index('idx_producer').on(table.producer),
|
||||
}));
|
||||
```
|
||||
|
||||
**Connection:**
|
||||
|
||||
Drizzle uses the same PostgreSQL connection pool:
|
||||
|
||||
```typescript
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
max: 10
|
||||
});
|
||||
|
||||
export const db = drizzle(pool);
|
||||
```
|
||||
|
||||
## Request Flow
|
||||
|
||||
### Public Campaign Email Submission
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as User Browser
|
||||
participant Nginx
|
||||
participant React as Admin GUI
|
||||
participant Express as Express API
|
||||
participant PG as PostgreSQL
|
||||
participant Redis
|
||||
participant BullMQ
|
||||
participant SMTP
|
||||
|
||||
User->>React: Visit /campaigns/123
|
||||
React->>Nginx: GET /campaigns/123
|
||||
Nginx->>React: Serve React app
|
||||
React->>Nginx: GET /api/campaigns/123
|
||||
Nginx->>Express: Forward to Express
|
||||
Express->>PG: SELECT campaign
|
||||
PG-->>Express: Campaign data
|
||||
Express-->>React: Campaign JSON
|
||||
React-->>User: Render page
|
||||
|
||||
User->>React: Submit email form
|
||||
React->>Nginx: POST /api/campaigns/123/send-email
|
||||
Nginx->>Express: Forward to Express
|
||||
Express->>Express: Rate limit check (30/hour)
|
||||
Express->>PG: INSERT CampaignEmail
|
||||
Express->>BullMQ: Enqueue job
|
||||
BullMQ->>Redis: Add job to queue
|
||||
Express-->>React: Success response
|
||||
React-->>User: "Email queued"
|
||||
|
||||
BullMQ->>Express: Process job (worker)
|
||||
Express->>PG: SELECT email + campaign
|
||||
Express->>Express: Build SMTP message
|
||||
Express->>SMTP: Send email
|
||||
SMTP-->>Express: Delivery confirmed
|
||||
Express->>PG: UPDATE status = SENT
|
||||
Express->>Redis: Increment cm_emails_sent_total
|
||||
```
|
||||
|
||||
### Admin Media Upload
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Admin as Admin Browser
|
||||
participant Nginx
|
||||
participant Fastify as Fastify Media API
|
||||
participant PG as PostgreSQL
|
||||
participant FS as File System
|
||||
|
||||
Admin->>Nginx: POST /api/media/videos (10GB file)
|
||||
Nginx->>Fastify: Stream upload (no buffering)
|
||||
Fastify->>FS: Save to /media/videos/
|
||||
Fastify->>PG: INSERT video metadata
|
||||
PG-->>Fastify: Video record
|
||||
Fastify-->>Admin: { id, path, thumbnail }
|
||||
```
|
||||
|
||||
**Key Difference:**
|
||||
- Express handles small JSON payloads (campaigns, locations, users)
|
||||
- Fastify handles large file uploads (streaming, no buffering)
|
||||
|
||||
## Shared Resources
|
||||
|
||||
### PostgreSQL Database
|
||||
|
||||
**Single Database, Multiple Schemas:**
|
||||
|
||||
- **Prisma Tables** — Main schema (User, Campaign, Location, etc.)
|
||||
- **Drizzle Tables** — Media schema (videos, jobs, reactions)
|
||||
|
||||
Both ORMs connect to the same `changemaker_v2` database:
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgresql://changemaker:password@v2-postgres:5432/changemaker_v2
|
||||
```
|
||||
|
||||
**No Conflicts:**
|
||||
- Prisma manages its own schema via migrations (`npx prisma migrate`)
|
||||
- Drizzle manages media tables via `npx drizzle-kit push`
|
||||
- Tables don't overlap (different prefixes)
|
||||
|
||||
### Redis Cache
|
||||
|
||||
Both APIs use Redis for:
|
||||
|
||||
- **Caching** — Postal codes (Express), video metadata (Fastify)
|
||||
- **Rate Limiting** — Redis-backed limits (Express: 30/hour, Fastify: 100/min)
|
||||
- **BullMQ Queues** — Email queue (Express), job queue (Fastify)
|
||||
|
||||
```typescript
|
||||
// Shared Redis connection
|
||||
import Redis from 'ioredis';
|
||||
|
||||
export const redis = new Redis({
|
||||
host: 'redis-changemaker',
|
||||
port: 6379,
|
||||
password: process.env.REDIS_PASSWORD,
|
||||
maxRetriesPerRequest: 3
|
||||
});
|
||||
```
|
||||
|
||||
### JWT Authentication
|
||||
|
||||
Both APIs verify the same JWT tokens:
|
||||
|
||||
```typescript
|
||||
// Express: api/src/middleware/auth.ts
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export const authenticate = (req, res, next) => {
|
||||
const token = req.headers.authorization?.split(' ')[1];
|
||||
const payload = jwt.verify(token, process.env.JWT_ACCESS_SECRET);
|
||||
req.user = payload; // { id, email, role }
|
||||
next();
|
||||
};
|
||||
|
||||
// Fastify: api/src/modules/media/middleware/auth.ts
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export const verifyJWT = async (request, reply) => {
|
||||
const token = request.headers.authorization?.split(' ')[1];
|
||||
const payload = jwt.verify(token, process.env.JWT_ACCESS_SECRET);
|
||||
request.user = payload;
|
||||
};
|
||||
```
|
||||
|
||||
**Shared Secret:** `JWT_ACCESS_SECRET` environment variable
|
||||
|
||||
## Nginx Routing
|
||||
|
||||
### Location Block Ordering
|
||||
|
||||
**Critical:** Media API location must come BEFORE general API location:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.cmlite.org;
|
||||
|
||||
# Media API (longest prefix first)
|
||||
location /api/media/ {
|
||||
proxy_pass http://changemaker-media-api:4100;
|
||||
client_max_body_size 10G;
|
||||
}
|
||||
|
||||
# Express API (catch-all)
|
||||
location /api/ {
|
||||
proxy_pass http://changemaker-v2-api:4000;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why Order Matters:**
|
||||
|
||||
Nginx matches longest prefix first. If `/api/` came first, it would match `/api/media/videos` and route to Express (wrong).
|
||||
|
||||
### Subdomain Routing (Production)
|
||||
|
||||
```nginx
|
||||
# Express API
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.cmlite.org;
|
||||
location / {
|
||||
proxy_pass http://changemaker-v2-api:4000;
|
||||
}
|
||||
}
|
||||
|
||||
# Fastify Media API
|
||||
server {
|
||||
listen 80;
|
||||
server_name media.cmlite.org;
|
||||
location / {
|
||||
proxy_pass http://changemaker-media-api:4100;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Comparison
|
||||
|
||||
### Benchmarks (Internal Testing)
|
||||
|
||||
**Simple GET Request (JSON response):**
|
||||
|
||||
| Framework | Requests/sec | Latency p95 | Memory |
|
||||
|-----------|--------------|-------------|--------|
|
||||
| Express | 12,500 | 35ms | 150MB |
|
||||
| Fastify | 28,000 | 15ms | 120MB |
|
||||
|
||||
**Large Upload (1GB file):**
|
||||
|
||||
| Framework | Upload Time | Memory Peak | CPU Usage |
|
||||
|-----------|-------------|-------------|-----------|
|
||||
| Express | 45s | 450MB | 85% |
|
||||
| Fastify | 38s | 280MB | 60% |
|
||||
|
||||
**Real-World Usage:**
|
||||
|
||||
- Express handles 95% of requests (campaigns, users, locations)
|
||||
- Fastify handles 5% of requests (video uploads, media library)
|
||||
- Both run comfortably on single-core containers
|
||||
|
||||
## Future: Full Microservices
|
||||
|
||||
The dual API design prepares for future microservices migration:
|
||||
|
||||
### Potential Split
|
||||
|
||||
```
|
||||
├── campaign-service/ # Express API (Influence module)
|
||||
├── map-service/ # Express API (Map module)
|
||||
├── media-service/ # Fastify API (Media module)
|
||||
├── auth-service/ # Shared authentication
|
||||
└── api-gateway/ # Nginx or Kong
|
||||
```
|
||||
|
||||
### Benefits
|
||||
|
||||
- **Independent deployment** — Ship campaign features without redeploying map
|
||||
- **Technology flexibility** — Use Go for high-throughput, Python for ML
|
||||
- **Team ownership** — Separate teams own separate services
|
||||
- **Fault isolation** — Media service crash doesn't affect campaigns
|
||||
|
||||
### Trade-offs
|
||||
|
||||
- **Operational complexity** — More containers, more monitoring
|
||||
- **Network latency** — Inter-service calls over HTTP
|
||||
- **Data consistency** — Distributed transactions harder
|
||||
- **Development overhead** — Multiple repos, versioning
|
||||
|
||||
**V2 Strategy:** Keep dual API until scaling requires split (likely 10,000+ users).
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Running Both APIs
|
||||
|
||||
```bash
|
||||
# Terminal 1: Express API
|
||||
cd api && npm run dev # Port 4000
|
||||
|
||||
# Terminal 2: Fastify Media API
|
||||
cd api && npm run dev:media # Port 4100
|
||||
|
||||
# Terminal 3: Admin GUI
|
||||
cd admin && npm run dev # Port 3000
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```bash
|
||||
# Start both APIs
|
||||
docker compose up -d api media-api
|
||||
|
||||
# View logs
|
||||
docker compose logs -f api
|
||||
docker compose logs -f media-api
|
||||
|
||||
# Rebuild after dependency changes
|
||||
docker compose build api media-api
|
||||
docker compose up -d api media-api
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
Both APIs expose Prometheus metrics:
|
||||
|
||||
- **Express:** `http://localhost:4000/api/metrics`
|
||||
- **Fastify:** `http://localhost:4100/metrics`
|
||||
|
||||
**Custom Metrics:**
|
||||
|
||||
```typescript
|
||||
// Express: api/src/utils/metrics.ts
|
||||
import client from 'prom-client';
|
||||
|
||||
export const httpRequestTotal = new client.Counter({
|
||||
name: 'http_request_total',
|
||||
help: 'Total HTTP requests',
|
||||
labelNames: ['method', 'route', 'status']
|
||||
});
|
||||
|
||||
export const emailsSentTotal = new client.Counter({
|
||||
name: 'cm_emails_sent_total',
|
||||
help: 'Total campaign emails sent'
|
||||
});
|
||||
|
||||
// Fastify: api/src/modules/media/metrics.ts
|
||||
export const mediaUploadsTotal = new client.Counter({
|
||||
name: 'cm_media_uploads_total',
|
||||
help: 'Total media uploads',
|
||||
labelNames: ['type']
|
||||
});
|
||||
```
|
||||
|
||||
Prometheus scrapes both endpoints every 15 seconds.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Media API Returns 404
|
||||
|
||||
**Cause:** Nginx routing issue (order of location blocks).
|
||||
|
||||
**Fix:** Ensure `/api/media/` comes BEFORE `/api/` in nginx config.
|
||||
|
||||
### Large Upload Fails (413)
|
||||
|
||||
**Cause:** `client_max_body_size` too small.
|
||||
|
||||
**Fix:** Increase in nginx config:
|
||||
|
||||
```nginx
|
||||
location /api/media/ {
|
||||
client_max_body_size 20G; # Increase from default
|
||||
}
|
||||
```
|
||||
|
||||
### Connection Pool Exhausted
|
||||
|
||||
**Cause:** Too many concurrent requests, not enough DB connections.
|
||||
|
||||
**Fix:** Increase connection limit in `DATABASE_URL`:
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=20
|
||||
```
|
||||
|
||||
Or reduce pool size per API instance (if running multiple):
|
||||
|
||||
```typescript
|
||||
// Prisma
|
||||
datasource db {
|
||||
url = env("DATABASE_URL") // Add ?connection_limit=5 for smaller pool
|
||||
}
|
||||
|
||||
// Drizzle
|
||||
const pool = new Pool({ max: 5 });
|
||||
```
|
||||
|
||||
### JWT Verification Fails Across APIs
|
||||
|
||||
**Cause:** Different `JWT_ACCESS_SECRET` values.
|
||||
|
||||
**Fix:** Ensure both APIs use the same secret:
|
||||
|
||||
```bash
|
||||
# .env
|
||||
JWT_ACCESS_SECRET=<same-value-for-both>
|
||||
```
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Database Architecture](database.md) — Prisma vs Drizzle schemas
|
||||
- [Authentication Flow](authentication.md) — JWT implementation
|
||||
- [Monitoring Stack](monitoring.md) — Prometheus metrics
|
||||
- [Nginx Configuration](../deployment/nginx.md) — Reverse proxy setup
|
||||
- [Scaling Strategies](../deployment/scaling.md) — Horizontal scaling
|
||||
@@ -1,590 +0,0 @@
|
||||
# V2 Architecture Overview
|
||||
|
||||
Changemaker Lite V2 is built on a modern microservices architecture with a dual API design, React admin interface, and comprehensive observability.
|
||||
|
||||
## System Architecture
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "User Access"
|
||||
Browser[Web Browser]
|
||||
VolunteerApp[Volunteer Mobile]
|
||||
end
|
||||
|
||||
subgraph "Nginx Reverse Proxy"
|
||||
Nginx[Nginx<br/>Subdomain Router]
|
||||
end
|
||||
|
||||
subgraph "Frontend Layer"
|
||||
AdminGUI[Admin GUI<br/>React + Vite + Ant Design<br/>Port 3000]
|
||||
PublicPages[Public Pages<br/>Dark Theme]
|
||||
VolunteerPortal[Volunteer Portal<br/>GPS Canvassing]
|
||||
end
|
||||
|
||||
subgraph "Backend Layer - Dual API"
|
||||
ExpressAPI[Express API<br/>Main Features<br/>Port 4000<br/>Prisma ORM]
|
||||
FastifyAPI[Fastify API<br/>Media Library<br/>Port 4100<br/>Drizzle ORM]
|
||||
end
|
||||
|
||||
subgraph "Data Layer"
|
||||
Postgres[(PostgreSQL 16<br/>27+ Models)]
|
||||
Redis[(Redis<br/>Cache + Queues)]
|
||||
end
|
||||
|
||||
subgraph "Job Processing"
|
||||
EmailQueue[BullMQ<br/>Email Queue]
|
||||
GeocodeQueue[BullMQ<br/>Geocoding Queue]
|
||||
end
|
||||
|
||||
subgraph "External Services"
|
||||
SMTP[SMTP Server<br/>Email Delivery]
|
||||
Represent[Represent API<br/>Canadian Reps]
|
||||
Geocoding[Geocoding Providers<br/>6 Services]
|
||||
Listmonk[Listmonk<br/>Newsletter Platform]
|
||||
end
|
||||
|
||||
subgraph "Observability"
|
||||
Prometheus[Prometheus<br/>Metrics]
|
||||
Grafana[Grafana<br/>Dashboards]
|
||||
Alertmanager[Alertmanager<br/>Notifications]
|
||||
end
|
||||
|
||||
Browser --> Nginx
|
||||
VolunteerApp --> Nginx
|
||||
|
||||
Nginx --> AdminGUI
|
||||
Nginx --> PublicPages
|
||||
Nginx --> VolunteerPortal
|
||||
|
||||
AdminGUI --> ExpressAPI
|
||||
AdminGUI --> FastifyAPI
|
||||
PublicPages --> ExpressAPI
|
||||
VolunteerPortal --> ExpressAPI
|
||||
|
||||
ExpressAPI --> Postgres
|
||||
ExpressAPI --> Redis
|
||||
ExpressAPI --> EmailQueue
|
||||
ExpressAPI --> GeocodeQueue
|
||||
ExpressAPI --> Represent
|
||||
ExpressAPI --> Geocoding
|
||||
ExpressAPI --> Listmonk
|
||||
ExpressAPI --> Prometheus
|
||||
|
||||
FastifyAPI --> Postgres
|
||||
FastifyAPI --> Redis
|
||||
FastifyAPI --> Prometheus
|
||||
|
||||
EmailQueue --> Redis
|
||||
EmailQueue --> SMTP
|
||||
GeocodeQueue --> Redis
|
||||
GeocodeQueue --> Geocoding
|
||||
|
||||
Prometheus --> Grafana
|
||||
Prometheus --> Alertmanager
|
||||
```
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. Nginx Reverse Proxy
|
||||
|
||||
**Purpose**: Routes HTTP requests to appropriate services based on subdomain
|
||||
|
||||
**Subdomains**:
|
||||
- `app.cmlite.org` → Admin GUI (React)
|
||||
- `api.cmlite.org` → Express API (main features)
|
||||
- `media.cmlite.org` → Fastify API (video library)
|
||||
- `db.cmlite.org` → NocoDB (data browser)
|
||||
- `docs.cmlite.org` → MkDocs (documentation)
|
||||
- `listmonk.cmlite.org` → Listmonk (newsletter)
|
||||
- `grafana.cmlite.org` → Grafana (monitoring)
|
||||
- And 8 more service subdomains...
|
||||
|
||||
**Configuration**: `/nginx/conf.d/`
|
||||
|
||||
[Learn more →](networking.md)
|
||||
|
||||
### 2. Frontend Layer
|
||||
|
||||
#### Admin GUI (Port 3000)
|
||||
- **Framework**: React 19 with Vite build tool
|
||||
- **UI Library**: Ant Design 5 (Table, Form, Modal, Drawer components)
|
||||
- **State Management**: Zustand stores (auth, canvass)
|
||||
- **Routing**: React Router v6
|
||||
- **HTTP Client**: Axios with 401 refresh interceptor
|
||||
|
||||
**Structure**:
|
||||
- 32 admin pages (campaigns, locations, users, settings, etc.)
|
||||
- 6 public pages (campaign view, response wall, map, shifts)
|
||||
- 4 volunteer portal pages (canvassing, assignments, activity)
|
||||
- Shared components (map, canvass, GrapesJS editor)
|
||||
|
||||
[Learn more →](frontend.md)
|
||||
|
||||
#### Public Pages
|
||||
- Dark blue/teal theme (consistent with V1 branding)
|
||||
- No authentication required
|
||||
- Mobile-responsive layouts
|
||||
- Public campaign submission
|
||||
- Response wall with upvoting
|
||||
- Public map with location markers
|
||||
- Shift signup forms
|
||||
|
||||
#### Volunteer Portal
|
||||
- Top navigation layout
|
||||
- Mobile-optimized (hamburger menu)
|
||||
- GPS-tracked canvassing
|
||||
- Full-screen map interface
|
||||
- Visit recording forms
|
||||
- Activity tracking
|
||||
|
||||
### 3. Backend Layer - Dual API Design
|
||||
|
||||
#### Express API (Port 4000)
|
||||
**Main application server** handling core features:
|
||||
|
||||
**14 Feature Modules**:
|
||||
1. **auth** - JWT login, register, refresh, logout
|
||||
2. **users** - User CRUD with pagination
|
||||
3. **settings** - Site settings singleton
|
||||
4. **campaigns** - Campaign CRUD + public routes
|
||||
5. **representatives** - Represent API integration
|
||||
6. **responses** - Response wall + moderation
|
||||
7. **email-queue** - BullMQ queue admin
|
||||
8. **campaign-emails** - Email tracking + stats
|
||||
9. **postal-codes** - Postal code cache
|
||||
10. **locations** - Location CRUD + geocoding + NAR import
|
||||
11. **cuts** - Cut (polygon) CRUD + spatial queries
|
||||
12. **shifts** - Shift CRUD + signups
|
||||
13. **canvass** - Volunteer canvassing (sessions, visits, routes)
|
||||
14. **pages** - Landing page builder (GrapesJS)
|
||||
|
||||
**Plus**: email-templates, listmonk, pangolin, docs, qr, services, observability
|
||||
|
||||
**ORM**: Prisma (27+ models)
|
||||
|
||||
**Architecture**:
|
||||
- Layered structure (routes → services → database)
|
||||
- Zod schema validation
|
||||
- Role-based access control (RBAC)
|
||||
- Error handling middleware
|
||||
- Winston logging
|
||||
|
||||
[Learn more →](dual-api.md)
|
||||
|
||||
#### Fastify API (Port 4100)
|
||||
**Specialized microservice** for media library:
|
||||
|
||||
**Features**:
|
||||
- Video CRUD (title, duration, orientation, producer)
|
||||
- Shared media (public gallery categories)
|
||||
- Lock/unlock system (public visibility control)
|
||||
- Reaction system (6 standard emojis)
|
||||
- Job queue monitoring
|
||||
- Bulk operations
|
||||
|
||||
**ORM**: Drizzle (lightweight schema-first)
|
||||
|
||||
**Why Separate?**:
|
||||
- Performance isolation (video ops don't slow main API)
|
||||
- Different ORM evaluation (Drizzle vs Prisma)
|
||||
- Independent scaling
|
||||
- Clear service boundaries
|
||||
|
||||
**Shared Resources**:
|
||||
- Same PostgreSQL database (different schemas)
|
||||
- Same Redis instance
|
||||
- Reuses JWT_ACCESS_SECRET for auth
|
||||
|
||||
[Learn more →](dual-api.md)
|
||||
|
||||
### 4. Data Layer
|
||||
|
||||
#### PostgreSQL 16
|
||||
**Primary database** with two ORM schemas:
|
||||
|
||||
**Prisma Schema** (27+ models):
|
||||
- User, RefreshToken (auth)
|
||||
- Campaign, Representative, Response, CampaignEmail (influence)
|
||||
- Location, Cut, Shift, ShiftSignup (map)
|
||||
- CanvassSession, CanvassVisit, TrackingSession, TrackPoint (canvass)
|
||||
- LandingPage, PageBlock, EmailTemplate (content)
|
||||
- SiteSettings, MapSettings (config)
|
||||
|
||||
**Drizzle Schema** (media tables):
|
||||
- videos
|
||||
- shared_media
|
||||
- reactions
|
||||
- jobs
|
||||
|
||||
**Indexes**: Optimized for common queries (userId, campaignId, cutId, etc.)
|
||||
|
||||
[Learn more →](database.md)
|
||||
|
||||
#### Redis
|
||||
**Multi-purpose cache and queue backend**:
|
||||
|
||||
- **Caching**: Postal codes (7-day TTL), representatives
|
||||
- **Rate Limiting**: Per-endpoint limits (Redis-backed)
|
||||
- **BullMQ Queues**: Email sending, bulk geocoding
|
||||
- **Sessions**: Future session storage (if needed)
|
||||
|
||||
**Authentication**: Required (`REDIS_PASSWORD` env var)
|
||||
|
||||
### 5. Job Processing
|
||||
|
||||
#### BullMQ Queues
|
||||
**Async job processing** for long-running operations:
|
||||
|
||||
**Email Queue**:
|
||||
- Campaign email sending (SMTP)
|
||||
- Email verification (double opt-in)
|
||||
- Confirmation emails (shift signups)
|
||||
- Retry logic (exponential backoff)
|
||||
- Rate limiting (avoid spam flagging)
|
||||
|
||||
**Geocoding Queue**:
|
||||
- Bulk address geocoding
|
||||
- Multi-provider fallback (6 services)
|
||||
- Rate limit compliance (500 jobs/min)
|
||||
- Result caching
|
||||
|
||||
**Queue Management**:
|
||||
- Admin routes for pause/resume
|
||||
- Job status monitoring
|
||||
- Failed job inspection
|
||||
- Queue metrics (Prometheus)
|
||||
|
||||
### 6. External Services
|
||||
|
||||
#### SMTP Server
|
||||
Email delivery for:
|
||||
- Campaign advocacy emails
|
||||
- Email verification
|
||||
- Password reset
|
||||
- Shift confirmation
|
||||
- Admin notifications
|
||||
|
||||
**Dev Mode**: MailHog captures emails (`EMAIL_TEST_MODE=true`)
|
||||
|
||||
#### Represent API
|
||||
Canadian elected representative lookup:
|
||||
- Postal code → MPs, MPPs, councillors
|
||||
- Caching (7-day TTL per postal code)
|
||||
- Fallback to cached data on API errors
|
||||
|
||||
#### Geocoding Providers
|
||||
Multi-provider geocoding with fallback:
|
||||
|
||||
1. Nominatim (OpenStreetMap, free)
|
||||
2. Mapbox (requires API key, best accuracy)
|
||||
3. ArcGIS (free tier available)
|
||||
4. Photon (OSM-based, no key required)
|
||||
5. Google (requires API key, high cost)
|
||||
6. LocationIQ (requires API key, generous free tier)
|
||||
|
||||
**Strategy**: Try each provider in order until success
|
||||
|
||||
#### Listmonk Newsletter Platform
|
||||
Email marketing integration:
|
||||
- Sync participants/locations/users → subscriber lists
|
||||
- Newsletter campaigns (separate from advocacy emails)
|
||||
- API integration (basic auth)
|
||||
- Health monitoring
|
||||
|
||||
### 7. Observability Stack
|
||||
|
||||
#### Prometheus
|
||||
**Metrics collection** with custom instrumentation:
|
||||
|
||||
**12 Custom Metrics** (`cm_*` prefix):
|
||||
- `cm_api_uptime_seconds` - API availability
|
||||
- `cm_email_queue_size` - Queue depth
|
||||
- `cm_email_sent_total` - Email delivery count
|
||||
- `cm_geocode_success_rate` - Geocoding quality
|
||||
- `cm_active_canvass_sessions` - Live canvassing
|
||||
- And 7 more domain-specific metrics...
|
||||
|
||||
**HTTP Metrics**:
|
||||
- `http_request_total` - Total requests
|
||||
- `http_request_duration_seconds` - Latency histogram
|
||||
- `http_request_errors_total` - Error count
|
||||
|
||||
**Scrape Targets**:
|
||||
- Express API (`:4000/metrics`)
|
||||
- Fastify API (`:4100/metrics`)
|
||||
- Redis Exporter
|
||||
- Node Exporter (host metrics)
|
||||
- cAdvisor (container metrics)
|
||||
|
||||
[Learn more →](monitoring.md)
|
||||
|
||||
#### Grafana
|
||||
**Visualization dashboards**:
|
||||
|
||||
1. **Application Overview** - API metrics, queue stats, sessions
|
||||
2. **Infrastructure** - Container metrics, host resources, Redis
|
||||
3. **Alerts & SLOs** - Error budgets, SLI tracking
|
||||
|
||||
**Auto-provisioned**: Dashboards in `/configs/grafana/`
|
||||
|
||||
#### Alertmanager
|
||||
**Alert routing and notifications**:
|
||||
|
||||
**12 Alert Rules**:
|
||||
- High error rate (>5% for 5 minutes)
|
||||
- Email queue stuck (no jobs processed in 10 minutes)
|
||||
- Service down (health check fails)
|
||||
- Database connection pool exhausted
|
||||
- Redis unavailable
|
||||
- And 7 more critical conditions...
|
||||
|
||||
**Notification Channels**:
|
||||
- Gotify (self-hosted push notifications)
|
||||
- Email (SMTP)
|
||||
- Webhook (custom integrations)
|
||||
|
||||
## Request Lifecycle
|
||||
|
||||
### Example: Public Campaign Email Submission
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as User Browser
|
||||
participant Nginx
|
||||
participant Admin as Admin GUI
|
||||
participant Express as Express API
|
||||
participant DB as PostgreSQL
|
||||
participant Redis
|
||||
participant Queue as BullMQ
|
||||
participant SMTP as SMTP Server
|
||||
participant Rep as Represent API
|
||||
|
||||
User->>Nginx: Visit /campaigns/123
|
||||
Nginx->>Admin: Route to React app
|
||||
Admin->>Express: GET /api/campaigns/123 (public)
|
||||
Express->>DB: Query campaign
|
||||
DB-->>Express: Campaign data
|
||||
Express-->>Admin: Campaign JSON
|
||||
Admin-->>User: Render campaign page
|
||||
|
||||
User->>Admin: Enter postal code + submit
|
||||
Admin->>Express: POST /api/postal-codes (lookup)
|
||||
Express->>Redis: Check cache
|
||||
Redis-->>Express: Cache miss
|
||||
Express->>Rep: GET /representatives/postal-code
|
||||
Rep-->>Express: Representative list
|
||||
Express->>Redis: Cache for 7 days
|
||||
Express-->>Admin: Representatives JSON
|
||||
Admin-->>User: Show rep selection
|
||||
|
||||
User->>Admin: Select rep + write email + submit
|
||||
Admin->>Express: POST /api/responses (create)
|
||||
Express->>DB: Insert response
|
||||
Express->>Queue: Enqueue verification email
|
||||
Express->>DB: Insert campaign email record
|
||||
DB-->>Express: Response created
|
||||
Express-->>Admin: Success response
|
||||
Admin-->>User: Show success message
|
||||
|
||||
Queue->>SMTP: Send verification email
|
||||
SMTP-->>Queue: Delivery confirmed
|
||||
|
||||
User->>User: Click verification link (email)
|
||||
User->>Nginx: GET /verify-response/:token
|
||||
Nginx->>Admin: Route to React app
|
||||
Admin->>Express: POST /api/responses/:id/verify
|
||||
Express->>DB: Update response (verified=true)
|
||||
Express->>Queue: Enqueue campaign email to rep
|
||||
DB-->>Express: Response verified
|
||||
Express-->>Admin: Success
|
||||
Admin-->>User: Email sent confirmation
|
||||
|
||||
Queue->>SMTP: Send campaign email to rep
|
||||
SMTP-->>Queue: Delivery confirmed
|
||||
```
|
||||
|
||||
## Technology Decisions
|
||||
|
||||
### Why TypeScript?
|
||||
- Type safety reduces runtime errors
|
||||
- Better IDE support and autocomplete
|
||||
- Easier refactoring
|
||||
- Self-documenting code
|
||||
|
||||
### Why Prisma + Drizzle?
|
||||
- **Prisma**: Great for complex models, migrations, auto-generated types
|
||||
- **Drizzle**: Lightweight, perfect for simple media tables
|
||||
- Evaluate both ORMs in production
|
||||
|
||||
### Why Dual API?
|
||||
- **Separation of concerns**: Media ops isolated from core features
|
||||
- **Performance**: Video processing doesn't block main API
|
||||
- **Scalability**: Independent horizontal scaling
|
||||
- **Technology evaluation**: Compare Express vs Fastify
|
||||
|
||||
### Why JWT over Sessions?
|
||||
- Stateless (scales horizontally)
|
||||
- No session storage overhead
|
||||
- Works across multiple API servers
|
||||
- Standard claims (iat, exp, sub)
|
||||
|
||||
### Why BullMQ over Bull?
|
||||
- Better TypeScript support
|
||||
- Improved performance
|
||||
- Active maintenance
|
||||
- Better documentation
|
||||
|
||||
### Why PostgreSQL over NoSQL?
|
||||
- Complex relational data (campaigns, locations, users)
|
||||
- ACID transactions (critical for email queue)
|
||||
- Full-text search
|
||||
- Spatial queries (PostGIS for future geo features)
|
||||
|
||||
## Deployment Architecture
|
||||
|
||||
### Docker Compose
|
||||
All services orchestrated in `docker-compose.yml`:
|
||||
|
||||
**Profiles**:
|
||||
- `default`: Core services (postgres, redis, api, admin, nginx)
|
||||
- `monitoring`: Prometheus, Grafana, Alertmanager, exporters
|
||||
|
||||
**Networks**:
|
||||
- `changemaker-lite` bridge network
|
||||
- Service discovery via container names
|
||||
|
||||
**Volumes**:
|
||||
- PostgreSQL data persistence
|
||||
- Redis data persistence
|
||||
- Uploads directory
|
||||
- Logs directory
|
||||
|
||||
[Learn more →](../deployment/docker-compose.md)
|
||||
|
||||
### Nginx Routing
|
||||
**Subdomain-based routing**:
|
||||
|
||||
```nginx
|
||||
# Admin GUI
|
||||
server {
|
||||
server_name app.cmlite.org;
|
||||
location / {
|
||||
proxy_pass http://admin:3000;
|
||||
}
|
||||
}
|
||||
|
||||
# Express API
|
||||
server {
|
||||
server_name api.cmlite.org;
|
||||
location / {
|
||||
proxy_pass http://api:4000;
|
||||
}
|
||||
}
|
||||
|
||||
# Fastify Media API
|
||||
server {
|
||||
server_name media.cmlite.org;
|
||||
location / {
|
||||
proxy_pass http://media-api:4100;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[Learn more →](networking.md)
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Authentication Flow
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant API as Express API
|
||||
participant DB as PostgreSQL
|
||||
participant Redis
|
||||
|
||||
Client->>API: POST /api/auth/login
|
||||
API->>DB: Verify credentials
|
||||
DB-->>API: User record
|
||||
API->>DB: Create refresh token (expires 7d)
|
||||
API->>Redis: Rate limit check
|
||||
API-->>Client: Access token (15min) + Refresh token (7d)
|
||||
|
||||
Note over Client: Access token expires
|
||||
|
||||
Client->>API: POST /api/auth/refresh
|
||||
API->>DB: Validate refresh token
|
||||
DB-->>API: Token valid
|
||||
API->>DB: Rotate refresh token (transaction)
|
||||
API-->>Client: New access token + New refresh token
|
||||
```
|
||||
|
||||
**Features**:
|
||||
- bcrypt password hashing (12+ chars, complexity requirements)
|
||||
- JWT access tokens (15min expiry)
|
||||
- Refresh tokens (7 days, stored in DB, rotated on use)
|
||||
- Rate limiting (10 requests/min on auth endpoints)
|
||||
- User enumeration prevention (401 not 404)
|
||||
- RBAC middleware (requireRole, requireNonTemp)
|
||||
|
||||
[Learn more →](authentication.md)
|
||||
|
||||
### Security Layers
|
||||
1. **Network**: Nginx rate limiting, fail2ban
|
||||
2. **Application**: Input validation (Zod schemas), RBAC
|
||||
3. **Data**: Encrypted fields (ENCRYPTION_KEY), SQL injection prevention (Prisma)
|
||||
4. **Transport**: HTTPS only (production), HSTS headers
|
||||
|
||||
[Learn more →](security.md)
|
||||
|
||||
## Scalability Considerations
|
||||
|
||||
### Horizontal Scaling
|
||||
- **Stateless APIs**: JWT auth allows multiple API instances
|
||||
- **Redis-backed queues**: Share job queues across workers
|
||||
- **Database connection pooling**: Prisma manages connections
|
||||
- **Nginx load balancing**: Distribute requests across API instances
|
||||
|
||||
### Vertical Scaling
|
||||
- Increase container resources (CPU, memory)
|
||||
- Optimize database queries (indexes, query planning)
|
||||
- Redis memory limits (LRU eviction policy)
|
||||
|
||||
### Bottlenecks
|
||||
- **PostgreSQL**: Single primary (future: read replicas)
|
||||
- **Redis**: Single instance (future: Redis Cluster)
|
||||
- **File uploads**: Local disk (future: S3-compatible storage)
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
### Golden Signals
|
||||
1. **Latency**: Request duration histograms
|
||||
2. **Traffic**: Request rate by endpoint
|
||||
3. **Errors**: Error rate (5xx responses)
|
||||
4. **Saturation**: Database connections, Redis memory, queue depth
|
||||
|
||||
### SLOs (Service Level Objectives)
|
||||
- **Availability**: 99.9% uptime (8.76 hours downtime/year)
|
||||
- **Latency**: p95 < 500ms, p99 < 1000ms
|
||||
- **Error Rate**: < 0.1% (1 error per 1000 requests)
|
||||
|
||||
### Alerting Strategy
|
||||
- **Critical**: Page on-call (service down, database unavailable)
|
||||
- **Warning**: Create ticket (queue growing, elevated errors)
|
||||
- **Info**: Log only (slow query, cache miss)
|
||||
|
||||
[Learn more →](monitoring.md)
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Dual API Architecture](dual-api.md) - Express + Fastify design
|
||||
- [Database Schema](database.md) - Complete ER diagram
|
||||
- [Authentication Flow](authentication.md) - JWT security model
|
||||
- [Frontend Architecture](frontend.md) - React + Vite + Ant Design
|
||||
- [Networking](networking.md) - Nginx routing and subdomains
|
||||
- [Security Model](security.md) - Comprehensive security audit
|
||||
- [Monitoring Stack](monitoring.md) - Prometheus + Grafana + Alertmanager
|
||||
- [Data Flow](data-flow.md) - Request lifecycle examples
|
||||
|
||||
---
|
||||
|
||||
**Next**: [Set up your development environment →](../development/local-setup.md)
|
||||
@@ -1,75 +0,0 @@
|
||||
# Backend Overview
|
||||
|
||||
The Changemaker Lite V2 backend is a dual-API architecture built with TypeScript, providing a robust foundation for campaign management, mapping, and media services.
|
||||
|
||||
## Architecture
|
||||
|
||||
The backend consists of two complementary API servers:
|
||||
|
||||
- **Express API** (Port 4000) - Main V2 features with Prisma ORM + PostgreSQL
|
||||
- **Fastify Media API** (Port 4100) - Video library with Drizzle ORM (shared database)
|
||||
|
||||
Both APIs share a common PostgreSQL 16 database but use different ORM approaches for their specific needs. The Express API handles the majority of business logic, while the Fastify API is optimized for media operations.
|
||||
|
||||
## Key Components
|
||||
|
||||
### [Modules](modules/index.md)
|
||||
Backend modules provide feature-specific functionality across authentication, campaigns, locations, media, and more. Each module follows a consistent pattern with schemas, services, and routes.
|
||||
|
||||
### [Services](services/index.md)
|
||||
Shared services provide cross-cutting concerns like email delivery, geocoding, queue management, and external API integrations.
|
||||
|
||||
### [Middleware](middleware/index.md)
|
||||
Middleware components handle authentication, authorization, rate limiting, validation, and error handling across all API endpoints.
|
||||
|
||||
### [Utilities](utilities/index.md)
|
||||
Utility modules provide common functionality for spatial calculations, logging, metrics collection, and data processing.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Runtime:** Node.js 20+ with TypeScript 5.x
|
||||
- **Main Framework:** Express.js (TypeScript)
|
||||
- **Media Framework:** Fastify (TypeScript)
|
||||
- **ORMs:**
|
||||
- Prisma (main API)
|
||||
- Drizzle (media API)
|
||||
- **Database:** PostgreSQL 16
|
||||
- **Cache/Queue:** Redis 7 with BullMQ
|
||||
- **Validation:** Zod schemas
|
||||
- **Authentication:** JWT with bcrypt
|
||||
|
||||
## API Structure
|
||||
|
||||
```
|
||||
api/
|
||||
├── src/
|
||||
│ ├── server.ts # Express API entry point (port 4000)
|
||||
│ ├── media-server.ts # Fastify media API (port 4100)
|
||||
│ ├── config/
|
||||
│ │ └── env.ts # Environment configuration
|
||||
│ ├── middleware/ # Auth, RBAC, validation, rate limiting
|
||||
│ ├── modules/ # Feature modules
|
||||
│ ├── services/ # Shared services
|
||||
│ ├── types/ # TypeScript definitions
|
||||
│ └── utils/ # Helper utilities
|
||||
├── prisma/
|
||||
│ ├── schema.prisma # Main database schema (30+ models)
|
||||
│ └── migrations/ # Database migrations
|
||||
└── drizzle/ # Media API schema
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Architecture Overview](../architecture/index.md)
|
||||
- [Database Schema](../database/index.md)
|
||||
- [API Reference](../api-reference/index.md)
|
||||
- [Development Guide](../development/index.md)
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Authentication Module](modules/auth.md)
|
||||
- [Campaign Management](modules/campaigns.md)
|
||||
- [Location Services](modules/locations.md)
|
||||
- [Media Management](modules/media.md)
|
||||
- [Email Service](services/email.md)
|
||||
- [Geocoding Service](services/geocoding.md)
|
||||