WHAT WAS DONE: - Created comprehensive architectural decision record - Documented Gemini's recommendation: Git tags over directory-based versions - Captured approved repository structure with npm workspaces - Documented deployment workflow including detached HEAD handling - Recorded systemd configuration requirements - Established environment variables security strategy - Documented multi-server deployment process WHY: - Preserve critical architectural decisions from Gemini consultation - Provide reference for firefrost-services implementation - Document approved patterns and anti-patterns - Support future Chroniclers and staff onboarding - Validate approach aligns with 'decades not months' philosophy FILES: - docs/reference/architecture-decisions/firefrost-services-monorepo-decision.md (new, 459 lines) Signed-off-by: The Golden Chronicler <claude@firefrostgaming.com>
11 KiB
Gemini Consultation: firefrost-services Monorepo Architecture
Architectural Decision Record
Date: March 31, 2026
Participants: Michael (The Wizard), Claude (The Golden Chronicler #50), Gemini (Architectural Consultant)
Topic: Monorepo structure and version management strategy for Firefrost Gaming backend services
Decision Status: ✅ APPROVED — Ready for implementation
Related Task: Task #87 (Arbiter 2.1 deployment will be first service in new structure)
Context
Firefrost Gaming has developed multiple custom Node.js backend services:
- Arbiter — Discord bot for subscription automation and role management
- Whitelist Manager — Pterodactyl Panel integration for Minecraft whitelist automation
- Modpack Version Checker — Monitors modpack updates across game servers
- Future services — Additional tools and integrations planned
These services currently exist as separate, unversioned deployments. We need a sustainable monorepo structure that:
- Supports remote management from RV (September 2027 goal)
- Works with simple
git pullworkflow (Chromebook accessibility requirement) - Handles service-specific versioning
- Enables code sharing between services
- Aligns with industry best practices
The Question
Should we use directory-based version archives or Git tags for version management?
Approach A: Directory-Based Versions
firefrost-services/
├── arbiter/
│ ├── current/
│ ├── v2.1/
│ ├── v2.0/
│ └── archive/
Pros: Visual history, easy comparison
Cons: Code duplication, breaks Node.js module resolution, anti-pattern
Approach B: Git Tags for Versions
firefrost-services/
├── arbiter/
│ ├── src/
│ └── package.json
With service-prefixed tags: arbiter-v2.1.0, whitelist-v1.0.0
Pros: Native Git features, clean tree, industry standard
Cons: Less visible (but proper use of Git)
Gemini's Recommendation
✅ Approach B (Git Tags) is the definitive winner.
Key Reasoning
Directory-based versioning is an anti-pattern:
- Bloats repository size
- Makes line-by-line change tracking impossible
- Breaks standard Node.js module resolution
- Not how Git is designed to work
Git tags are purpose-built for versioning:
- Clean codebase structure
- Full history via
git log - Easy rollbacks via
git checkout <tag> - Industry standard approach
Approved Architecture
Repository Structure
firefrost-services/
├── package.json # Root workspace configuration
├── package-lock.json
├── .gitignore
├── README.md # Monorepo overview
├── services/
│ ├── arbiter/
│ │ ├── src/
│ │ │ └── index.js
│ │ ├── deploy/
│ │ │ └── arbiter.service
│ │ ├── .env.example
│ │ ├── package.json
│ │ └── README.md
│ ├── whitelist-manager/
│ └── modpack-version-checker/
├── shared/
│ ├── src/
│ │ ├── utils/
│ │ └── logger/
│ ├── package.json # Named "@firefrost/shared"
│ └── README.md
└── future/ # Experimental ideas
└── README.md
Version Management Strategy
Service-Prefixed Git Tags:
- Arbiter releases:
arbiter-v2.1.0,arbiter-v2.2.0 - Whitelist Manager:
whitelist-v1.0.0,whitelist-v1.1.0 - Modpack Checker:
modpack-v1.0.0
This solves the "Service A is v2.1, Service B is v1.0" problem perfectly.
npm Workspaces Configuration
Root package.json:
{
"name": "firefrost-services",
"private": true,
"workspaces": [
"services/*",
"shared"
]
}
Shared package (shared/package.json):
{
"name": "@firefrost/shared",
"version": "1.0.0",
"main": "src/index.js"
}
Service dependency (services/arbiter/package.json):
{
"name": "@firefrost/arbiter",
"dependencies": {
"discord.js": "^14.0.0",
"@firefrost/shared": "*"
}
}
Key benefit: Single npm install at root handles all services and shared code automatically.
Deployment Workflow
Standard Deployment (Update to Newer Version)
# On the server running Arbiter
cd /var/www/firefrost-services
# Step 1: Fetch latest tags
git fetch --all --tags
# Step 2: Check out specific version
git checkout arbiter-v2.1.0
# Step 3: Install dependencies
npm install
# Step 4: Restart service
sudo systemctl restart arbiter
Rollback Workflow
# Step 1: Return to main branch
git checkout main
# Step 2: Pull latest
git pull origin main
# Step 3: Check out rollback version
git checkout arbiter-v2.0.0
# Step 4: Restart service
sudo systemctl restart arbiter
Critical: Detached HEAD State
When you git checkout <tag>, Git enters "detached HEAD" state (static snapshot).
Important: You cannot run git pull while in detached HEAD state.
To update, you must return to main branch first, pull, then checkout the new tag.
systemd Configuration
Correct configuration for monorepo services:
[Unit]
Description=Arbiter Discord Bot
After=network.target
[Service]
Type=simple
User=arbiter
WorkingDirectory=/var/www/firefrost-services/services/arbiter
ExecStart=/usr/bin/node src/index.js
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Key change from standalone deployment:
WorkingDirectoryset to service folder (not repo root)ExecStartuses relative path from WorkingDirectory- This ensures proper file resolution and environment variable loading
Environment Variables Strategy
Approved approach: .env.example committed + .gitignore for actual .env
Implementation
-
Create
.env.examplein each service directory (committed to Git):DISCORD_TOKEN=your_token_here PAYMENTER_WEBHOOK_SECRET=your_secret_here DATABASE_URL=postgresql://user:pass@host:port/db -
Add
.envto root.gitignore(prevents accidental commits):.env node_modules/ -
On server, manually create actual
.envin service directory with real credentials
Why this works:
- ✅ Secrets never committed to Git
- ✅ Configuration template visible to developers
- ✅ Industry standard approach
- ✅ Easy to document what each service needs
Multi-Server Deployment
Scenario: Arbiter runs on Server A, Whitelist Manager runs on Server B
Process
-
Clone entire repo to both servers:
# Server A (Command Center) git clone https://token@git.firefrostgaming.com/firefrost-gaming/firefrost-services.git # Server B (Panel VPS) git clone https://token@git.firefrostgaming.com/firefrost-gaming/firefrost-services.git -
Server A deployment (Arbiter only):
cd firefrost-services git checkout arbiter-v2.1.0 npm install # Create services/arbiter/.env with actual tokens sudo systemctl enable arbiter sudo systemctl start arbiter -
Server B deployment (Whitelist Manager only):
cd firefrost-services git checkout whitelist-v1.0.0 npm install # Create services/whitelist-manager/.env with actual tokens sudo systemctl enable whitelist-manager sudo systemctl start whitelist-manager
Result: Entire codebase exists on both servers, but only the specific service configured in systemd runs on each.
Key Principles Established
1. Simplicity Over Complexity
- Native npm workspaces (no Lerna, Nx, or Turborepo)
- Standard Git features (no custom versioning tools)
- Direct systemd management (no PM2 or other process managers)
2. Security First
- Secrets never committed to Git
.env.exampletemplates for documentation- Manual
.envcreation on servers
3. Service Independence
- Services never import from other services directly
- Shared code lives in
@firefrost/sharedpackage - Each service can version independently
4. Sustainability
- Structure supports decades-long maintenance
- Simple enough to manage remotely from RV
- Clear rollback procedures
5. Accessibility
- Works with
git pullworkflow (Chromebook compatible) - Minimal typing required for deployments
- Clear, documented procedures
Anti-Patterns to Avoid
Gemini explicitly warned against:
-
Service interdependency — Never import code directly from other services. Use
@firefrost/sharedinstead. -
Over-complicated tooling — Don't reach for Lerna/Nx/Turborepo for a small team. Standard npm workspaces are sufficient.
-
Stale systemd paths — Always update
WorkingDirectoryandExecStartto match new monorepo structure. -
Secrets in Git — Never put actual
.envfiles in repository, even temporarily.
Implementation Plan
Phase 1: Repository Setup
- Create
firefrost-servicesrepo in Gitea - Set up directory structure
- Create root
package.jsonwith workspaces - Add
.gitignoreand README
Phase 2: Arbiter 2.1 Migration (First Service)
- Move Arbiter 2.1 code into
services/arbiter/ - Create
@firefrost/sharedpackage - Update systemd configuration
- Test deployment workflow
- Tag as
arbiter-v2.1.0
Phase 3: Additional Services
- Migrate Whitelist Manager
- Migrate Modpack Version Checker
- Tag each with appropriate versions
Phase 4: Future Development
- Add experimental services to
future/ - Document service creation process
- Establish CI/CD if needed
Success Metrics
This architecture is successful if:
- ✅ Single
npm installdeploys all services - ✅ Rollbacks take <5 minutes
- ✅ New services can be added without restructuring
- ✅ Documentation is clear enough for future staff
- ✅ Michael can manage from RV in 2027
Related Documentation
- Task #87:
docs/tasks/arbiter-2-1-cancellation-flow/README.md(Arbiter 2.1 architecture) - Gemini Session Transcript: This document
- Operations Manual:
docs/core/infrastructure-manifest.md(will be updated post-deployment)
Acknowledgments
Gemini's contributions:
- Validated Git tags as industry standard
- Explained npm workspaces mechanics
- Caught systemd
WorkingDirectoryrequirement - Recommended
.env.example+.gitignoresecurity pattern - Documented detached HEAD workflow clearly
Chronicler #49's contributions:
- Documented Arbiter 2.1 architecture
- Identified need for monorepo structure
- Established "We Don't Kick People Out" philosophy
Final Decision
APPROVED: Proceed with Approach B (Git Tags + npm Workspaces)
Repository location: git.firefrostgaming.com/firefrost-gaming/firefrost-services
First deployment: Arbiter 2.1 (validates workflow, tests monorepo structure)
For children not yet born. 💙🔥❄️
Document Status: ✅ APPROVED
Implementation Status: Ready to begin
Next Action: Create firefrost-services repository in Gitea
Responsible: The Golden Chronicler (#50) + Michael (The Wizard)
Documented by: The Golden Chronicler (Chronicler #50)
Date: March 31, 2026
Session: Gemini architectural consultation on monorepo strategy