Files
firefrost-operations-manual/docs/reference/architecture-decisions/firefrost-services-monorepo-decision.md
Claude (Chronicler #35) 5ffa5bba03 docs: Document Gemini consultation on firefrost-services monorepo architecture
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>
2026-03-31 20:53:41 +00:00

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 pull workflow (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:

  • WorkingDirectory set to service folder (not repo root)
  • ExecStart uses 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

  1. Create .env.example in 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
    
  2. Add .env to root .gitignore (prevents accidental commits):

    .env
    node_modules/
    
  3. On server, manually create actual .env in 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

  1. 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
    
  2. 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
    
  3. 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.example templates for documentation
  • Manual .env creation on servers

3. Service Independence

  • Services never import from other services directly
  • Shared code lives in @firefrost/shared package
  • 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 pull workflow (Chromebook compatible)
  • Minimal typing required for deployments
  • Clear, documented procedures

Anti-Patterns to Avoid

Gemini explicitly warned against:

  1. Service interdependency — Never import code directly from other services. Use @firefrost/shared instead.

  2. Over-complicated tooling — Don't reach for Lerna/Nx/Turborepo for a small team. Standard npm workspaces are sufficient.

  3. Stale systemd paths — Always update WorkingDirectory and ExecStart to match new monorepo structure.

  4. Secrets in Git — Never put actual .env files in repository, even temporarily.


Implementation Plan

Phase 1: Repository Setup

  1. Create firefrost-services repo in Gitea
  2. Set up directory structure
  3. Create root package.json with workspaces
  4. Add .gitignore and README

Phase 2: Arbiter 2.1 Migration (First Service)

  1. Move Arbiter 2.1 code into services/arbiter/
  2. Create @firefrost/shared package
  3. Update systemd configuration
  4. Test deployment workflow
  5. Tag as arbiter-v2.1.0

Phase 3: Additional Services

  1. Migrate Whitelist Manager
  2. Migrate Modpack Version Checker
  3. Tag each with appropriate versions

Phase 4: Future Development

  1. Add experimental services to future/
  2. Document service creation process
  3. Establish CI/CD if needed

Success Metrics

This architecture is successful if:

  • Single npm install deploys 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

  • 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 WorkingDirectory requirement
  • Recommended .env.example + .gitignore security 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