Compare commits
305 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4f96dfe31 | ||
|
|
b329951719 | ||
|
|
0273059148 | ||
|
|
23509d700b | ||
|
|
6bf9e41242 | ||
|
|
224579a5ea | ||
|
|
2cfda0fb2b | ||
|
|
b0b69fb172 | ||
|
|
263a7e3e47 | ||
|
|
722771e305 | ||
|
|
de916c5215 | ||
|
|
3d2b6963c1 | ||
|
|
fd064ed48c | ||
|
|
86f87e71e6 | ||
|
|
222188edf8 | ||
|
|
6efe2eaa7a | ||
|
|
e1cb136cc3 | ||
|
|
a48886e481 | ||
|
|
fe59b64722 | ||
|
|
3b7afcf2f8 | ||
|
|
166e4c8424 | ||
|
|
3773243312 | ||
|
|
f7a4e74991 | ||
|
|
c2ecc0515e | ||
|
|
1bee838c8d | ||
|
|
b1d8dde4bf | ||
|
|
b4bb0235c3 | ||
|
|
ccc7568c06 | ||
|
|
34aca32835 | ||
|
|
6856762a6b | ||
|
|
073f7ac7fb | ||
|
|
c8032cade7 | ||
|
|
d77e35a4a1 | ||
|
|
38edf84da2 | ||
|
|
72c378f136 | ||
|
|
6dc6ce0059 | ||
|
|
02af4832c0 | ||
|
|
2648133528 | ||
|
|
648f577446 | ||
|
|
a01d7b9d7f | ||
|
|
c3af0d51e4 | ||
|
|
6eac92cf88 | ||
|
|
a950694f67 | ||
|
|
441dac4a54 | ||
|
|
a193523f31 | ||
|
|
b483680bfa | ||
|
|
d16a525ffc | ||
|
|
a404410efd | ||
|
|
a346d3ef56 | ||
|
|
371a464e29 | ||
|
|
e21a348b3b | ||
|
|
98f4f5b82a | ||
|
|
b4fbfc6adf | ||
|
|
f23f71ef04 | ||
|
|
36caddc609 | ||
|
|
29f0127a60 | ||
|
|
75c9feecec | ||
|
|
4e354c1c70 | ||
|
|
953e7f27ef | ||
|
|
fe6445c023 | ||
|
|
60740386ac | ||
|
|
996b59672f | ||
|
|
e8e1f6106b | ||
|
|
d2e572661d | ||
|
|
3197361557 | ||
|
|
a65b21570d | ||
|
|
f01b0c3c91 | ||
|
|
538523832a | ||
|
|
3c68174a98 | ||
|
|
47d6644f64 | ||
|
|
c0b6bc5a22 | ||
|
|
75a59fe3c0 | ||
|
|
cef0d8465e | ||
|
|
88a3744289 | ||
|
|
27b2744786 | ||
|
|
f39b6d6c67 | ||
|
|
0caddef86d | ||
|
|
b20f342bd3 | ||
|
|
9514204bd5 | ||
|
|
7e3ffe2577 | ||
|
|
c4cbde3a0e | ||
|
|
663b0a2d9c | ||
|
|
9e3122c408 | ||
|
|
04815fe9ef | ||
|
|
03d7f87aff | ||
|
|
1783055c99 | ||
|
|
a256aa2090 | ||
|
|
28608e9fa8 | ||
|
|
7b2f3f810b | ||
|
|
60ab055754 | ||
|
|
8c3a0abe26 | ||
|
|
be6b14bd67 | ||
|
|
c0435bc1d0 | ||
|
|
dd05a41567 | ||
|
|
ec37fc819c | ||
|
|
b84958c0ee | ||
|
|
9991240eab | ||
|
|
9415c1b984 | ||
|
|
3906303754 | ||
|
|
d8f7f76a51 | ||
|
|
32e2d726bb | ||
|
|
dce0e06023 | ||
|
|
c545d2409e | ||
|
|
68ee40e89d | ||
|
|
8de93f26ce | ||
|
|
31f245a1b9 | ||
|
|
28f2c3d904 | ||
|
|
36800ae7a7 | ||
|
|
5e666e7853 | ||
|
|
87d834942a | ||
|
|
6b9cfe6976 | ||
|
|
3b64110f01 | ||
|
|
8cfbd9d277 | ||
|
|
7ef83fd0a0 | ||
|
|
962dc3bd4f | ||
|
|
bd63c3d3e4 | ||
|
|
698273d636 | ||
|
|
763e7940a6 | ||
|
|
0bdd745527 | ||
|
|
c6afaf08b3 | ||
|
|
8d2241e031 | ||
|
|
4e5ee7e49d | ||
|
|
93907d4d16 | ||
|
|
0311249938 | ||
|
|
1e9c7fbeeb | ||
|
|
00b5eadc7d | ||
|
|
521e379edd | ||
|
|
990542abc9 | ||
|
|
b297712bb7 | ||
|
|
0d6752792a | ||
|
|
ab0b328bd2 | ||
|
|
bb7728fe0d | ||
|
|
c80ead379c | ||
|
|
9d1c5d4291 | ||
|
|
9d40560ccd | ||
|
|
705960b61f | ||
|
|
d793e0d1c8 | ||
|
|
c0dfc6e186 | ||
|
|
5a515b9447 | ||
|
|
9640505c43 | ||
|
|
3893fab8d8 | ||
|
|
bd9a5fcde3 | ||
|
|
5775fc0cd6 | ||
|
|
fa5fb364c1 | ||
|
|
176cc6151f | ||
|
|
1255c315ee | ||
|
|
d66ea6212d | ||
|
|
d99dafb16b | ||
|
|
5d73c6df70 | ||
|
|
83333a47fa | ||
|
|
8f0ff1884a | ||
|
|
44fb51fcfb | ||
|
|
37dcd7fa6b | ||
|
|
9dd308f0bb | ||
|
|
a4453fe6cd | ||
|
|
44a304331f | ||
|
|
8511066d8b | ||
|
|
34cc2b7110 | ||
|
|
d34459296d | ||
|
|
c2d72cbabc | ||
|
|
e3909151a4 | ||
|
|
8872f67727 | ||
|
|
7a2a1d8dbd | ||
|
|
95fbd9e181 | ||
|
|
7c58cea3e5 | ||
|
|
b5ffc5d26d | ||
|
|
9a758ce4a5 | ||
|
|
a7b940b95d | ||
|
|
fd50009f67 | ||
|
|
2d6d4aeee7 | ||
|
|
dc73e27be3 | ||
|
|
09de0758f5 | ||
|
|
48097a9a57 | ||
|
|
11e2d7db7d | ||
|
|
2dfe0f9764 | ||
|
|
4c7e77d35e | ||
|
|
bfb9d4dba0 | ||
|
|
6ed12cdeb0 | ||
|
|
a305c7e686 | ||
|
|
91432dad98 | ||
|
|
08a52ee3cf | ||
|
|
b0aa52c2c8 | ||
|
|
3457b87aef | ||
|
|
c6d40dcf39 | ||
|
|
4ffae74a2f | ||
|
|
cffe742093 | ||
|
|
179bac2911 | ||
|
|
240a4776f6 | ||
|
|
1ca6ef4dfa | ||
|
|
d22ff8c3c9 | ||
|
|
bad8036114 | ||
|
|
541fb26e0b | ||
|
|
f8eabf3881 | ||
|
|
b156c0b63f | ||
|
|
30a1920d58 | ||
|
|
c1e17496c8 | ||
|
|
b7330653ab | ||
|
|
9777a7af9d | ||
|
|
c07c29c60c | ||
|
|
aca7669243 | ||
|
|
b8f9926e9b | ||
|
|
78179de2bc | ||
|
|
48f74e8658 | ||
|
|
075ab899c5 | ||
|
|
03974d1f13 | ||
|
|
0b61d38419 | ||
|
|
bd783093a9 | ||
|
|
4788140c2c | ||
|
|
0c7dad36ea | ||
|
|
2e3d272e26 | ||
|
|
55385634f2 | ||
|
|
a83766efb4 | ||
|
|
5c980ea681 | ||
|
|
483d12c34d | ||
|
|
7c534b53a4 | ||
|
|
918fb99b87 | ||
|
|
74f7876955 | ||
|
|
274edccf8a | ||
|
|
21b6fa9788 | ||
|
|
942335156f | ||
|
|
c0750ea2c1 | ||
|
|
34464a91af | ||
|
|
f35325a597 | ||
|
|
92e460a90b | ||
|
|
cfdd89377f | ||
|
|
be2f5eb5a5 | ||
|
|
b8ed2095ba | ||
|
|
685626f13f | ||
|
|
32b378f539 | ||
|
|
811e3046cf | ||
|
|
0acea3b95f | ||
|
|
2740dc5fd3 | ||
|
|
b4280dc630 | ||
|
|
12ffdd45f5 | ||
|
|
d227bce0a8 | ||
|
|
47a600eeb5 | ||
|
|
e30ff4d694 | ||
|
|
081bad1279 | ||
|
|
cbf5d219fc | ||
|
|
02bddc0baf | ||
|
|
ef562ef59a | ||
|
|
dc59e5c1de | ||
|
|
69200d8ac3 | ||
|
|
7ecce5da8f | ||
|
|
06f7afe25d | ||
|
|
083885c874 | ||
|
|
05d23e2dfc | ||
|
|
940840d69a | ||
|
|
f5a75d204f | ||
|
|
40cb6cef31 | ||
|
|
9752c6fd89 | ||
|
|
911f5801fc | ||
|
|
8768c6773f | ||
|
|
9e4fa13fdb | ||
|
|
b96ab1fb24 | ||
|
|
04bc2e734f | ||
|
|
b639f92da6 | ||
|
|
e99ef3b942 | ||
|
|
7cf0eec2db | ||
|
|
20b2fab994 | ||
|
|
c7c2340321 | ||
|
|
460d36c9b2 | ||
|
|
5bd4c60238 | ||
|
|
795020b55c | ||
|
|
a13d9a2c66 | ||
|
|
c2b6610e6d | ||
|
|
7d21b4290a | ||
|
|
7f990933df | ||
|
|
d121bd21f6 | ||
|
|
91eea2c5ff | ||
|
|
3666241aac | ||
|
|
567164ef7d | ||
|
|
e59ee04b03 | ||
|
|
1a3e884186 | ||
|
|
6e15a62378 | ||
|
|
05d2164dce | ||
|
|
c160647f0b | ||
|
|
d735e3d9db | ||
|
|
5a607c8c8b | ||
|
|
8e37120289 | ||
|
|
35315c2e81 | ||
|
|
845d121fb2 | ||
|
|
517ec996a9 | ||
|
|
7437b4fa7b | ||
|
|
6992790104 | ||
|
|
5c97b40237 | ||
|
|
326f6529f3 | ||
|
|
0f2ece4f88 | ||
|
|
e36b20d06e | ||
|
|
0cbea6d993 | ||
|
|
1eda8894d5 | ||
|
|
35aded99fe | ||
|
|
1a97e82ec8 | ||
|
|
bc66fec77a | ||
|
|
d9b54187ee | ||
|
|
3e4055c5dc | ||
|
|
8a56c920db | ||
|
|
22a8a3f92d | ||
|
|
3ee303244e | ||
|
|
71454946e5 | ||
|
|
5e8201fd22 | ||
|
|
2f67708fcf | ||
|
|
e23f44ad67 | ||
|
|
62ddb8b8b6 | ||
|
|
291b329067 |
177
CLAUDE.md
Normal file
177
CLAUDE.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Firefrost Gaming — Local Nitro Workspace
|
||||||
|
|
||||||
|
## What This Is
|
||||||
|
Claude Code is now running locally on Michael's Nitro laptop (AN515-55).
|
||||||
|
This repo is `firefrost-services` — all services, mods, and tools in one place.
|
||||||
|
Git remote pushes to git.firefrostgaming.com (Gitea).
|
||||||
|
|
||||||
|
## Environment Change (April 15, 2026)
|
||||||
|
Previously: Claude Code ran via SSH on Dev Panel (64.50.188.128) at `/opt/mod-builds/firefrost-services`
|
||||||
|
Now: Claude Code runs locally on Nitro at `C:\Users\mkrau\firefrost-services`
|
||||||
|
|
||||||
|
**What this means:**
|
||||||
|
- Java/Gradle builds are NOT available locally — mod compilation must be done on Dev Panel or dispatched via bridge
|
||||||
|
- All three repos are cloned locally: `firefrost-services`, `firefrost-operations-manual`, `firefrost-website`
|
||||||
|
- Workflow: edit locally → git push → Chronicler deploys to servers
|
||||||
|
- The bridge protocol works the same way
|
||||||
|
|
||||||
|
## Projects
|
||||||
|
- `services/rules-mod/` — Minecraft /rules command mod (3 versions)
|
||||||
|
- `services/modpack-version-checker/` — ModpackChecker (Python CLI + Pterodactyl Blueprint extension)
|
||||||
|
- `services/arbiter-3.0/` — Arbiter backend (Node.js/Express)
|
||||||
|
- `services/trinity-core/` — MCP gateway for SSH access
|
||||||
|
|
||||||
|
## Java Versions (for mod builds)
|
||||||
|
- Java 8: `/usr/lib/jvm/java-8-openjdk-amd64` (Forge 1.16.5)
|
||||||
|
- Java 17: `/usr/lib/jvm/java-17-openjdk-amd64` (Forge 1.20.1)
|
||||||
|
- Java 21: `/usr/lib/jvm/java-21-openjdk-amd64` (NeoForge 1.21.1)
|
||||||
|
- Switcher: `source use-java {8|17|21}`
|
||||||
|
|
||||||
|
## Gradle (for mod builds)
|
||||||
|
- Gradle 8.8: `/opt/gradle-8.8` (NeoForge + modern Forge)
|
||||||
|
- Gradle 7.6.4: `/opt/gradle-7.6.4` (Forge 1.16.5)
|
||||||
|
|
||||||
|
## Other Tools
|
||||||
|
- Python 3.12, PHP 8.3, Node.js 24
|
||||||
|
- Blueprint beta-2026-01 (Pterodactyl extension framework)
|
||||||
|
- ModpackChecker Phase 5 backup: `/root/modpackchecker_backup/`
|
||||||
|
|
||||||
|
## NextCloud Upload (for mod jars)
|
||||||
|
```
|
||||||
|
curl -u "mkrause612:APP_TOKEN" -T file.jar \
|
||||||
|
"https://downloads.firefrostgaming.com/remote.php/dav/files/mkrause612/Firefrost-Mods/..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Active Tasks
|
||||||
|
|
||||||
|
### Task 138 — Discord Rules Mod (CurseForge Release)
|
||||||
|
**Goal:** Fork the Firefrost-specific rules mod into a generic version and publish on CurseForge.
|
||||||
|
|
||||||
|
**What to do:**
|
||||||
|
- Create `services/rules-mod/discord-rules/` directory (fork from 1.21.1 code)
|
||||||
|
- Mod name: "Discord Rules" (not "Firefrost Rules")
|
||||||
|
- Remove Fire/Frost/Arcane color detection — replace with configurable header_color and body_color in TOML config
|
||||||
|
- Remove emoji-to-bracket conversion (or make it a config toggle)
|
||||||
|
- Generic branding in mods.toml, MIT license
|
||||||
|
- Compile all 3 versions (1.21.1, 1.20.1, 1.16.5)
|
||||||
|
- Draft CurseForge project page description and changelog
|
||||||
|
|
||||||
|
**Source code:** `services/rules-mod/1.21.1/`, `1.20.1/`, `1.16.5/`
|
||||||
|
|
||||||
|
## Gemini Consultations (Reference Library)
|
||||||
|
The operations manual is cloned at `C:\Users\mkrau\firefrost-operations-manual\` (local Nitro) or `/opt/mod-builds/firefrost-operations-manual/` (Dev Panel via SSH).
|
||||||
|
Gemini architectural consultations are at `docs/consultations/` in that repo.
|
||||||
|
When working on a project, check for relevant consultations BEFORE making architectural decisions.
|
||||||
|
|
||||||
|
### Key consultations by project:
|
||||||
|
|
||||||
|
**Rules Mod / Forge Ecosystem:**
|
||||||
|
- `gemini-rules-mod-compilation-2026-04-12.md` — build strategy, port guide
|
||||||
|
- `gemini-forge-ecosystem-*.md` — Forge/NeoForge ecosystem reference
|
||||||
|
- `gemini-blueprint-casing-2026-04-06.md` — Blueprint naming conventions
|
||||||
|
|
||||||
|
**ModpackChecker:**
|
||||||
|
- `gemini-modpack-version-checker-review-2026-04-01.md` — full architecture review
|
||||||
|
- `gemini-modpack-database-schema-2026-04-01.md` — database schema design
|
||||||
|
- `gemini-modpackchecker-round3-licensing-support/` — licensing and support model
|
||||||
|
- `gemini-hybrid-detection-2026-04-06.md` — hybrid auto-detection approach
|
||||||
|
|
||||||
|
To read a consultation: `cat /opt/mod-builds/firefrost-operations-manual/docs/consultations/FILENAME`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## COMMUNICATION BRIDGE PROTOCOL
|
||||||
|
|
||||||
|
You are connected to a higher-level planning AI (the Chronicler) via Git.
|
||||||
|
The Chronicler runs on claude.ai and has access to infrastructure, Gemini consultations, and the full operations manual. Michael is the messenger between you.
|
||||||
|
|
||||||
|
### 1. Triggering a Request
|
||||||
|
If you encounter an architectural decision outside your context, or fail at a task more than 3 times, STOP.
|
||||||
|
Write a request file to `docs/code-bridge/requests/` using this format:
|
||||||
|
|
||||||
|
**Filename:** `REQ-YYYY-MM-DD-short-topic.md`
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Architectural Request
|
||||||
|
|
||||||
|
**Date:** YYYY-MM-DD
|
||||||
|
**Topic:** [Brief summary of the issue]
|
||||||
|
|
||||||
|
## 1. Current State
|
||||||
|
[What you were trying to do]
|
||||||
|
|
||||||
|
## 2. The Blocker
|
||||||
|
[Exactly where you got stuck or what decision you cannot make]
|
||||||
|
|
||||||
|
## 3. Questions for Gemini
|
||||||
|
- [Question 1]
|
||||||
|
- [Question 2]
|
||||||
|
```
|
||||||
|
|
||||||
|
After writing the file, commit and push:
|
||||||
|
```bash
|
||||||
|
git add docs/code-bridge/requests/
|
||||||
|
git commit -m "bridge: Request — [topic]"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
Then tell Michael: "I've filed a consultation request. Please bring it to the Chronicler."
|
||||||
|
|
||||||
|
### 2. Checking for Responses
|
||||||
|
Before starting a new task, always check `docs/code-bridge/responses/` for new files.
|
||||||
|
Response files are named `RES-YYYY-MM-DD-short-topic.md` (matching the REQ name).
|
||||||
|
|
||||||
|
Response format:
|
||||||
|
```markdown
|
||||||
|
# Architectural Response
|
||||||
|
|
||||||
|
**Re:** [Matching REQ name]
|
||||||
|
**Date:** YYYY-MM-DD
|
||||||
|
|
||||||
|
## 1. Gemini's Verdict
|
||||||
|
[1-2 sentence summary]
|
||||||
|
|
||||||
|
## 2. Action Plan
|
||||||
|
- [ ] Step 1
|
||||||
|
- [ ] Step 2
|
||||||
|
|
||||||
|
## 3. Updated Rules/Context
|
||||||
|
[Anything new to remember going forward]
|
||||||
|
```
|
||||||
|
|
||||||
|
After reading and executing a response, move it to `docs/code-bridge/archive/`.
|
||||||
|
|
||||||
|
### 3. Updating Status
|
||||||
|
At the end of your workflow, or before handing control back to Michael, update:
|
||||||
|
`docs/code-bridge/status/ACTIVE_CONTEXT.md`
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Code Status Update
|
||||||
|
**Last Updated:** YYYY-MM-DD HH:MM
|
||||||
|
|
||||||
|
## Current Focus
|
||||||
|
[What you are actively building]
|
||||||
|
|
||||||
|
## Recently Completed
|
||||||
|
- [Task 1]
|
||||||
|
- [Task 2]
|
||||||
|
|
||||||
|
## Next Steps Pending
|
||||||
|
- [Task 3]
|
||||||
|
- [Task 4]
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit and push the status update.
|
||||||
|
|
||||||
|
### 4. Queue Management
|
||||||
|
If you check `docs/code-bridge/requests/` and see more than 2 pending requests without responses, pause all major architectural changes and wait for Michael to clear the queue.
|
||||||
|
|
||||||
|
### 5. Clarifications
|
||||||
|
If a response references something you don't understand, file a new request:
|
||||||
|
`REQ-YYYY-MM-DD-clarification-on-[topic].md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
Michael has hand surgery limitations. Small code blocks (8-10 lines).
|
||||||
|
One question at a time. Complete files over patches.
|
||||||
78
cloudflare-workers/servers-api/README.md
Normal file
78
cloudflare-workers/servers-api/README.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Servers API - Cloudflare Worker
|
||||||
|
|
||||||
|
**Purpose:** Proxies Pterodactyl Panel API to provide live server status for firefrostgaming.com
|
||||||
|
|
||||||
|
**Deployed URL:** https://servers-api.firefrostgaming.workers.dev
|
||||||
|
**Created:** April 3, 2026 by Chronicler #56 (The Velocity)
|
||||||
|
**Retrieved from Cloudflare:** April 8, 2026 by Chronicler #68
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What It Does
|
||||||
|
|
||||||
|
1. Receives request from website (firefrostgaming.com or pages.dev preview)
|
||||||
|
2. Fetches server list from Pterodactyl Panel API
|
||||||
|
3. Fetches live resource stats for each server
|
||||||
|
4. Returns JSON with server name, status (Online/Offline), player count, description
|
||||||
|
5. Caches response for 60 seconds
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Configure these in Cloudflare Workers dashboard:
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `PANEL_URL` | Pterodactyl panel URL (https://panel.firefrostgaming.com) |
|
||||||
|
| `CLIENT_API_KEY` | Pterodactyl client API key for webuser_api account |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CORS Configuration
|
||||||
|
|
||||||
|
Allowed origins:
|
||||||
|
- `https://firefrostgaming.com`
|
||||||
|
- `https://firefrost-website.pages.dev`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Response Format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"id": "abc123",
|
||||||
|
"name": "All The Mods 10",
|
||||||
|
"status": "Online",
|
||||||
|
"players": 3,
|
||||||
|
"description": "ATM10 modpack server"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
This Worker is deployed via Cloudflare dashboard. To update:
|
||||||
|
|
||||||
|
1. Edit code in Cloudflare Workers dashboard, OR
|
||||||
|
2. Use Wrangler CLI: `wrangler deploy`
|
||||||
|
|
||||||
|
**Note:** This file is the source of truth. If editing in dashboard, sync changes back here.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
| Date | Change | By |
|
||||||
|
|------|--------|-----|
|
||||||
|
| 2026-04-03 | Initial creation | Chronicler #56 (The Velocity) |
|
||||||
|
| 2026-04-08 | Added to git (was dashboard-only) | Chronicler #68 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️
|
||||||
103
cloudflare-workers/servers-api/index.js
Normal file
103
cloudflare-workers/servers-api/index.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* Firefrost Gaming - Servers API Worker
|
||||||
|
*
|
||||||
|
* Cloudflare Worker that proxies Pterodactyl Panel API
|
||||||
|
* to provide live server status for the website.
|
||||||
|
*
|
||||||
|
* Deployed: https://servers-api.firefrostgaming.workers.dev
|
||||||
|
* Created: April 3, 2026 (Chronicler #56 - The Velocity)
|
||||||
|
*
|
||||||
|
* Environment Variables Required:
|
||||||
|
* PANEL_URL - Pterodactyl panel URL (https://panel.firefrostgaming.com)
|
||||||
|
* CLIENT_API_KEY - Pterodactyl client API key
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async fetch(request, env) {
|
||||||
|
// Determine allowed origin
|
||||||
|
const origin = request.headers.get('Origin');
|
||||||
|
const allowedOrigins = [
|
||||||
|
'https://firefrostgaming.com',
|
||||||
|
'https://firefrost-website.pages.dev'
|
||||||
|
];
|
||||||
|
const allowedOrigin = allowedOrigins.includes(origin)
|
||||||
|
? origin
|
||||||
|
: 'https://firefrostgaming.com';
|
||||||
|
|
||||||
|
// Handle CORS preflight
|
||||||
|
if (request.method === 'OPTIONS') {
|
||||||
|
return new Response(null, {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': allowedOrigin,
|
||||||
|
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const PANEL_URL = env.PANEL_URL;
|
||||||
|
const API_KEY = env.CLIENT_API_KEY;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch server list
|
||||||
|
const listResponse = await fetch(`${PANEL_URL}/api/client`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${API_KEY}`,
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const listData = await listResponse.json();
|
||||||
|
|
||||||
|
if (!listData.data) throw new Error("Failed to fetch server list");
|
||||||
|
|
||||||
|
// Fetch live stats for all servers
|
||||||
|
const serverPromises = listData.data.map(async (server) => {
|
||||||
|
const id = server.attributes.identifier;
|
||||||
|
|
||||||
|
const statsResponse = await fetch(
|
||||||
|
`${PANEL_URL}/api/client/servers/${id}/resources`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${API_KEY}`,
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const stats = await statsResponse.json();
|
||||||
|
const isRunning = stats.attributes?.current_state === 'running';
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
name: server.attributes.name,
|
||||||
|
status: isRunning ? 'Online' : 'Offline',
|
||||||
|
players: isRunning ? (stats.attributes?.resources?.players || 0) : 0,
|
||||||
|
description: server.attributes.description
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalServers = await Promise.all(serverPromises);
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ servers: finalServers }), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': allowedOrigin,
|
||||||
|
'Cache-Control': 'public, s-maxage=60, max-age=60'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return new Response(JSON.stringify({
|
||||||
|
error: "Servers temporarily unreachable",
|
||||||
|
servers: []
|
||||||
|
}), {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': allowedOrigin
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Chronicler Dispatch — Auto-Detection Not Implemented
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Production Issue — Found 0 servers
|
||||||
|
|
||||||
|
`php artisan modpackchecker:check` returns "Found 0 servers with modpack configuration" on the live panel. Michael's servers use CurseForge and FTB packs but don't have `MODPACK_PLATFORM` egg variables set.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
`CheckModpackUpdates.php` only queries servers with `MODPACK_PLATFORM` egg variable. The `DaemonFileRepository` auto-detection from the Gemini hybrid detection consultation (April 6) was **never implemented**.
|
||||||
|
|
||||||
|
## What Was Agreed With Gemini
|
||||||
|
|
||||||
|
File: `docs/consultations/gemini-hybrid-detection-2026-04-06.md`
|
||||||
|
|
||||||
|
The agreed "Magic & Manual" hybrid approach:
|
||||||
|
1. Check egg variables first (fastest)
|
||||||
|
2. If missing → use `DaemonFileRepository` to read `manifest.json` (CurseForge detection via `projectID`)
|
||||||
|
3. If missing → read `modrinth.index.json` (Modrinth)
|
||||||
|
4. If found → save to DB with `detection_method = 'file'`
|
||||||
|
5. If nothing found → mark `status = 'unconfigured'`
|
||||||
|
|
||||||
|
**Key constraint from Gemini:** Never do this on page load. Background cron only. The `DaemonFileRepository` call is a network request to Wings.
|
||||||
|
|
||||||
|
## What Needs to Be Built
|
||||||
|
|
||||||
|
Update `CheckModpackUpdates.php` to:
|
||||||
|
1. Get ALL servers (not just ones with egg variables)
|
||||||
|
2. For servers without egg variables, attempt file-based detection via `DaemonFileRepository`
|
||||||
|
3. Read `manifest.json` → extract `projectID` for CurseForge
|
||||||
|
4. Read `modrinth.index.json` → extract for Modrinth
|
||||||
|
5. FTB: check for `version.json` or similar FTB manifest file
|
||||||
|
6. Save with `detection_method = 'file'`
|
||||||
|
7. Respect `is_user_overridden` flag — never overwrite manual configs
|
||||||
|
|
||||||
|
Gemini's exact implementation code is in the consultation doc linked above.
|
||||||
|
|
||||||
|
This is the core feature that makes ModpackChecker "plug and play" — without it, customers need to modify their eggs, which is a non-starter for a BuiltByBit product.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Chronicler Dispatch — current_version empty, widget shows NOT CONFIGURED
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## State of modpackchecker_servers table
|
||||||
|
|
||||||
|
```
|
||||||
|
a0efbfe8 | curseforge | up_to_date | current= | latest=FTB StoneBlock 4 1.10.0
|
||||||
|
9310d0a6 | curseforge | up_to_date | current= | latest=Society - Capital Hill - 0.20.0
|
||||||
|
82e63949 | curseforge | up_to_date | current= | latest=All the Mods 10-6.6
|
||||||
|
```
|
||||||
|
|
||||||
|
22 records created ✅ — detection working.
|
||||||
|
|
||||||
|
## Two Issues
|
||||||
|
|
||||||
|
**1. `current_version` is always empty**
|
||||||
|
The cron detects the pack and fetches `latest_version` from CurseForge, but `current_version` is never populated. Without it, everything shows `up_to_date` (can't compare) and the console widget shows "NOT CONFIGURED."
|
||||||
|
|
||||||
|
Where should `current_version` come from?
|
||||||
|
- Egg variable `MODPACK_CURRENT_VERSION`? (requires egg change — not ideal)
|
||||||
|
- A file on the server like `version.json` or `instance.json`?
|
||||||
|
- The `modpack_installations` table — does it have a version column?
|
||||||
|
|
||||||
|
**2. Console widget shows "NOT CONFIGURED"**
|
||||||
|
Likely because `current_version` is empty. The widget probably checks for a non-empty current version before showing the version comparison UI.
|
||||||
|
|
||||||
|
Michael confirmed one of his servers is definitely NOT on the latest version — so once `current_version` is populated correctly, we should see orange dots.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
35
docs/code-bridge/archive/MSG-2026-04-12-curseforge-403.md
Normal file
35
docs/code-bridge/archive/MSG-2026-04-12-curseforge-403.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Chronicler Dispatch — CurseForge 403: API key not being sent correctly
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Diagnosis
|
||||||
|
|
||||||
|
CurseForge API key IS valid and stored correctly. Tested via PHP tinker:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$key = $bp->dbGet('modpackchecker', 'curseforge_api_key');
|
||||||
|
// Returns valid key, length 60
|
||||||
|
file_get_contents('https://api.curseforge.com/v1/mods/925200', false,
|
||||||
|
stream_context_create(['http' => ['header' => 'x-api-key: ' . $key . "\r\n"]]));
|
||||||
|
// Returns valid JSON data ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
So the key works. The 403s are coming from `ModpackApiService.php` — it's not passing the key correctly to CurseForge.
|
||||||
|
|
||||||
|
## What to Check in ModpackApiService.php
|
||||||
|
|
||||||
|
1. Is it reading the key via `$bp->dbGet('modpackchecker', 'curseforge_api_key')`?
|
||||||
|
2. Is it using `x-api-key` header (NOT `Authorization: Bearer`)?
|
||||||
|
3. Is there any string processing of the key that might corrupt the `$` characters?
|
||||||
|
|
||||||
|
The CurseForge API requires `x-api-key: <key>` as the header. Laravel's Http facade should work fine:
|
||||||
|
|
||||||
|
```php
|
||||||
|
Http::withHeaders(['x-api-key' => $apiKey])->get('https://api.curseforge.com/v1/mods/' . $modpackId)
|
||||||
|
```
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
37
docs/code-bridge/archive/MSG-2026-04-12-detection-debug.md
Normal file
37
docs/code-bridge/archive/MSG-2026-04-12-detection-debug.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Chronicler Dispatch — Detection Running But Finding Nothing
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
Detection is now scanning all 22 servers ✅. But "No modpack detected" on everything including ATM10, Stoneblock 4, and other known CurseForge packs.
|
||||||
|
|
||||||
|
## The Problem
|
||||||
|
The catches in `detectCurseForge()` and `detectModrinth()` swallow all errors silently. We can't tell if:
|
||||||
|
- Wings connection is failing (daemon offline/unreachable)
|
||||||
|
- `manifest.json` doesn't exist at the root level
|
||||||
|
- `manifest.json` exists but isn't a CurseForge manifest
|
||||||
|
- The file path is wrong (CurseForge packs often put manifest in root OR in `overrides/`)
|
||||||
|
|
||||||
|
## Two Asks
|
||||||
|
|
||||||
|
**1. Add verbose error logging (temporary)**
|
||||||
|
Change the catches to log the exception message so we can see what's failing:
|
||||||
|
```php
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->line(" [debug] detectCurseForge failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Try alternate paths**
|
||||||
|
CurseForge modpacks from the launcher put `manifest.json` at the pack root. But when installed on a server, it may be at:
|
||||||
|
- `manifest.json` (root) ← current
|
||||||
|
- `config/manifest.json`
|
||||||
|
- `mods/manifest.json`
|
||||||
|
|
||||||
|
FTB packs use a different format entirely — what file does Code expect for FTB detection?
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Chronicler Dispatch — manifest.json Not Present on Live Servers
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Root Cause Found
|
||||||
|
|
||||||
|
ATM10 server root directory listing — no `manifest.json`:
|
||||||
|
```
|
||||||
|
blueprints, config, crash-reports, mods, logs, world, server.jar, run.sh...
|
||||||
|
```
|
||||||
|
|
||||||
|
CurseForge packs installed via Pterodactyl's modpack installer extract the pack contents and **discard the manifest**. The `manifest.json` only exists during the install process, not at runtime.
|
||||||
|
|
||||||
|
Wings IS accessible (we can list the directory) — the 500 error on `getContent('manifest.json')` is just Wings saying "file not found."
|
||||||
|
|
||||||
|
## What We Have Instead
|
||||||
|
|
||||||
|
The server IS running ATM10. We can see:
|
||||||
|
- `mods/` folder — contains all the mod jars
|
||||||
|
- `server.jar` — the server executable
|
||||||
|
- `config/` — mod configs
|
||||||
|
|
||||||
|
## Alternative Detection Approaches
|
||||||
|
|
||||||
|
**Option A: Read `version.json` or `instance.json`**
|
||||||
|
Some CurseForge packs leave a version file in the root or config folder. Worth checking `config/`, `kubejs/`, etc.
|
||||||
|
|
||||||
|
**Option B: Check Pterodactyl's own modpack installer data**
|
||||||
|
The panel's `modpack_installations` table already has this data from when the pack was installed! Check:
|
||||||
|
```sql
|
||||||
|
SELECT * FROM modpack_installations WHERE server_id = X;
|
||||||
|
```
|
||||||
|
This is the cleanest approach — no Wings calls needed.
|
||||||
|
|
||||||
|
**Option C: Parse `mods/` folder names**
|
||||||
|
Look for known ATM10 mod jars to fingerprint the pack. Fragile and slow.
|
||||||
|
|
||||||
|
**Recommended: Option B first.** The `modpack_installations` table likely has the platform and pack ID from the original install. Check what's in that table for Michael's servers.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
39
docs/code-bridge/archive/MSG-2026-04-12-live-panel-deploy.md
Normal file
39
docs/code-bridge/archive/MSG-2026-04-12-live-panel-deploy.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Chronicler Dispatch — Live Panel Deploy Complete + Syntax Fix Needed
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Live Panel Deploy: SUCCESS ✅
|
||||||
|
|
||||||
|
ModpackChecker is live on the production Panel VPS (45.94.168.138).
|
||||||
|
|
||||||
|
- Blueprint install: ✅
|
||||||
|
- yarn build:production: ✅ `webpack 5.103.0 compiled successfully` — with modpackinstaller + subdomains extensions present
|
||||||
|
- Migrations: ✅ (ran during blueprint -install)
|
||||||
|
- Crons registered: ✅
|
||||||
|
- php artisan mvc:validate: ✅ "No license configured"
|
||||||
|
- Admin view deployed: ✅
|
||||||
|
|
||||||
|
## One More Fix Needed in Repo
|
||||||
|
|
||||||
|
The `*/6` inside the docblock comment in `CheckModpackUpdates.php` line 16 is STILL in the repo — it was fixed in the build.sh copy for Dev Panel but the source file still has it. This breaks `php artisan` on some PHP versions.
|
||||||
|
|
||||||
|
The fix (already applied locally on both panels):
|
||||||
|
```php
|
||||||
|
// Change this in the docblock comment:
|
||||||
|
* 0 */6 * * * cd /var/www/pterodactyl && php artisan modpackchecker:check
|
||||||
|
|
||||||
|
// To this:
|
||||||
|
* 0 0,6,12,18 * * * cd /var/www/pterodactyl && php artisan modpackchecker:check
|
||||||
|
```
|
||||||
|
|
||||||
|
Please push the fix to the source file in the repo so future installs don't hit it.
|
||||||
|
|
||||||
|
## What Michael Sees Tomorrow
|
||||||
|
|
||||||
|
Visit `https://panel.firefrostgaming.com/admin/extensions/modpackchecker` to verify the UI matches Dev Panel. License section showing "Not Activated" ready for BuiltByBit order ID.
|
||||||
|
|
||||||
|
🔥❄️ — Chronicler #84, The Meridian
|
||||||
65
docs/code-bridge/archive/MSG-2026-04-12-manifest-version.md
Normal file
65
docs/code-bridge/archive/MSG-2026-04-12-manifest-version.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Chronicler Dispatch — manifest.json has version field on some servers
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
Mythcraft 5 HAS a `manifest.json` at `/home/container/manifest.json` with useful data:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifestType": "minecraftModpack",
|
||||||
|
"name": "MYTHCRAFT 5",
|
||||||
|
"version": "Update 5",
|
||||||
|
"projectID": null // at root level — projectID is nested in files[] array
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `version` field ("Update 5") is the currently installed version. This is real data
|
||||||
|
we can use for `current_version` — not just the pack ID.
|
||||||
|
|
||||||
|
## What This Means for Detection
|
||||||
|
|
||||||
|
When `detectCurseForge()` reads `manifest.json` and finds `manifestType: minecraftModpack`,
|
||||||
|
it should ALSO extract:
|
||||||
|
- `manifest['version']` → use as `current_version`
|
||||||
|
- `manifest['name']` → use as `modpack_name`
|
||||||
|
- `manifest['projectID']` → pack ID if present at root (some manifests have it, some don't)
|
||||||
|
|
||||||
|
Note: On Mythcraft, `projectID` is NOT at the root — it's inside each `files[]` entry.
|
||||||
|
The root doesn't have a project ID. The `modpack_installations` table has it (737497).
|
||||||
|
|
||||||
|
## Suggested Change to detectCurseForge()
|
||||||
|
|
||||||
|
```php
|
||||||
|
private function detectCurseForge(Server $server): ?array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$content = $this->fileRepository->getContent('manifest.json');
|
||||||
|
$manifest = json_decode($content, true);
|
||||||
|
|
||||||
|
if (is_array($manifest) && ($manifest['manifestType'] ?? '') === 'minecraftModpack') {
|
||||||
|
$projectId = $manifest['projectID'] ?? null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'platform' => 'curseforge',
|
||||||
|
'modpack_id' => $projectId ? (string) $projectId : null,
|
||||||
|
'name' => $manifest['name'] ?? null,
|
||||||
|
'installed_version' => $manifest['version'] ?? null, // ← NEW
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in `processServer()`, when detection returns `installed_version`, use it as
|
||||||
|
`current_version` instead of seeding with `latest_version`. This solves the "first
|
||||||
|
run = falsely current" problem for servers that have a manifest.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Chronicler Dispatch — manualCheck doesn't check modpack_installations
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
The console widget calls `manualCheck()` in `ModpackAPIController.php`. That method only checks:
|
||||||
|
1. Egg variables
|
||||||
|
2. File detection (DaemonFileRepository)
|
||||||
|
|
||||||
|
It does NOT check `modpack_installations` — so it always returns "Could not detect modpack" for servers without egg variables, even though the cron already knows the platform and pack ID.
|
||||||
|
|
||||||
|
## The Fix
|
||||||
|
|
||||||
|
Add `modpack_installations` as step 2 in `manualCheck()`, before file detection:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// 2. Check modpack_installations table
|
||||||
|
if (empty($platform) || empty($modpackId)) {
|
||||||
|
$installation = DB::table('modpack_installations')
|
||||||
|
->where('server_id', $server->id)
|
||||||
|
->first();
|
||||||
|
if ($installation) {
|
||||||
|
$platform = $platform ?: $installation->provider;
|
||||||
|
$modpackId = $modpackId ?: (string) $installation->modpack_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Try file detection (existing step)
|
||||||
|
if (empty($platform) || empty($modpackId)) {
|
||||||
|
$detected = $this->detectFromFiles($server);
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also — the widget only shows `latest_version` but not `current_version`. Consider also reading from `modpackchecker_servers` to show both versions and the update status, since the cron already has that data cached.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Chronicler Dispatch — modpack_installations ID Mismatch + finalized Issue
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Two Issues Found
|
||||||
|
|
||||||
|
### Issue 1: Only 5 of 50 rows have finalized=1
|
||||||
|
```
|
||||||
|
Finalized values: 1 0 1 1 1 0 0 1 0 0 1 0 0 0 0 0 0 ... (45 zeros)
|
||||||
|
```
|
||||||
|
Most installations are `finalized=0`. The query `->where('finalized', 1)` excludes them all.
|
||||||
|
|
||||||
|
**Fix:** Either remove the `finalized` filter, or change to `->where('finalized', '!=', null)` — or check what `finalized=0` means (in-progress install vs completed).
|
||||||
|
|
||||||
|
### Issue 2: server_id type mismatch
|
||||||
|
The join query (`modpack_installations.server_id = servers.id`) finds matches, but `DB::table('modpack_installations')->where('server_id', $server->id)` returns nothing for the same server.
|
||||||
|
|
||||||
|
Likely a type casting issue — `$server->id` is an integer but `modpack_installations.server_id` may be stored/cast as a string.
|
||||||
|
|
||||||
|
**Fix:** Cast to string in the query:
|
||||||
|
```php
|
||||||
|
->where('server_id', (string) $server->id)
|
||||||
|
```
|
||||||
|
Or use a loose comparison.
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Both fixes together:
|
||||||
|
```php
|
||||||
|
$installation = DB::table('modpack_installations')
|
||||||
|
->where('server_id', $server->id) // or (string) $server->id
|
||||||
|
->where('finalized', '!=', null) // or remove finalized filter entirely
|
||||||
|
->first();
|
||||||
|
```
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Chronicler Dispatch — modpack_installations has no 'id' column
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error
|
||||||
|
|
||||||
|
```
|
||||||
|
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'ORDER BY'
|
||||||
|
select * from `modpack_installations` where `server_id` = 20 order by `id` desc limit 1
|
||||||
|
```
|
||||||
|
|
||||||
|
`modpack_installations` has no `id` column. Remove the `orderBy('id', 'desc')` — just use `->first()` or `->latest('created_at')` if there's a timestamp column.
|
||||||
|
|
||||||
|
Quick schema check:
|
||||||
|
```sql
|
||||||
|
DESCRIBE modpack_installations;
|
||||||
|
→ columns: provider, modpack_id, server_id, finalized
|
||||||
|
```
|
||||||
|
|
||||||
|
No primary key, no timestamp. Just `->first()` is fine.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Chronicler Dispatch — Use modpack_installations Table (Data Confirmed)
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Answer
|
||||||
|
|
||||||
|
The `modpack_installations` table already has everything we need. No Wings calls required.
|
||||||
|
|
||||||
|
```
|
||||||
|
Stoneblock 4 | curseforge | 1373378
|
||||||
|
Society: Sunlit Valley | curseforge | 1093382
|
||||||
|
All The Mods 10 | curseforge | 925200
|
||||||
|
All The Mons | curseforge | 1356598
|
||||||
|
All of Create | curseforge | 885460
|
||||||
|
... 19 servers total with CurseForge IDs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
```sql
|
||||||
|
modpack_installations:
|
||||||
|
server_id (FK to servers.id)
|
||||||
|
provider ('curseforge', 'ftb', etc.)
|
||||||
|
modpack_id (the pack ID)
|
||||||
|
finalized (1 = install complete)
|
||||||
|
```
|
||||||
|
|
||||||
|
## What CheckModpackUpdates.php Should Do
|
||||||
|
|
||||||
|
Replace the DaemonFileRepository approach entirely with a simple DB join:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Get all servers that have modpack data
|
||||||
|
$servers = Server::join('modpack_installations', 'servers.id', '=', 'modpack_installations.server_id')
|
||||||
|
->select('servers.*', 'modpack_installations.provider', 'modpack_installations.modpack_id')
|
||||||
|
->where('modpack_installations.finalized', 1)
|
||||||
|
->get();
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use `provider` and `modpack_id` directly — no file detection needed for these servers. Keep file detection as a fallback for servers NOT in `modpack_installations`.
|
||||||
|
|
||||||
|
## Detection Priority Order
|
||||||
|
|
||||||
|
1. `modpack_installations` table (fastest, most reliable) ← ADD THIS
|
||||||
|
2. Egg variables (MODPACK_PLATFORM/MODPACK_ID) ← keep
|
||||||
|
3. DaemonFileRepository file scan ← keep as last resort fallback
|
||||||
|
|
||||||
|
This will immediately detect all 19 CurseForge servers on the live panel.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
24
docs/code-bridge/archive/MSG-2026-04-12-phase11d-approach.md
Normal file
24
docs/code-bridge/archive/MSG-2026-04-12-phase11d-approach.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Chronicler Message — Phase 11D Deployment Approach
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Great work on 11D. File the deploy request when ready.
|
||||||
|
|
||||||
|
One important note for the request: **deploy to Dev Panel (64.50.188.128) first, NOT the live Panel VPS (45.94.168.138).**
|
||||||
|
|
||||||
|
Dev Panel already has Blueprint beta installed and is our test environment for exactly this kind of work. Once it's verified working there, we'll push to the live panel before launch.
|
||||||
|
|
||||||
|
Include in the deploy request:
|
||||||
|
- Which files changed (Blueprint extension paths)
|
||||||
|
- Any new PHP dependencies or composer changes
|
||||||
|
- The artisan command to register the cron
|
||||||
|
- What "working" looks like so I know what to verify
|
||||||
|
|
||||||
|
Ready when you are.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Chronicler Dispatch — Dashboard Badge Not Rendering
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Badge still not showing — even in incognito
|
||||||
|
|
||||||
|
Build compiles, data exists in `modpackchecker_servers` for server `a49fc33c` (ATM9, update_available), but badge is not rendering on the dashboard.
|
||||||
|
|
||||||
|
## Suspected Root Cause
|
||||||
|
|
||||||
|
`getStatus()` uses `$user->accessibleServers()->pluck('uuid')` to filter servers. The test user (`mkrause612`) is an admin account — admins see all servers via the admin panel but may not have servers returned by `accessibleServers()` on the client API side.
|
||||||
|
|
||||||
|
**Please check:**
|
||||||
|
1. Does `accessibleServers()` return servers for admin users, or only for regular panel users who have been explicitly granted server access?
|
||||||
|
2. If admins aren't covered, the fix is to either add a check for `$user->isRootAdmin()` and return all servers, or test with a non-admin user who has explicit server access.
|
||||||
|
|
||||||
|
The DB data is correct — `a49fc33c | curseforge | update_available`. The API just may not be returning it for an admin user.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Chronicler Dispatch — Add Badge Build Now
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decision: Add it now
|
||||||
|
|
||||||
|
Michael wants to test on the live panel (which has additional Blueprint modules) as the real-world validation. If `yarn build:production` succeeds there, badges ship in v1.0.0 — not v1.1.0.
|
||||||
|
|
||||||
|
Go ahead and add the Node version detection + badge build to build.sh now. Use the exact implementation from the Gemini consult:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Node version detection
|
||||||
|
NODE_MAJOR_VERSION=$(node -v | grep -oE '[0-9]+' | head -1)
|
||||||
|
echo "Detected Node.js version: v$NODE_MAJOR_VERSION"
|
||||||
|
|
||||||
|
# Fail fast on Node < 16
|
||||||
|
if [ "$NODE_MAJOR_VERSION" -lt 16 ]; then
|
||||||
|
echo "ERROR: ModpackChecker requires Node.js 16 or higher."
|
||||||
|
echo "Please upgrade Node.js on your panel and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Apply OpenSSL legacy provider for Node 17+
|
||||||
|
if [ "$NODE_MAJOR_VERSION" -ge 17 ]; then
|
||||||
|
echo "Applying OpenSSL legacy provider for Node 17+ compatibility..."
|
||||||
|
export NODE_OPTIONS=--openssl-legacy-provider
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure `yarn build:production` runs at the end of build.sh after all injections.
|
||||||
|
|
||||||
|
Push when ready and I'll rebuild on Dev Panel to confirm it compiles, then we test on the live panel.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Chronicler Dispatch — Dashboard Badges: v1.1.0 Implementation Plan
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gemini Consultation Complete ✅
|
||||||
|
|
||||||
|
Full doc: `firefrost-operations-manual/docs/consultations/gemini-blueprint-css-build-2026-04-12.md`
|
||||||
|
|
||||||
|
## The Plan
|
||||||
|
|
||||||
|
**Path A wins.** Add smart Node version detection to build.sh. Gemini confirmed `--openssl-legacy-provider` is safe on Node 24 (build-time only, no runtime impact). Vanilla JS badges rejected as too brittle.
|
||||||
|
|
||||||
|
**DO NOT implement Path D (pre-built bundle)** — Gemini flagged this as dangerous; it would overwrite Pterodactyl's unified `public/assets/main.js` and break all other Blueprint extensions on the customer's panel.
|
||||||
|
|
||||||
|
## build.sh Changes for v1.1.0
|
||||||
|
|
||||||
|
Add this block near the top of build.sh, before any injection logic:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Node version detection
|
||||||
|
NODE_MAJOR_VERSION=$(node -v | grep -oE '[0-9]+' | head -1)
|
||||||
|
echo "Detected Node.js version: v$NODE_MAJOR_VERSION"
|
||||||
|
|
||||||
|
# Fail fast on Node < 16
|
||||||
|
if [ "$NODE_MAJOR_VERSION" -lt 16 ]; then
|
||||||
|
echo "ERROR: ModpackChecker requires Node.js 16 or higher."
|
||||||
|
echo "Please upgrade Node.js on your panel and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Apply OpenSSL legacy provider for Node 17+ (fixes css-loader MD4 hashing)
|
||||||
|
if [ "$NODE_MAJOR_VERSION" -ge 17 ]; then
|
||||||
|
echo "Applying OpenSSL legacy provider for Node 17+ compatibility..."
|
||||||
|
export NODE_OPTIONS=--openssl-legacy-provider
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## BuiltByBit Listing Copy (v1.1.0 section)
|
||||||
|
|
||||||
|
Gemini's suggested wording:
|
||||||
|
> "v1.1.0 includes our new interactive Dashboard Badges! This feature requires a standard panel asset compilation. Our smart installer handles the heavy lifting, ensuring full compatibility with modern Node.js environments."
|
||||||
|
|
||||||
|
## Timeline
|
||||||
|
|
||||||
|
This is post-launch work — v1.1.0 milestone. No rush. Current v1.0.0 ships clean without badges.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
48
docs/code-bridge/archive/MSG-2026-04-12-phase11d-blocker.md
Normal file
48
docs/code-bridge/archive/MSG-2026-04-12-phase11d-blocker.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Chronicler Dispatch — Phase 11D Partial Deploy
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What's Working
|
||||||
|
- Blueprint reinstalled successfully ✅
|
||||||
|
- Cron registered (`0 4 * * * php artisan mvc:validate`) ✅
|
||||||
|
- Admin page should be accessible ✅
|
||||||
|
|
||||||
|
## Blocker
|
||||||
|
`php artisan mvc:validate` fails with:
|
||||||
|
|
||||||
|
```
|
||||||
|
ERROR There are no commands defined in the "mvc" namespace.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause:** Blueprint's `requests.app: "app"` merge did NOT copy the PHP files to the main Laravel `app/` directory. Confirmed:
|
||||||
|
|
||||||
|
```
|
||||||
|
find /var/www/pterodactyl/app -name "LicenseService.php" -o -name "ValidateLicense.php"
|
||||||
|
→ (no output)
|
||||||
|
```
|
||||||
|
|
||||||
|
Files are still only in:
|
||||||
|
```
|
||||||
|
/var/www/pterodactyl/.blueprint/extensions/modpackchecker/app/Services/LicenseService.php
|
||||||
|
/var/www/pterodactyl/.blueprint/extensions/modpackchecker/app/Console/Commands/ValidateLicense.php
|
||||||
|
```
|
||||||
|
|
||||||
|
## Question for Code
|
||||||
|
|
||||||
|
Is `requests.app` in `conf.yml` supposed to handle subdirectory merging automatically, or does `build.sh` need to explicitly copy `app/Services/` and `app/Console/Commands/`?
|
||||||
|
|
||||||
|
Workaround I can apply right now if needed:
|
||||||
|
```bash
|
||||||
|
cp .blueprint/extensions/modpackchecker/app/Services/LicenseService.php app/Services/
|
||||||
|
cp .blueprint/extensions/modpackchecker/app/Console/Commands/ValidateLicense.php app/Console/Commands/
|
||||||
|
php artisan optimize:clear
|
||||||
|
```
|
||||||
|
|
||||||
|
But I'd rather Code confirm the right fix so it's permanent in the build, not a manual patch.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Chronicler Dispatch — Phase 11D Blueprint Controller Issue
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Root Cause Found
|
||||||
|
|
||||||
|
Blueprint auto-generates its own controller at:
|
||||||
|
```
|
||||||
|
app/Http/Controllers/Admin/Extensions/modpackchecker/modpackcheckerExtensionController.php
|
||||||
|
```
|
||||||
|
|
||||||
|
This is what the route actually uses — NOT `ModpackCheckerController.php` which Code wrote.
|
||||||
|
|
||||||
|
Blueprint's auto-generated controller didn't have `LicenseService`. I've patched it on Dev Panel by:
|
||||||
|
1. Adding `use Pterodactyl\Services\LicenseService;`
|
||||||
|
2. Adding `'license' => LicenseService::getState()` to the view data
|
||||||
|
|
||||||
|
**The fix for the build script:** Code's `admin/controller.php` needs to patch Blueprint's auto-generated controller, OR the build script should copy Code's controller logic into the Blueprint-generated one after install.
|
||||||
|
|
||||||
|
Refreshing the page now to verify the patch works. Will report back.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Chronicler Dispatch — Callout Box Colors
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Callout Boxes Still Wrong
|
||||||
|
|
||||||
|
The bright cyan and orange fills don't match the Pterodactyl dark theme. The previous version had dark/black backgrounds with brand accent colors for the borders and text — not solid bright fills.
|
||||||
|
|
||||||
|
**What's needed:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Dark background, accent color border + heading */
|
||||||
|
.callout.callout-info {
|
||||||
|
background: #1a1a2e; /* dark background */
|
||||||
|
border-color: #00b4d8; /* cyan accent border */
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.callout.callout-info h4 {
|
||||||
|
color: #00b4d8; /* cyan heading */
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout.callout-warning {
|
||||||
|
background: #1a1a2e;
|
||||||
|
border-color: #f59e0b; /* amber/orange accent border */
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.callout.callout-warning h4 {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use inline styles directly in the blade if that's easier. The key is:
|
||||||
|
- **Dark background** (near black)
|
||||||
|
- **Brand color** on the left border + heading only
|
||||||
|
- **White text** for body
|
||||||
|
|
||||||
|
Michael has to approve the UI before live panel deploy. This is the last visual blocker.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Chronicler Dispatch — Order ID Case Sensitivity
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 11D Working ✅
|
||||||
|
|
||||||
|
UI is live and functional:
|
||||||
|
- License section showing correctly
|
||||||
|
- "Not Activated" gray badge ✅
|
||||||
|
- PRO TIER locks on Check Interval + Discord Notifications ✅
|
||||||
|
|
||||||
|
## One Bug Found
|
||||||
|
|
||||||
|
Order ID lookup is case-sensitive. `test-001` fails, `TEST-001` works.
|
||||||
|
|
||||||
|
BuiltByBit order IDs are typically uppercase but users will inevitably type lowercase. Recommend making the lookup case-insensitive:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// In LicenseService activate() — change:
|
||||||
|
WHERE order_id = $1
|
||||||
|
|
||||||
|
// To:
|
||||||
|
WHERE UPPER(order_id) = UPPER($1)
|
||||||
|
// or
|
||||||
|
WHERE order_id ILIKE $1
|
||||||
|
```
|
||||||
|
|
||||||
|
PostgreSQL's `ILIKE` is the cleanest fix. Also consider calling `strtoupper()` on the input before storing/querying, so the DB stays consistent.
|
||||||
|
|
||||||
|
Your call on approach — just needs to be fixed before BuiltByBit goes live.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
38
docs/code-bridge/archive/MSG-2026-04-12-phase11d-contrast.md
Normal file
38
docs/code-bridge/archive/MSG-2026-04-12-phase11d-contrast.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Chronicler Dispatch — Callout Text Contrast
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UI Target Found ✅
|
||||||
|
|
||||||
|
Michael found the reference screenshot — the dark Blueprint theme with cyan info boxes and orange help box is correct and what we want.
|
||||||
|
|
||||||
|
## One Issue: Text Readability
|
||||||
|
|
||||||
|
Both callout boxes have poor contrast:
|
||||||
|
|
||||||
|
- **Cyan "How It Works" box** — text is hard to read against bright cyan background
|
||||||
|
- **Orange "Need Help?" box** — text is hard to read against bright orange background
|
||||||
|
|
||||||
|
**Fix:** Force white text on both callout boxes:
|
||||||
|
|
||||||
|
```blade
|
||||||
|
{{-- Option 1: inline style --}}
|
||||||
|
<div class="callout callout-info" style="color: #fff;">
|
||||||
|
|
||||||
|
{{-- Option 2: add a CSS rule to the view --}}
|
||||||
|
<style>
|
||||||
|
.callout.callout-info, .callout.callout-warning { color: #fff; }
|
||||||
|
.callout.callout-info a, .callout.callout-warning a { color: #fff; text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
White text reads cleanly on both cyan and orange. Links should also be white + underlined.
|
||||||
|
|
||||||
|
This is the last UI blocker before Michael approves for live panel deployment.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Chronicler Dispatch — CSS Specificity Fix Needed
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
AdminLTE's callout background is overriding our custom styles. Applied `!important` as a local fix on Dev Panel:
|
||||||
|
|
||||||
|
```css
|
||||||
|
background: #1a1a2e !important;
|
||||||
|
```
|
||||||
|
|
||||||
|
Please add `!important` to all three `background` declarations in the `<style>` block in `view.blade.php` so the fix is permanent in the repo.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Chronicler Dispatch — Phase 11D $license Variable Missing
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 500 Error on Admin Page
|
||||||
|
|
||||||
|
After manually copying the updated `view.blade.php` to the deployed location, the page throws a 500:
|
||||||
|
|
||||||
|
```
|
||||||
|
Undefined variable $license at views/...index.blade.php:66
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause:** The controller isn't passing `$license` to the view. The view references `$license['status']`, `$license['grace_expires']`, etc. but `controller.php` isn't populating it via `LicenseService::getState()`.
|
||||||
|
|
||||||
|
Please check `admin/controller.php` — does it call `LicenseService::getState()` and pass `$license` to the view? If not, that's the fix needed.
|
||||||
|
|
||||||
|
Once pushed I'll re-copy the controller and test again.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Chronicler Dispatch — settings-notices partial missing
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
New 500 after the layout rewrite:
|
||||||
|
|
||||||
|
```
|
||||||
|
View [partials.admin.settings-notices] not found.
|
||||||
|
```
|
||||||
|
|
||||||
|
This partial doesn't exist in Blueprint beta-2026-01 on Dev Panel. Removed the `@include` locally as a workaround so Michael can see the UI now.
|
||||||
|
|
||||||
|
Please remove `@include('partials/admin.settings-notices')` from view.blade.php in the repo so the fix is permanent.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Chronicler Dispatch — Phase 11D ModpackApiService Missing
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
Syntax error fixed ✅. `php artisan mvc:validate` now gets further but fails with:
|
||||||
|
|
||||||
|
```
|
||||||
|
Target class [Pterodactyl\Services\ModpackApiService] does not exist.
|
||||||
|
Class "Pterodactyl\Services\ModpackApiService" does not exist
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause:** `build.sh` copies `LicenseService.php` but not `ModpackApiService.php`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
find /var/www/pterodactyl/app -name "ModpackApiService.php"
|
||||||
|
→ (no output)
|
||||||
|
```
|
||||||
|
|
||||||
|
Please add this line to build.sh's PHP Classes section:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp "$EXT_DIR/app/Services/ModpackApiService.php" app/Services/ModpackApiService.php
|
||||||
|
echo "✓ Copied ModpackApiService.php"
|
||||||
|
```
|
||||||
|
|
||||||
|
Push and I'll re-run immediately.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Chronicler Dispatch — No Dashboard Badges
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UI Test Results
|
||||||
|
|
||||||
|
- **Admin extension page** — pending callout color fix (being tested now)
|
||||||
|
- **Server page / console widget** — ✅ working as intended, test variables work
|
||||||
|
- **Dashboard server cards** — ❌ no badge dots visible
|
||||||
|
|
||||||
|
The orange/green update status badges are not appearing on the dashboard server cards. The `UpdateBadge.tsx` component was copied correctly by build.sh, and `ServerRow.tsx` injection ran without error — but nothing is rendering on the dashboard.
|
||||||
|
|
||||||
|
Please investigate:
|
||||||
|
1. Is `UpdateBadge` actually being injected into `ServerRow.tsx`?
|
||||||
|
2. Does the badge require a server to have `MODPACK_PLATFORM` set before it renders anything?
|
||||||
|
3. Is there a JS error in the browser console?
|
||||||
|
|
||||||
|
Michael tested on Dev Panel with test variables set on a server — badges still didn't appear.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Chronicler Dispatch — Phase 11D Syntax Error
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
Build script fix is working — PHP files are now copying correctly to Laravel app/ tree. ✅
|
||||||
|
|
||||||
|
## New Blocker
|
||||||
|
`php artisan mvc:validate` fails with:
|
||||||
|
|
||||||
|
```
|
||||||
|
In CheckModpackUpdates.php line 16:
|
||||||
|
syntax error, unexpected token "*"
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a PHP version compatibility issue — the `*` token error usually means a named argument or spread operator syntax that the installed PHP version doesn't support.
|
||||||
|
|
||||||
|
Dev Panel is running **PHP 8.3**. Please check line 16 of `CheckModpackUpdates.php` for anything that might not be compatible.
|
||||||
|
|
||||||
|
Once fixed, push and I'll re-run the build script manually and test again.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Chronicler Dispatch — Phase 11D UI Styling
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UI Feedback from Michael
|
||||||
|
|
||||||
|
The current admin page renders as plain unstyled HTML — not integrated with Pterodactyl's admin UI. Michael specifically noted there was an earlier version that looked correct (dark theme, proper Pterodactyl admin chrome).
|
||||||
|
|
||||||
|
**What's needed:** `view.blade.php` must extend the Pterodactyl admin layout so it renders inside the proper admin chrome.
|
||||||
|
|
||||||
|
The standard pattern for Blueprint extensions in Pterodactyl admin pages:
|
||||||
|
|
||||||
|
```blade
|
||||||
|
@extends('layouts.admin')
|
||||||
|
@include('partials/admin.settings-notices')
|
||||||
|
|
||||||
|
@section('title')
|
||||||
|
ModpackChecker
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content-header')
|
||||||
|
<h1>ModpackChecker<small>4-Platform Modpack Version Monitoring</small></h1>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="box box-primary">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">License</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
{{-- license content here --}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
```
|
||||||
|
|
||||||
|
Use AdminLTE's `box`, `box-primary`, `form-group`, `btn btn-primary` classes — that's what the rest of the Pterodactyl admin uses.
|
||||||
|
|
||||||
|
Michael has final approval on UI before it goes to the live panel. Please push an updated view.blade.php with proper Pterodactyl admin styling.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# MSG-2026-04-13-async-error-handling
|
||||||
|
|
||||||
|
**From:** Chronicler #85
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Priority:** HIGH — required before live panel deploy
|
||||||
|
**Status:** OPEN
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Gemini flagged async error handling as a blind spot in our ErrorBoundary approach.
|
||||||
|
ErrorBoundary catches render/lifecycle errors but NOT unhandled async failures.
|
||||||
|
|
||||||
|
## Current Problems in wrapper.tsx
|
||||||
|
|
||||||
|
**1. useEffect catch (line ~49):**
|
||||||
|
```tsx
|
||||||
|
.catch(() => setData(null))
|
||||||
|
.finally(() => setLoading(false))
|
||||||
|
```
|
||||||
|
On failure: `data = null` → `if (!data) return null` → widget vanishes silently.
|
||||||
|
User sees nothing, no explanation.
|
||||||
|
|
||||||
|
**2. refresh() catch (line ~59):**
|
||||||
|
```tsx
|
||||||
|
} catch {}
|
||||||
|
```
|
||||||
|
Completely empty. Fails silently, user has no idea the refresh failed.
|
||||||
|
|
||||||
|
## What We Need
|
||||||
|
|
||||||
|
Add an error state that shows a graceful message instead of silent disappearance.
|
||||||
|
|
||||||
|
Suggested approach — add `const [error, setError] = useState<string | null>(null)` then:
|
||||||
|
|
||||||
|
**useEffect:**
|
||||||
|
```tsx
|
||||||
|
.catch(() => setError('Unable to load modpack status.'))
|
||||||
|
.finally(() => setLoading(false))
|
||||||
|
```
|
||||||
|
|
||||||
|
**refresh:**
|
||||||
|
```tsx
|
||||||
|
} catch {
|
||||||
|
setError('Check failed. Try again.');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**In render (before the main return):**
|
||||||
|
```tsx
|
||||||
|
if (error) return (
|
||||||
|
<div className="text-gray-400 text-xs px-2 py-1">{error}</div>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
This way the widget slot always shows *something* — either data, loading, error message, or the ErrorBoundary fallback. No silent disappearance.
|
||||||
|
|
||||||
|
## Gemini's Exact Words
|
||||||
|
|
||||||
|
> "If an async API call fails and you don't have a .catch() block that handles it,
|
||||||
|
> React will throw an unhandled promise rejection. Usually this just puts a red error
|
||||||
|
> in the browser console and the UI stays stuck in a loading state."
|
||||||
|
> "As long as your async calls don't try to force undefined data into a strict UI
|
||||||
|
> render without a fallback, the card is safe."
|
||||||
|
|
||||||
|
## After Code Pushes
|
||||||
|
|
||||||
|
Chronicler deploys to live panel immediately after.
|
||||||
|
|
||||||
|
---
|
||||||
|
*— Chronicler #85*
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
# Chronicler Dispatch — Build Pipeline Hardening (Gemini Consultation Complete)
|
||||||
|
|
||||||
|
**Date:** April 13, 2026
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Happened Tonight
|
||||||
|
|
||||||
|
The new v1.1.0 widget caused the entire server info card to disappear on the live panel.
|
||||||
|
Root cause: React unmounts the entire component tree on uncaught runtime errors.
|
||||||
|
We emergency-reverted. Panel is stable. Backend v1.1.0 is deployed. Frontend still on v1.0.0.
|
||||||
|
|
||||||
|
Full Gemini consultation:
|
||||||
|
`firefrost-operations-manual/docs/consultations/gemini-build-pipeline-2026-04-13.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Code Needs to Build (In Order)
|
||||||
|
|
||||||
|
### 1. ErrorBoundary.tsx — FIRST PRIORITY
|
||||||
|
|
||||||
|
Create `views/server/ErrorBoundary.tsx`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import React, { Component, ErrorInfo, ReactNode } from "react";
|
||||||
|
|
||||||
|
interface Props { children?: ReactNode; fallback?: ReactNode; }
|
||||||
|
interface State { hasError: boolean; }
|
||||||
|
|
||||||
|
class ModpackErrorBoundary extends Component<Props, State> {
|
||||||
|
public state: State = { hasError: false };
|
||||||
|
|
||||||
|
public static getDerivedStateFromError(_: Error): State {
|
||||||
|
return { hasError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||||
|
console.error("ModpackChecker Component Error:", error, errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return this.props.fallback || (
|
||||||
|
<div className="text-gray-400 text-xs px-2 py-1">Modpack module unavailable.</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModpackErrorBoundary;
|
||||||
|
```
|
||||||
|
|
||||||
|
Update build.sh injection to wrap the component:
|
||||||
|
```bash
|
||||||
|
# Instead of injecting: <ModpackVersionCard />
|
||||||
|
# Inject: <ModpackErrorBoundary><ModpackVersionCard /></ModpackErrorBoundary>
|
||||||
|
# Also add the import for ModpackErrorBoundary
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. build.sh Hardening
|
||||||
|
|
||||||
|
Add at the very top:
|
||||||
|
```bash
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
```
|
||||||
|
|
||||||
|
Add pre-flight TypeScript check BEFORE yarn build:
|
||||||
|
```bash
|
||||||
|
echo "Running pre-flight TypeScript check..."
|
||||||
|
yarn tsc --noEmit 2>&1 | head -20 || {
|
||||||
|
echo "❌ TypeScript validation failed — aborting build"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "✓ TypeScript check passed"
|
||||||
|
```
|
||||||
|
|
||||||
|
Fix Blueprint controller detection (use find instead of hardcoded path):
|
||||||
|
```bash
|
||||||
|
BP_CONTROLLER=$(find app/Http/Controllers/Admin/Extensions/modpackchecker -name "*Controller.php" | head -n 1)
|
||||||
|
if [ -n "$BP_CONTROLLER" ]; then
|
||||||
|
cp "$EXT_DIR/admin/controller.php" "$BP_CONTROLLER"
|
||||||
|
echo "✓ Overwrote Blueprint controller"
|
||||||
|
else
|
||||||
|
echo "❌ Blueprint controller not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Pre-commit PHP Lint Hook
|
||||||
|
|
||||||
|
Add to repo as `scripts/pre-commit-hook.sh` (Chronicler will install manually):
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep ".php$")
|
||||||
|
for FILE in $FILES; do
|
||||||
|
php -l "$FILE" || { echo "Aborting commit: PHP syntax error in $FILE"; exit 1; }
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
```
|
||||||
|
|
||||||
|
This permanently kills the */6 docblock problem at the source.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment Order After Code Pushes
|
||||||
|
|
||||||
|
1. Dev Panel first — test the ErrorBoundary catches gracefully
|
||||||
|
2. Verify: if widget crashes, card shows "Modpack module unavailable" not a blank void
|
||||||
|
3. Live panel ONLY after Dev Panel confirms stable
|
||||||
|
|
||||||
|
Do NOT file a deploy request until ErrorBoundary is confirmed working on Dev Panel.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
44
docs/code-bridge/archive/MSG-2026-04-13-buildsh-clobber.md
Normal file
44
docs/code-bridge/archive/MSG-2026-04-13-buildsh-clobber.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Chronicler Dispatch — build.sh keeps clobbering deployed PHP files
|
||||||
|
|
||||||
|
**Date:** April 13, 2026
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critical Issue
|
||||||
|
|
||||||
|
Every time `blueprint -install modpackchecker` or `bash build.sh` runs, it copies PHP files from `.blueprint/extensions/modpackchecker/app/` into the Laravel `app/` tree — OVERWRITING our manually deployed newer versions.
|
||||||
|
|
||||||
|
The sequence that breaks things:
|
||||||
|
1. We copy new PHP files from repo to `/var/www/pterodactyl/app/`
|
||||||
|
2. Blueprint install or build.sh runs
|
||||||
|
3. build.sh copies OLD PHP from `.blueprint/extensions/` back over our files
|
||||||
|
4. Everything reverts
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
The `.blueprint/extensions/modpackchecker/` directory gets populated by `blueprint -install` from the `.blueprint` package, not from our repo directly. So it has whatever version was last installed — not the latest.
|
||||||
|
|
||||||
|
## The Fix
|
||||||
|
|
||||||
|
build.sh should NOT copy PHP files. PHP files should only be deployed by the Chronicler manually. Remove these lines from build.sh:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# REMOVE these from build.sh:
|
||||||
|
cp "$EXT_DIR/app/Services/LicenseService.php" app/Services/LicenseService.php
|
||||||
|
cp "$EXT_DIR/app/Services/ModpackApiService.php" app/Services/ModpackApiService.php
|
||||||
|
cp "$EXT_DIR/app/Console/Commands/ValidateLicense.php" ...
|
||||||
|
cp "$EXT_DIR/app/Console/Commands/CheckModpackUpdates.php" ...
|
||||||
|
cp "$EXT_DIR/app/Http/Controllers/ModpackAPIController.php" ...
|
||||||
|
# Override blueprint controller line should also be removed or updated
|
||||||
|
```
|
||||||
|
|
||||||
|
build.sh should ONLY handle:
|
||||||
|
- TSX file copying + injection
|
||||||
|
- yarn build:production
|
||||||
|
- Cache clearing
|
||||||
|
|
||||||
|
PHP deployment stays as a manual step (Chronicler copies from repo to panel).
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# MSG-2026-04-13-dev-panel-manual-changes
|
||||||
|
|
||||||
|
**From:** Chronicler #85
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Priority:** HIGH — repo does not reflect Dev Panel state
|
||||||
|
**Status:** OPEN
|
||||||
|
|
||||||
|
## What I Did on Dev Panel That's Not in the Repo
|
||||||
|
|
||||||
|
During Dev Panel validation I made several manual fixes that exposed gaps in how
|
||||||
|
Blueprint handles the extension package. These need to be properly encoded in the
|
||||||
|
repo so live panel deploy and future installs work correctly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. AfterInformation.tsx — ErrorBoundary wrapper added manually
|
||||||
|
|
||||||
|
Blueprint's `blueprint -install` skipped the ErrorBoundary injection because
|
||||||
|
`ModpackVersionCard` was already present (the "already present" check in build.sh
|
||||||
|
only looks for the import, not whether it's wrapped).
|
||||||
|
|
||||||
|
I manually patched the file directly:
|
||||||
|
```tsx
|
||||||
|
// Before (on server):
|
||||||
|
<ModpackVersionCard />
|
||||||
|
|
||||||
|
// After (manually patched):
|
||||||
|
<ModpackErrorBoundary><ModpackVersionCard /></ModpackErrorBoundary>
|
||||||
|
```
|
||||||
|
|
||||||
|
**The build.sh injection logic needs to handle the update case** — if
|
||||||
|
`ModpackVersionCard` is present but NOT wrapped in `ModpackErrorBoundary`,
|
||||||
|
it should add the wrapper. Currently it just skips entirely.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. wrapper.tsx never replaced ModpackVersionCard.tsx via build.sh
|
||||||
|
|
||||||
|
build.sh copies `wrapper.tsx → ModpackVersionCard.tsx` but skips if
|
||||||
|
`ModpackVersionCard` import already exists in `AfterInformation.tsx`.
|
||||||
|
The "already present" check is too broad — it prevents the component file
|
||||||
|
itself from being updated on subsequent deploys.
|
||||||
|
|
||||||
|
I manually copied: `wrapper.tsx → resources/scripts/components/server/ModpackVersionCard.tsx`
|
||||||
|
|
||||||
|
**build.sh needs to always copy wrapper.tsx regardless of injection state.**
|
||||||
|
Separate the "copy the component file" step from the "inject into AfterInformation" step.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Blueprint extension package was stale
|
||||||
|
|
||||||
|
After `blueprint -install`, Blueprint restores files from its own internal
|
||||||
|
package — overwriting anything we deployed. I had to manually copy updated files
|
||||||
|
INTO the Blueprint extension package:
|
||||||
|
|
||||||
|
```
|
||||||
|
.blueprint/extensions/modpackchecker/build.sh ← copied v1.1.0
|
||||||
|
.blueprint/extensions/modpackchecker/views/server/ErrorBoundary.tsx ← copied
|
||||||
|
.blueprint/extensions/modpackchecker/views/server/wrapper.tsx ← copied
|
||||||
|
.blueprint/extensions/modpackchecker/routes/client.php ← copied
|
||||||
|
.blueprint/extensions/modpackchecker/routers/client.php ← copied
|
||||||
|
```
|
||||||
|
|
||||||
|
**The `.conf` Blueprint archive needs to be rebuilt to include all v1.1.0 files.**
|
||||||
|
Otherwise every `blueprint -install` will regress to stale files and require
|
||||||
|
manual re-copying after each install.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Routes deployed to 3 locations manually
|
||||||
|
|
||||||
|
`routes/client.php` exists in the repo but Blueprint wasn't picking it up.
|
||||||
|
Had to copy to all three locations Blueprint uses:
|
||||||
|
```
|
||||||
|
.blueprint/extensions/modpackchecker/routes/client.php
|
||||||
|
.blueprint/extensions/modpackchecker/routers/client.php
|
||||||
|
routes/blueprint/client/modpackchecker.php
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary of What Needs to Be Fixed in the Repo
|
||||||
|
|
||||||
|
1. **build.sh** — separate component copy from injection; always copy wrapper.tsx;
|
||||||
|
add ErrorBoundary wrapper detection for update path
|
||||||
|
2. **Blueprint .conf archive** — rebuild to include v1.1.0 files so fresh installs
|
||||||
|
don't regress (this may require Blueprint-specific tooling)
|
||||||
|
3. **Deployment docs** — document that after `blueprint -install`, manually copy
|
||||||
|
wrapper.tsx and routes until the .conf is rebuilt
|
||||||
|
|
||||||
|
## What's Currently Working on Dev Panel
|
||||||
|
|
||||||
|
Despite all of the above, Dev Panel IS working correctly right now because I
|
||||||
|
manually fixed everything. The widget loads zero-click, ErrorBoundary is in place,
|
||||||
|
routes are registered. But it's fragile — another `blueprint -install` would
|
||||||
|
regress it.
|
||||||
|
|
||||||
|
---
|
||||||
|
*— Chronicler #85*
|
||||||
22
docs/code-bridge/archive/MSG-2026-04-13-docblock-final.md
Normal file
22
docs/code-bridge/archive/MSG-2026-04-13-docblock-final.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Chronicler Dispatch — Fix */6 in repo PERMANENTLY
|
||||||
|
|
||||||
|
**Date:** April 13, 2026
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The `*/6` docblock comment in `CheckModpackUpdates.php` line 16 keeps coming back because build.sh copies from the source file which still has it.
|
||||||
|
|
||||||
|
Please fix it in the SOURCE file in the repo — change:
|
||||||
|
```
|
||||||
|
0 */6 * * *
|
||||||
|
```
|
||||||
|
to:
|
||||||
|
```
|
||||||
|
0 0,6,12,18 * * *
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the THIRD time I've had to patch it on the server. Fix it at the source so it stops coming back.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
15
docs/code-bridge/archive/MSG-2026-04-13-keep-going-p2.md
Normal file
15
docs/code-bridge/archive/MSG-2026-04-13-keep-going-p2.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Chronicler Dispatch — Keep Going with Priority 2
|
||||||
|
|
||||||
|
**Date:** April 13, 2026
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Keep going. Deploy and test can wait — consolidate when more pieces are ready.
|
||||||
|
|
||||||
|
Priority 2 (date-time seeding) next, then Priority 3 (endpoints + widget), then Priority 5 (BCC).
|
||||||
|
|
||||||
|
One consolidated deploy when they're all done.
|
||||||
|
|
||||||
|
*— Chronicler #84*
|
||||||
22
docs/code-bridge/archive/MSG-2026-04-13-keep-going-p3b.md
Normal file
22
docs/code-bridge/archive/MSG-2026-04-13-keep-going-p3b.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Chronicler Dispatch — Keep Going with Priority 3b (Widget TSX)
|
||||||
|
|
||||||
|
**Date:** April 13, 2026
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Keep going. Full stack first, one consolidated deploy and test at the end.
|
||||||
|
|
||||||
|
Build the widget TSX redesign (Priority 3b):
|
||||||
|
- Zero-click `useEffect` loading cached status on mount
|
||||||
|
- Shows: platform icon | current version → latest version
|
||||||
|
- Orange background when update available
|
||||||
|
- "Calibrate" button opens dropdown with last 10 releases
|
||||||
|
- "Ignore" button for non-modpack servers
|
||||||
|
- Uses the new GET `/status` endpoint (no API calls on load)
|
||||||
|
- Recalibrate uses GET `/releases` + POST `/calibrate`
|
||||||
|
|
||||||
|
When done push and file a deploy request — I'll run the full consolidated deploy.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# MSG-2026-04-13-stale-installer-versions
|
||||||
|
|
||||||
|
**From:** Chronicler #85
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Priority:** HIGH — pending_calibration never triggers on live panel
|
||||||
|
**Status:** OPEN
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The cron's detection chain has a fallback that reads `current_version` from the
|
||||||
|
existing DB row. For installer-method servers, this means stale full installer
|
||||||
|
filenames (e.g. `DeceasedCraft_Beta_DH_Edition_5.10.16`) persist indefinitely —
|
||||||
|
the cron finds them in the DB, uses them, and never reaches `pending_calibration`.
|
||||||
|
|
||||||
|
## Live Panel DB Evidence
|
||||||
|
|
||||||
|
```
|
||||||
|
DeceasedCraft: current: DeceasedCraft_Beta_DH_Edition_5.10.16 ← stale filename
|
||||||
|
FTB Stoneblock: current: FTB StoneBlock 4 1.10.0 ← stale filename
|
||||||
|
ATM10 Sky: current: ATM10 To the Sky-2.0.2 ← stale filename
|
||||||
|
```
|
||||||
|
|
||||||
|
None of these are showing `pending_calibration` — they're all `update_available`
|
||||||
|
because the string comparison fails between the full filename and the clean
|
||||||
|
semver from the API.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
In `checkVersion()`, the DB fallback runs before pending_calibration:
|
||||||
|
```php
|
||||||
|
if (empty($currentVersion)) {
|
||||||
|
$currentVersion = $existing->current_version ?? null; // ← picks up stale value
|
||||||
|
}
|
||||||
|
// ...never reaches pending_calibration because $currentVersion is not empty
|
||||||
|
```
|
||||||
|
|
||||||
|
## The Fix
|
||||||
|
|
||||||
|
For `installer` detection method, the DB fallback should be skipped OR the
|
||||||
|
stale value should be validated before use.
|
||||||
|
|
||||||
|
**Option A (recommended):** For installer-method servers, only use DB value
|
||||||
|
if `current_file_id` is also set. If there's a current_version string but no
|
||||||
|
file_id, treat it as unvalidated and continue to Truth File / pending_calibration:
|
||||||
|
|
||||||
|
```php
|
||||||
|
if (empty($currentVersion)) {
|
||||||
|
if ($method !== 'installer' || !empty($existing->current_file_id)) {
|
||||||
|
$currentVersion = $existing->current_version ?? null;
|
||||||
|
$currentFileId = $existing->current_file_id ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B:** Detect "dirty" version strings — if current_version contains
|
||||||
|
spaces or looks like a full filename (contains the modpack name), treat as
|
||||||
|
unvalidated.
|
||||||
|
|
||||||
|
Option A is cleaner and more reliable.
|
||||||
|
|
||||||
|
## Expected Behavior After Fix
|
||||||
|
|
||||||
|
- Servers with stale installer filenames → Truth File check → not found →
|
||||||
|
`pending_calibration`
|
||||||
|
- Servers that have been calibrated (have `current_file_id`) → use DB value
|
||||||
|
→ normal comparison
|
||||||
|
- Servers where manifest.json was found → Truth File written → file ID
|
||||||
|
comparison going forward
|
||||||
|
|
||||||
|
## Also — One Data Cleanup Needed
|
||||||
|
|
||||||
|
After the fix, existing stale rows need to be cleared so the cron re-evaluates
|
||||||
|
them. Either:
|
||||||
|
1. Add a one-time migration that nulls `current_version` where `current_file_id`
|
||||||
|
is null and `detection_method = 'installer'`
|
||||||
|
2. Or document a manual SQL command Chronicler can run
|
||||||
|
|
||||||
|
---
|
||||||
|
*— Chronicler #85*
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
# MSG-2026-04-13-truth-file-version-detection
|
||||||
|
|
||||||
|
**From:** Chronicler #85
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Priority:** HIGH — needed before BuiltByBit listings, ideally before April 15
|
||||||
|
**Status:** OPEN
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
After 3 rounds of Gemini consultation and live Wings API testing, we have a
|
||||||
|
complete architectural decision for version detection. Here's everything you
|
||||||
|
need to implement it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What We Proved via Live Testing
|
||||||
|
|
||||||
|
- `DaemonFileRepository::getContent()` and `putContent()` both work from Panel
|
||||||
|
PHP context (confirmed via tinker on live panel)
|
||||||
|
- `manifest.json` does NOT survive Pterodactyl modpack installation on any of
|
||||||
|
our 22 servers — installer discards it
|
||||||
|
- `modpack_installations` table has no version info — only provider + modpack_id
|
||||||
|
- Wings directory listing works — we can read the server filesystem
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Fix: Truth File Strategy
|
||||||
|
|
||||||
|
### New Detection Order (replaces current fall-through logic)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. DB: current_file_id present? → skip to comparison
|
||||||
|
2. Wings: read /.modpack-checker.json → parse file_id → store in DB → compare
|
||||||
|
3. Wings: read /manifest.json → parse file_id → write Truth File → store in DB → compare
|
||||||
|
4. Nothing found → set status = 'pending_calibration' → STOP (never seed from latest)
|
||||||
|
```
|
||||||
|
|
||||||
|
**CRITICAL RULE: Never seed current_version from latest API result.**
|
||||||
|
If we don't know the installed version, we say we don't know. Store `null` /
|
||||||
|
`pending_calibration`, never assume latest = installed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Truth File
|
||||||
|
|
||||||
|
Write `.modpack-checker.json` to server root via `DaemonFileRepository::putContent()`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"extension": "modpackchecker",
|
||||||
|
"project_id": "490660",
|
||||||
|
"file_id": "7097953",
|
||||||
|
"version": "5.10.15",
|
||||||
|
"calibrated_at": "2026-04-13T05:00:00+00:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Write the Truth File in TWO scenarios:**
|
||||||
|
1. When admin calibrates (picks version from dropdown)
|
||||||
|
2. When ANY detection method successfully finds the version (manifest.json etc.)
|
||||||
|
|
||||||
|
This makes detection self-healing — once tracked by any method, always tracked
|
||||||
|
even if the original source file disappears in a future update.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## New DB Status Value
|
||||||
|
|
||||||
|
Add `pending_calibration` to the status enum in `modpackchecker_servers`:
|
||||||
|
```sql
|
||||||
|
ALTER TABLE modpackchecker_servers
|
||||||
|
MODIFY COLUMN status ENUM(
|
||||||
|
'up_to_date','update_available','error','unknown','pending_calibration'
|
||||||
|
) NOT NULL DEFAULT 'unknown';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files to Change
|
||||||
|
|
||||||
|
### 1. `app/Console/Commands/CheckModpackUpdates.php`
|
||||||
|
|
||||||
|
In `checkVersion()` — replace the seeding fallback with Truth File logic:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// After existing detection chain (egg var, existing DB)...
|
||||||
|
|
||||||
|
// NEW: Check for Truth File on server filesystem
|
||||||
|
if (empty($currentVersion)) {
|
||||||
|
try {
|
||||||
|
$repo = app(\Pterodactyl\Repositories\Wings\DaemonFileRepository::class)
|
||||||
|
->setServer($server);
|
||||||
|
$truthFile = json_decode($repo->getContent('/.modpack-checker.json'), true);
|
||||||
|
if (!empty($truthFile['file_id'])) {
|
||||||
|
$currentVersion = $truthFile['version'] ?? null;
|
||||||
|
$currentFileId = $truthFile['file_id'];
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// File doesn't exist — continue to next check
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW: Check for legacy manifest.json
|
||||||
|
if (empty($currentVersion)) {
|
||||||
|
try {
|
||||||
|
$repo = $repo ?? app(\Pterodactyl\Repositories\Wings\DaemonFileRepository::class)
|
||||||
|
->setServer($server);
|
||||||
|
$manifest = json_decode($repo->getContent('/manifest.json'), true);
|
||||||
|
if (!empty($manifest['files'][0]['fileID'])) {
|
||||||
|
$currentFileId = (string) $manifest['files'][0]['fileID'];
|
||||||
|
// Write Truth File immediately so it persists
|
||||||
|
$this->writeTruthFile($server, $modpackId, $currentFileId, null);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// File doesn't exist — continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// REMOVE the seeding fallback entirely. Replace with:
|
||||||
|
if (empty($currentVersion) && empty($currentFileId)) {
|
||||||
|
$this->updateDatabase($server, [
|
||||||
|
'platform' => $platform,
|
||||||
|
'modpack_id' => $modpackId,
|
||||||
|
'modpack_name' => $latestData['name'],
|
||||||
|
'status' => 'pending_calibration',
|
||||||
|
'detection_method' => $method,
|
||||||
|
'last_checked' => now(),
|
||||||
|
]);
|
||||||
|
$this->info(" ⏳ PENDING: {$latestData['name']} — calibration required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a `writeTruthFile()` helper method:
|
||||||
|
```php
|
||||||
|
private function writeTruthFile(Server $server, string $projectId,
|
||||||
|
string $fileId, ?string $version): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$repo = app(\Pterodactyl\Repositories\Wings\DaemonFileRepository::class)
|
||||||
|
->setServer($server);
|
||||||
|
$repo->putContent('/.modpack-checker.json', json_encode([
|
||||||
|
'extension' => 'modpackchecker',
|
||||||
|
'project_id' => $projectId,
|
||||||
|
'file_id' => $fileId,
|
||||||
|
'version' => $version,
|
||||||
|
'calibrated_at' => now()->toIso8601String(),
|
||||||
|
], JSON_PRETTY_PRINT));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Non-fatal — log and continue
|
||||||
|
\Log::warning('[MVC] Could not write Truth File: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `app/Http/Controllers/ModpackAPIController.php`
|
||||||
|
|
||||||
|
In the `calibrate()` method — after storing the file_id in DB, write the Truth File:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// After DB update in calibrate()...
|
||||||
|
$this->writeTruthFileForServer($server, $modpackId, $fileId, $version);
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the same `writeTruthFile` logic (or extract to a shared service).
|
||||||
|
|
||||||
|
### 3. `views/server/wrapper.tsx`
|
||||||
|
|
||||||
|
Add `pending_calibration` handling. When `data.configured === false` and
|
||||||
|
status is pending, show calibration prompt instead of empty/error state:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// After error state check, before main render...
|
||||||
|
if (!data.configured && !data.update_available) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(
|
||||||
|
'rounded shadow-lg bg-gray-600',
|
||||||
|
'col-span-3 md:col-span-2 lg:col-span-6',
|
||||||
|
'px-3 py-2 md:p-3 lg:p-4 mt-2'
|
||||||
|
)}>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<FontAwesomeIcon icon={faCube} className="w-4 h-4 mr-2 text-gray-400" />
|
||||||
|
<span className="text-gray-400 text-sm">
|
||||||
|
{data.modpack_name || 'Modpack'} — Version unknown
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button onClick={openCalibrate}
|
||||||
|
className="text-xs px-3 py-1 bg-cyan-600 hover:bg-cyan-500
|
||||||
|
text-white rounded transition-colors">
|
||||||
|
Identify Version
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Ignore toggle — Muted Card (from previous Gemini consultation)
|
||||||
|
|
||||||
|
Replace `if (data.is_ignored) return null;` with:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
if (data.is_ignored) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(
|
||||||
|
'rounded shadow-lg bg-gray-600 opacity-50',
|
||||||
|
'col-span-3 md:col-span-2 lg:col-span-6',
|
||||||
|
'px-3 py-2 md:p-3 lg:p-4 mt-2'
|
||||||
|
)}>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<FontAwesomeIcon icon={faCube} className="w-4 h-4 mr-2 text-gray-400" />
|
||||||
|
<span className="text-gray-400 text-sm">
|
||||||
|
{data.modpack_name || 'Modpack'} — Updates ignored
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button onClick={toggleIgnore}
|
||||||
|
className="text-xs px-3 py-1 bg-gray-700 hover:bg-gray-600
|
||||||
|
text-gray-200 rounded transition-colors">
|
||||||
|
Resume
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Needed
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE modpackchecker_servers
|
||||||
|
MODIFY COLUMN status ENUM(
|
||||||
|
'up_to_date','update_available','error','unknown','pending_calibration'
|
||||||
|
) NOT NULL DEFAULT 'unknown';
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to a new migration file:
|
||||||
|
`database/migrations/2026_04_13_000002_add_pending_calibration_status.php`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Post-Launch Enhancement (NOT for April 15)
|
||||||
|
|
||||||
|
Log parsing at server restart — possible future detection method. Every modpack
|
||||||
|
formats startup logs differently, too brittle for now. Flag as Task for after
|
||||||
|
launch.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
- [ ] Server with no Truth File → shows "Identify Version" button
|
||||||
|
- [ ] Admin selects version → Truth File written to server root → DB updated
|
||||||
|
- [ ] Cron reads Truth File on next run → correct status shown
|
||||||
|
- [ ] Server shows update available after Truth File written with old version
|
||||||
|
- [ ] Ignore toggle → muted card with Resume button (not vanish)
|
||||||
|
- [ ] Resume → normal card restored
|
||||||
|
- [ ] Never seeds current_version from latest API result
|
||||||
|
|
||||||
|
---
|
||||||
|
*— Chronicler #85*
|
||||||
236
docs/code-bridge/archive/MSG-2026-04-13-v110-architecture.md
Normal file
236
docs/code-bridge/archive/MSG-2026-04-13-v110-architecture.md
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
# Chronicler Dispatch — v1.1.0 Full Architecture Plan (Gemini Consultation Complete)
|
||||||
|
|
||||||
|
**Date:** April 13, 2026
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Gemini consultation complete. Michael has reviewed and approved the plan.
|
||||||
|
Full consultation: `firefrost-operations-manual/docs/consultations/gemini-modpackchecker-ux-overhaul-2026-04-12.md`
|
||||||
|
|
||||||
|
No time pressure — Michael wants to get this right. These are v1.1.0 priorities in order.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority 1: File ID Comparison (Foundation)
|
||||||
|
|
||||||
|
**The problem:** We're comparing messy display name strings ("ATM10-6.5" vs "ATM10-6.6"). Unreliable and ugly.
|
||||||
|
|
||||||
|
**Gemini's fix:** Use sequential File IDs from CurseForge/Modrinth. `latest_file_id > current_file_id` = update available. Clean, reliable, platform-agnostic.
|
||||||
|
|
||||||
|
**Schema migration needed:**
|
||||||
|
```sql
|
||||||
|
ALTER TABLE modpackchecker_servers
|
||||||
|
ADD COLUMN current_file_id VARCHAR(64) NULL,
|
||||||
|
ADD COLUMN latest_file_id VARCHAR(64) NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
**API changes:**
|
||||||
|
- `ModpackApiService::fetchLatestVersion()` should also return `file_id`
|
||||||
|
- CurseForge: use the file's `id` field
|
||||||
|
- Modrinth: use the version's `id` field
|
||||||
|
- FTB: use the version `id`
|
||||||
|
- Technic: use the build number
|
||||||
|
|
||||||
|
**Version comparison logic:**
|
||||||
|
```php
|
||||||
|
// Prefer file ID comparison if available
|
||||||
|
if ($currentFileId && $latestFileId) {
|
||||||
|
$updateAvailable = $latestFileId !== $currentFileId;
|
||||||
|
} else {
|
||||||
|
// Fallback to string comparison
|
||||||
|
$updateAvailable = $currentVersion !== $latestVersion;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority 2: Date-Time Seeding Heuristic
|
||||||
|
|
||||||
|
**The problem:** First run seeds `current_version = latest_version`, which is wrong for servers that have been running old versions.
|
||||||
|
|
||||||
|
**Gemini's fix:** On first detection, fetch the platform's file history. Find the release closest to (but not after) `modpack_installations.created_at`. That's the assumed current version.
|
||||||
|
|
||||||
|
```php
|
||||||
|
private function seedCurrentVersion(string $platform, string $modpackId, ?string $installDate): array
|
||||||
|
{
|
||||||
|
if (!$installDate) {
|
||||||
|
// No install date — fall back to latest
|
||||||
|
return $this->apiService->fetchLatestVersion($platform, $modpackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$allFiles = $this->apiService->fetchFileHistory($platform, $modpackId);
|
||||||
|
|
||||||
|
$assumedCurrent = collect($allFiles)
|
||||||
|
->filter(fn($f) => $f['releaseDate'] <= $installDate)
|
||||||
|
->sortByDesc('releaseDate')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return $assumedCurrent ?? $this->apiService->fetchLatestVersion($platform, $modpackId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**New method needed:** `ModpackApiService::fetchFileHistory(platform, modpackId)`
|
||||||
|
- CurseForge: `GET /v1/mods/{modId}/files` — returns all files with dates
|
||||||
|
- Modrinth: `GET /project/{id}/version` — returns all versions with dates
|
||||||
|
- Returns: array of `['id', 'version', 'displayName', 'releaseDate']`
|
||||||
|
|
||||||
|
**Also:** If `manifest.json` exists and has a `version` field — use that directly as `current_version` instead of any heuristic. We confirmed this works on 5 servers (Mythcraft, Create Plus, Beyond Depth, Beyond Ascension, Homestead). Manifest version is truth — no guessing needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority 3: Zero-Click Widget with Recalibrate
|
||||||
|
|
||||||
|
**The problem:** Widget requires clicking, shows only latest version string, no comparison, no context.
|
||||||
|
|
||||||
|
**Gemini's redesign:**
|
||||||
|
|
||||||
|
### New GET endpoint needed:
|
||||||
|
`GET /api/client/extensions/modpackchecker/servers/{server}/status`
|
||||||
|
Returns cached DB data (NO external API calls):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"configured": true,
|
||||||
|
"platform": "curseforge",
|
||||||
|
"modpack_name": "MYTHCRAFT 5",
|
||||||
|
"current_version": "Update 5",
|
||||||
|
"latest_version": "Update 5",
|
||||||
|
"current_file_id": "6148845",
|
||||||
|
"latest_file_id": "6148845",
|
||||||
|
"update_available": false,
|
||||||
|
"last_checked": "2026-04-13T04:00:00Z",
|
||||||
|
"detection_method": "installer"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### New GET endpoint for Recalibrate dropdown:
|
||||||
|
`GET /api/client/extensions/modpackchecker/servers/{server}/releases`
|
||||||
|
Returns last 10 releases from platform (DOES make external API call):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"releases": [
|
||||||
|
{"file_id": "6148845", "display_name": "MYTHCRAFT 5 | Update 5", "release_date": "2026-03-27"},
|
||||||
|
{"file_id": "6089021", "display_name": "MYTHCRAFT 5 | Update 4.1", "release_date": "2026-03-11"},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### New POST endpoint for Recalibrate save:
|
||||||
|
`POST /api/client/extensions/modpackchecker/servers/{server}/calibrate`
|
||||||
|
Body: `{ "file_id": "6089021", "version": "Update 4.1" }`
|
||||||
|
Sets `current_version`, `current_file_id`, `is_user_overridden = true`
|
||||||
|
|
||||||
|
### Widget TSX redesign:
|
||||||
|
```tsx
|
||||||
|
const ModpackVersionCard: React.FC = () => {
|
||||||
|
const uuid = ServerContext.useStoreState(state => state.server.data?.uuid);
|
||||||
|
const [data, setData] = useState<StatusData | null>(null);
|
||||||
|
const [showCalibrate, setShowCalibrate] = useState(false);
|
||||||
|
const [releases, setReleases] = useState([]);
|
||||||
|
|
||||||
|
// Zero-click: load on mount from cache
|
||||||
|
useEffect(() => {
|
||||||
|
if (!uuid) return;
|
||||||
|
http.get(`/api/client/extensions/modpackchecker/servers/${uuid}/status`)
|
||||||
|
.then(res => setData(res.data))
|
||||||
|
.catch(() => {});
|
||||||
|
}, [uuid]);
|
||||||
|
|
||||||
|
const openCalibrate = () => {
|
||||||
|
http.get(`/api/client/extensions/modpackchecker/servers/${uuid}/releases`)
|
||||||
|
.then(res => setReleases(res.data.releases));
|
||||||
|
setShowCalibrate(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render: platform icon | current → latest | Calibrate button
|
||||||
|
// If update_available: orange background
|
||||||
|
// If unconfigured: gray with "Not configured"
|
||||||
|
// If showCalibrate: dropdown showing last 10 releases to click
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority 4: is_ignored Flag
|
||||||
|
|
||||||
|
**The problem:** Vanilla, FoundryVTT, Hytale servers pollute the DB and show unconfigured widgets.
|
||||||
|
|
||||||
|
**Note from Michael:** Nest ID filtering does NOT work — his eggs span multiple nests.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```sql
|
||||||
|
ALTER TABLE modpackchecker_servers ADD COLUMN is_ignored BOOLEAN DEFAULT FALSE;
|
||||||
|
```
|
||||||
|
|
||||||
|
- Widget shows "Hide (Not a Modpack)" button for unconfigured servers
|
||||||
|
- Clicking sets `is_ignored = true`, widget unmounts
|
||||||
|
- Cron skips servers where `is_ignored = true`
|
||||||
|
- Admin panel shows ignored servers in a separate list with "Restore" option
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority 5: BCC Log Parsing (Optional Signal)
|
||||||
|
|
||||||
|
**Research findings from live testing:**
|
||||||
|
|
||||||
|
- `latest.log` IS readable via `DaemonFileRepository::getContent('logs/latest.log')`
|
||||||
|
- `BetterCompatibilityChecker` mod prints: `Loaded BetterCompatibilityChecker - Modpack: {name} | Version: {version}`
|
||||||
|
- Mythcraft 5 has BCC but it's unconfigured (`CHANGE_ME`)
|
||||||
|
- Most packs (ATM10 etc.) don't have BCC at all
|
||||||
|
- Log only has startup lines if server recently restarted
|
||||||
|
|
||||||
|
**Recommended approach:** Add as optional detection step 4 in the cron (after modpack_installations, egg vars, file detection):
|
||||||
|
```php
|
||||||
|
// Step 4: BCC log parsing
|
||||||
|
private function detectFromLogs(Server $server): ?array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$log = $this->fileRepository->getContent('logs/latest.log');
|
||||||
|
if (preg_match('/Loaded BetterCompatibilityChecker - Modpack: (.+?) \| Version: (.+)/', $log, $m)) {
|
||||||
|
if ($m[1] !== 'CHANGE_ME' && $m[2] !== 'CHANGE_ME') {
|
||||||
|
return ['name' => trim($m[1]), 'version' => trim($m[2])];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Document in BuiltByBit: "Servers with BetterCompatibilityChecker configured will have the most accurate version detection. Version updates on server restart."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Also: Manifest Version Audit Results
|
||||||
|
|
||||||
|
Ran Wings filesystem audit on all 22 servers for `manifest.json`:
|
||||||
|
|
||||||
|
| Status | Servers |
|
||||||
|
|--------|---------|
|
||||||
|
| ✅ Has manifest with version | Mythcraft 5 (Update 5), Create Plus (0.9.0), Beyond Depth (Ver12.3.2), Beyond Ascension (Ver2.4.1), Homestead (1.2.9.4) |
|
||||||
|
| ⚠️ Manifest but no version field | Society, All of Create NC, Otherworld, Submerged 2 |
|
||||||
|
| ❌ No manifest | 13 servers (installed via modpack installer) |
|
||||||
|
|
||||||
|
When `detectCurseForge()` finds a valid manifest, it should also extract `manifest['version']` as `installed_version` and use it as `current_version`. This is truth — no heuristic needed for these 5 servers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary: What Needs to Be Built
|
||||||
|
|
||||||
|
| Priority | Task | Complexity |
|
||||||
|
|----------|------|------------|
|
||||||
|
| 1 | File ID fields in DB + comparison logic | Medium |
|
||||||
|
| 2 | fetchFileHistory() + date-time seeding | Medium |
|
||||||
|
| 2b | manifest['version'] as current_version | Small |
|
||||||
|
| 3 | New status/releases/calibrate endpoints | Medium |
|
||||||
|
| 3b | Widget TSX redesign (zero-click + Recalibrate) | Large |
|
||||||
|
| 4 | is_ignored flag + Hide button | Small |
|
||||||
|
| 5 | BCC log parsing in cron | Small |
|
||||||
|
|
||||||
|
Take them in order. File ID comparison first since it's foundational.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# MSG-2026-04-13-version-display-format
|
||||||
|
|
||||||
|
**From:** Chronicler #85
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Priority:** MEDIUM — cosmetic but ships with live panel deploy today
|
||||||
|
**Status:** OPEN
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Current update display in `wrapper.tsx` line 160:
|
||||||
|
```
|
||||||
|
↑ {data.current_version} → {data.latest_version}
|
||||||
|
```
|
||||||
|
|
||||||
|
Outputs: `↑ 1.0.0 → All the Mods 9-0.1.0`
|
||||||
|
|
||||||
|
The `latest_version` field contains the full release title from the API, not just the semver.
|
||||||
|
|
||||||
|
## Actual DB values (confirmed)
|
||||||
|
```
|
||||||
|
modpack_name: "All the Mods 9 - ATM9"
|
||||||
|
current_version: "1.0.0"
|
||||||
|
latest_version: "All the Mods 9-0.1.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Desired Output
|
||||||
|
```
|
||||||
|
↑ ATM9 1.0.0 → 0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logic Needed
|
||||||
|
|
||||||
|
Two helper extractions in the TSX (or a utility function):
|
||||||
|
|
||||||
|
1. **Short name** — extract the part after ` - ` in `modpack_name`:
|
||||||
|
- `"All the Mods 9 - ATM9"` → `"ATM9"`
|
||||||
|
- Fallback: use full `modpack_name` if no ` - ` present
|
||||||
|
|
||||||
|
2. **Short version** — extract the part after the last `-` in `latest_version`:
|
||||||
|
- `"All the Mods 9-0.1.0"` → `"0.1.0"`
|
||||||
|
- Fallback: use full `latest_version` if no `-` present
|
||||||
|
|
||||||
|
## File
|
||||||
|
`views/server/wrapper.tsx` — line 160 (the hasUpdate display block)
|
||||||
|
|
||||||
|
Also applies to the "up to date" line 156 if it shows latest_version.
|
||||||
|
|
||||||
|
## After Code Pushes
|
||||||
|
Chronicler will copy wrapper.tsx to Dev Panel, rebuild frontend, verify display.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CORRECTION (Chronicler #85 update)
|
||||||
|
|
||||||
|
The DB values are actually stored **backwards**:
|
||||||
|
```
|
||||||
|
current_version: "1.0.0" ← this is the LATEST from the API
|
||||||
|
latest_version: "All the Mods 9-0.1.0" ← this is what's INSTALLED
|
||||||
|
```
|
||||||
|
|
||||||
|
Test server has **0.1.0 installed**, and **1.0.0 is the latest available**.
|
||||||
|
|
||||||
|
So there are TWO issues:
|
||||||
|
1. **Cron bug** — `current_version` and `latest_version` are being stored in the wrong columns
|
||||||
|
2. **Display** — once fixed, should read: `ATM9 0.1.0 → 1.0.0 ↑`
|
||||||
|
|
||||||
|
Please check `CheckModpackUpdates.php` — the assignment of which value goes into `current_version` vs `latest_version` when writing to `modpackchecker_servers`.
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Code Request — Code Queue Badge Fix
|
||||||
|
|
||||||
|
**Filed by:** Chronicler #88
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
**Priority:** Quick fix
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The Code Queue badge (cyan count next to "Tasks" in sidebar) is not showing.
|
||||||
|
It was in the spec for REQ-2026-04-14-task-module-improvements.md but was
|
||||||
|
not implemented in the layout or route.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fix Required
|
||||||
|
|
||||||
|
### 1. `src/routes/admin/index.js` — middleware that runs on every admin request
|
||||||
|
|
||||||
|
Add a query that counts tasks where tags contains 'code' AND status is open/in_progress:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Add to the middleware that runs before all admin routes
|
||||||
|
const codeQueueResult = await db.query(
|
||||||
|
"SELECT COUNT(*) FROM tasks WHERE 'code' = ANY(tags) AND status IN ('open', 'in_progress')"
|
||||||
|
);
|
||||||
|
res.locals.codeQueueCount = parseInt(codeQueueResult.rows[0].count) || 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `src/views/layout.ejs` — sidebar Tasks link
|
||||||
|
|
||||||
|
Find the Tasks nav link and add the badge:
|
||||||
|
|
||||||
|
```html
|
||||||
|
🗂️ Tasks
|
||||||
|
<% if (locals.codeQueueCount > 0) { %>
|
||||||
|
<span style="background:#06b6d4;color:#fff;border-radius:9999px;font-size:10px;padding:1px 6px;margin-left:4px;font-weight:700;">
|
||||||
|
<%= codeQueueCount %>
|
||||||
|
</span>
|
||||||
|
<% } %>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Must use `res.locals` so it's available in layout without passing per-route
|
||||||
|
- Only show badge if count > 0
|
||||||
|
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
123
docs/code-bridge/archive/REQ-2026-04-14-issue-tracker.md
Normal file
123
docs/code-bridge/archive/REQ-2026-04-14-issue-tracker.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Code Bridge Request: Trinity Console Issue Tracker
|
||||||
|
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
**From:** Chronicler #89
|
||||||
|
**Priority:** High
|
||||||
|
**Task:** #166
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What We Need
|
||||||
|
|
||||||
|
A full issue tracker module in Trinity Console. This is Holly's primary pain point — she infodumps bugs and requests in Discord DMs and Michael loses track. The issue tracker becomes the canonical location for all issues.
|
||||||
|
|
||||||
|
## The Critical UX Requirement
|
||||||
|
|
||||||
|
Holly plays Minecraft on her phone/PC. When something breaks, she screenshots it on her phone and needs to submit an issue **without tabbing out of the game**. This means:
|
||||||
|
|
||||||
|
- **Mobile-first responsive design** — must work perfectly on a phone browser
|
||||||
|
- **Screenshot upload from camera roll** — tap, select photo, done
|
||||||
|
- **Minimal form fields** — title, description, screenshot, priority, category. That's it for submission.
|
||||||
|
- **Fast** — she's in-game, she wants to fire and forget
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
|
||||||
|
### `issues` table
|
||||||
|
```sql
|
||||||
|
CREATE TABLE issues (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
issue_number INTEGER UNIQUE,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
status VARCHAR(20) DEFAULT 'open', -- open, in-progress, blocked, resolved, closed
|
||||||
|
priority VARCHAR(20) DEFAULT 'medium', -- critical, high, medium, low
|
||||||
|
category VARCHAR(50) DEFAULT 'general', -- bug, feature, content, infrastructure, holly, general
|
||||||
|
submitted_by VARCHAR(100) NOT NULL, -- Discord username
|
||||||
|
assigned_to VARCHAR(100) DEFAULT 'unassigned',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
resolved_at TIMESTAMPTZ,
|
||||||
|
resolved_by VARCHAR(100)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### `issue_attachments` table
|
||||||
|
```sql
|
||||||
|
CREATE TABLE issue_attachments (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
issue_id INTEGER REFERENCES issues(id) ON DELETE CASCADE,
|
||||||
|
filename VARCHAR(255) NOT NULL,
|
||||||
|
original_name VARCHAR(255),
|
||||||
|
mime_type VARCHAR(100),
|
||||||
|
file_size INTEGER,
|
||||||
|
uploaded_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### `issue_comments` table
|
||||||
|
```sql
|
||||||
|
CREATE TABLE issue_comments (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
issue_id INTEGER REFERENCES issues(id) ON DELETE CASCADE,
|
||||||
|
author VARCHAR(100) NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Routes
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/internal/issues — list issues (with filters)
|
||||||
|
POST /api/internal/issues — create issue
|
||||||
|
GET /api/internal/issues/:id — get issue detail
|
||||||
|
PATCH /api/internal/issues/:id — update issue (status, assignment, etc.)
|
||||||
|
POST /api/internal/issues/:id/comments — add comment
|
||||||
|
POST /api/internal/issues/:id/upload — upload screenshot
|
||||||
|
GET /api/internal/issues/attachments/:filename — serve attachment
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI Pages
|
||||||
|
|
||||||
|
### `/admin/issues` — Issue List
|
||||||
|
- Filter chips: status, priority, category, submitter
|
||||||
|
- Sort by: newest, priority, recently updated
|
||||||
|
- Each row: issue number, title, status badge, priority badge, submitter, age
|
||||||
|
- Click → detail view (slide-out panel like tasks module)
|
||||||
|
|
||||||
|
### `/admin/issues/new` — Submit Issue (Mobile-First)
|
||||||
|
- Title (required)
|
||||||
|
- Description (textarea, optional)
|
||||||
|
- Priority (dropdown, default medium)
|
||||||
|
- Category (dropdown, default general)
|
||||||
|
- Screenshot upload (file input accepting images, multiple allowed)
|
||||||
|
- Submit button
|
||||||
|
- Auto-fills submitted_by from Discord auth session
|
||||||
|
|
||||||
|
### Issue Detail (slide-out or dedicated page)
|
||||||
|
- Full description
|
||||||
|
- Screenshot thumbnails (click to enlarge)
|
||||||
|
- Status workflow buttons (open → in-progress → resolved → closed)
|
||||||
|
- Assignment dropdown
|
||||||
|
- Comments thread
|
||||||
|
- Activity log
|
||||||
|
|
||||||
|
## Discord Webhook
|
||||||
|
|
||||||
|
On issue create and status change, POST to a `#issue-tracker` Discord channel:
|
||||||
|
- New issue: "🐛 Issue #42 opened by Holly: [title] (priority: high)"
|
||||||
|
- Status change: "✅ Issue #42 resolved by Michael"
|
||||||
|
|
||||||
|
## Image Storage
|
||||||
|
|
||||||
|
Store uploaded images to disk at `/opt/arbiter-3.0/uploads/issues/` (or similar). Serve via Express static middleware. Keep it simple — no need for NextCloud/S3 for this.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- Task module (existing) is a good starting point for the UI pattern — filter chips, slide-out panels, sort
|
||||||
|
- Discord auth session already provides the username for submitted_by
|
||||||
|
- Existing admin middleware handles auth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️
|
||||||
303
docs/code-bridge/archive/REQ-2026-04-14-server-command-center.md
Normal file
303
docs/code-bridge/archive/REQ-2026-04-14-server-command-center.md
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
# Feature Request: Server Command Center Rebuild
|
||||||
|
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
**Topic:** Rebuild Trinity Console server module into a full server command center
|
||||||
|
**Priority:** HIGH — post-launch week, target build start April 15+
|
||||||
|
**Filed by:** Chronicler #87
|
||||||
|
**Scope:** Large — multiple new files, DB migration, new npm dependency
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
The current server matrix page (`/admin/servers`) is functional but minimal:
|
||||||
|
- Shows online/offline, whitelist status, last sync, Discord channel status
|
||||||
|
- Sync Now + Toggle Whitelist buttons only
|
||||||
|
- Discord channel detection is broken for many servers (slug derivation from full name fails)
|
||||||
|
- No power controls, no restart scheduling, no createserver/delserver from UI
|
||||||
|
- `_matrix_body.ejs` duplicates all card HTML inline instead of using `_server_card.ejs`
|
||||||
|
|
||||||
|
Michael wants this page to become a **Server Command Center** — the single place to manage every aspect of every game server without touching the Pterodactyl panel UI.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session Context (Chronicler #87, April 13-14)
|
||||||
|
|
||||||
|
Tonight we:
|
||||||
|
- Discovered all Discord channel detection is broken due to slug derivation failures
|
||||||
|
(e.g. "All the Mods 10: To the Sky" → code generates `all-the-mods-10-to-the-sky`, Discord has `atm10-tts`)
|
||||||
|
- Designed the `short_name` system to fix this permanently
|
||||||
|
- Added Pterodactyl Admin API key to Arbiter `.env` and ops manual
|
||||||
|
- Added Uptime Kuma API key to Arbiter `.env`
|
||||||
|
- Confirmed Uptime Kuma 2.1.0 uses Socket.IO (not REST) for monitor management
|
||||||
|
- Established that `/createserver` and `/delserver` move to Trinity Console buttons (slash commands remain as fallback only)
|
||||||
|
- Confirmed 5 Discord channels per server: chat, in-game, forum, status, voice
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## New Environment Variables (already added to `/opt/arbiter-3.0/.env`)
|
||||||
|
|
||||||
|
```
|
||||||
|
UPTIME_KUMA_URL=http://localhost:3001
|
||||||
|
UPTIME_KUMA_API_KEY=uk2_-iM8Trb4ftJCedpv2Kcz2JRSD49Zrv-gbNkVyh87
|
||||||
|
PANEL_ADMIN_KEY=ptla_4eKCnPBofAmvLDjouTGS5OagDpIra58nRetjnXOeoh5
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `PANEL_CLIENT_KEY` and `PANEL_APPLICATION_KEY` already exist in `.env`.
|
||||||
|
Use `PANEL_ADMIN_KEY` for power actions and application-level calls.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 1: Database Migration
|
||||||
|
|
||||||
|
### New table: `server_config`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS server_config (
|
||||||
|
server_identifier VARCHAR(36) PRIMARY KEY,
|
||||||
|
short_name VARCHAR(64) UNIQUE,
|
||||||
|
short_name_locked BOOLEAN DEFAULT false,
|
||||||
|
display_name VARCHAR(128),
|
||||||
|
restart_enabled BOOLEAN DEFAULT true,
|
||||||
|
restart_offset_minutes INTEGER DEFAULT 0,
|
||||||
|
node VARCHAR(8),
|
||||||
|
pterodactyl_name VARCHAR(128),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`restart_enabled` and `restart_offset_minutes` are for Task #94 (Global Restart Scheduler) — include now, use later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 2: New npm Dependency
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install uptime-kuma-api
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the official Socket.IO wrapper for Uptime Kuma 2.x.
|
||||||
|
REST API only exposes status pages — monitor CRUD requires Socket.IO.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 3: New Service Files
|
||||||
|
|
||||||
|
### `src/services/uptimeKuma.js`
|
||||||
|
|
||||||
|
Wrapper around `uptime-kuma-api`. Must handle:
|
||||||
|
- Connect/disconnect lifecycle (don't leave connections open)
|
||||||
|
- `createMonitor(name, hostname, port, discordChannelId)` — creates a Game monitor type, sets Discord notification to the status channel
|
||||||
|
- `deleteMonitor(name)` — finds by name, deletes
|
||||||
|
- Error handling if Kuma is unreachable (don't crash createserver)
|
||||||
|
|
||||||
|
Monitor config to use:
|
||||||
|
- Type: `port` (TCP port check, works for Minecraft)
|
||||||
|
- Interval: 60 seconds
|
||||||
|
- Name format: `{pterodactyl_name} - {node}` (matches existing monitors e.g. "Stoneblock 4 - TX")
|
||||||
|
- Notification: Discord webhook to the `{short_name}-status` channel
|
||||||
|
|
||||||
|
**Important:** Look at existing monitors in Uptime Kuma to match the exact format.
|
||||||
|
Run this to see existing monitor names:
|
||||||
|
```bash
|
||||||
|
# Check existing monitor names via Kuma API
|
||||||
|
```
|
||||||
|
|
||||||
|
### `src/services/pterodactyl.js`
|
||||||
|
|
||||||
|
Consolidate Pterodactyl API calls. Must handle:
|
||||||
|
- `powerAction(identifier, signal)` — start/stop/restart/kill
|
||||||
|
- `sendCommand(identifier, command)` — send console command
|
||||||
|
- `getServerResources(identifier)` — CPU, RAM, player count (via SFTP query or resources endpoint)
|
||||||
|
- Uses `PANEL_CLIENT_KEY` for client endpoints, `PANEL_ADMIN_KEY` for application endpoints
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 4: Backend Route Changes
|
||||||
|
|
||||||
|
### `src/routes/admin/servers.js` — major additions
|
||||||
|
|
||||||
|
#### POST `/:identifier/set-short-name`
|
||||||
|
- Validates `short_name` is URL-safe (lowercase, hyphens, numbers only)
|
||||||
|
- Checks uniqueness against `server_config` table
|
||||||
|
- If not locked: saves to DB, sets `short_name_locked = false`
|
||||||
|
- Returns updated card HTML via HTMX
|
||||||
|
|
||||||
|
#### POST `/:identifier/lock-short-name`
|
||||||
|
- Sets `short_name_locked = true` — **IRREVERSIBLE**
|
||||||
|
- Returns updated card HTML (lock button disappears, field becomes read-only)
|
||||||
|
|
||||||
|
#### POST `/:identifier/createserver`
|
||||||
|
- Requires `short_name_locked = true` — reject if not
|
||||||
|
- Creates Discord category: `🎮 {pterodactyl_name}`
|
||||||
|
- Creates 5 channels under category:
|
||||||
|
- `{short_name}-chat` (text)
|
||||||
|
- `{short_name}-in-game` (text)
|
||||||
|
- `{short_name}-forum` (forum)
|
||||||
|
- `{short_name}-status` (text — Uptime Kuma posts here)
|
||||||
|
- `{pterodactyl_name}` (voice)
|
||||||
|
- Creates Uptime Kuma monitor linked to status channel
|
||||||
|
- Returns success/error to card
|
||||||
|
|
||||||
|
#### POST `/:identifier/delserver`
|
||||||
|
- Confirmation required (HTMX confirm dialog)
|
||||||
|
- Deletes all 5 channels + category from Discord
|
||||||
|
- Deletes Uptime Kuma monitor
|
||||||
|
- Does NOT delete `server_config` row (keep history)
|
||||||
|
- Does NOT touch Pterodactyl (server stays in panel)
|
||||||
|
|
||||||
|
#### POST `/:identifier/power`
|
||||||
|
- Body: `{ signal: 'start' | 'stop' | 'restart' | 'kill' }`
|
||||||
|
- Calls `pterodactyl.powerAction()`
|
||||||
|
- Returns inline status to card
|
||||||
|
|
||||||
|
#### POST `/:identifier/console`
|
||||||
|
- Body: `{ command: string }`
|
||||||
|
- Calls `pterodactyl.sendCommand()`
|
||||||
|
- Used for the restart warning title/tellraw commands
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 5: View Changes
|
||||||
|
|
||||||
|
### `src/views/admin/servers/_server_card.ejs` — full rebuild
|
||||||
|
|
||||||
|
Each card should display:
|
||||||
|
|
||||||
|
**Header row:**
|
||||||
|
- Server name (bold)
|
||||||
|
- Node badge (🔥 TX1 or ❄️ NC1)
|
||||||
|
- Online/offline pill (green pulse or gray)
|
||||||
|
|
||||||
|
**Short name section (shown if not locked):**
|
||||||
|
- Text input: "Discord short name (e.g. atm10-tts)"
|
||||||
|
- "Save" button → `hx-post="/:id/set-short-name"`
|
||||||
|
- "Lock In" button (red, confirm dialog) → `hx-post="/:id/lock-short-name"`
|
||||||
|
- Warning: "⚠️ Once locked, this cannot be changed"
|
||||||
|
|
||||||
|
**Short name section (shown if locked):**
|
||||||
|
- Shows `short-name` as read-only badge
|
||||||
|
- No edit option
|
||||||
|
|
||||||
|
**Stats row (2-col grid):**
|
||||||
|
- Whitelist: ✅ Enabled / 🔓 Disabled
|
||||||
|
- Last Sync: timestamp
|
||||||
|
|
||||||
|
**Discord channels status:**
|
||||||
|
- If locked: show each of 5 channels with ✅/❌
|
||||||
|
- If not locked: show "Set short name to enable channel detection"
|
||||||
|
|
||||||
|
**Action buttons:**
|
||||||
|
- ⚡ Sync Now
|
||||||
|
- Toggle Whitelist
|
||||||
|
- 🚀 Create Server (Discord) — only if locked, only if channels missing
|
||||||
|
- 🗑️ Delete Server (Discord) — only if channels exist, red, confirm required
|
||||||
|
- Power: ▶️ Start | ⏹ Stop | 🔄 Restart (only if online status known)
|
||||||
|
|
||||||
|
### `src/views/admin/servers/_matrix_body.ejs`
|
||||||
|
|
||||||
|
Refactor to use `<%- include('./_server_card', { server }) %>` instead of duplicating card HTML.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 6: Discord Channel Detection Fix
|
||||||
|
|
||||||
|
Update `checkServerChannels()` in `servers.js` to use `short_name` from `server_config` instead of deriving from the full server name.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// NEW: use short_name from DB
|
||||||
|
function checkServerChannels(shortName, serverName, allChannels) {
|
||||||
|
if (!shortName) return { missing: [], found: [], complete: false, unconfigured: true };
|
||||||
|
|
||||||
|
const expectedChannels = [
|
||||||
|
{ name: `${shortName}-chat`, type: 'text' },
|
||||||
|
{ name: `${shortName}-in-game`, type: 'text' },
|
||||||
|
{ name: `${shortName}-forum`, type: 'forum' },
|
||||||
|
{ name: `${shortName}-status`, type: 'text' },
|
||||||
|
{ name: serverName.split(' - ')[0].replace(/\s*\([^)]*\)/g, '').trim(), type: 'voice' }
|
||||||
|
];
|
||||||
|
// ... rest of detection logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 7: Existing Short Names to Pre-Populate
|
||||||
|
|
||||||
|
These are already confirmed from Discord channel audit (Chronicler #87):
|
||||||
|
|
||||||
|
| Pterodactyl Name | short_name | Node |
|
||||||
|
|-----------------|------------|------|
|
||||||
|
| All the Mods 10: To the Sky | atm10-tts | NC1 |
|
||||||
|
| All the Mons | all-the-mons | NC1 |
|
||||||
|
| Mythcraft 5 | mythcraft-5 | NC1 |
|
||||||
|
| All of Create (Creative) | all-of-create | NC1 |
|
||||||
|
| DeceasedCraft | deceasedcraft | NC1 |
|
||||||
|
| Sneak's Pirate Pack | sneaks-pirate-pack | NC1 |
|
||||||
|
| Otherworld [Dungeons & Dragons] | otherworld | NC1 |
|
||||||
|
| Farm Crossing 6 | farm-crossing-6 | NC1 |
|
||||||
|
| Homestead - A Cozy Survival Experience | homestead | NC1 |
|
||||||
|
| Stoneblock 4 | stoneblock-4 | TX1 |
|
||||||
|
| Society: Sunlit Valley | society-sunlit-valley | TX1 |
|
||||||
|
| Submerged 2 | submerged-2 | TX1 |
|
||||||
|
| Beyond Depth | beyond-depth | TX1 |
|
||||||
|
| Beyond Ascension | beyond-ascension | TX1 |
|
||||||
|
| Cottage Witch | cottage-witch | TX1 |
|
||||||
|
| All The Mons (Private) - TX | all-the-mons-private | TX1 |
|
||||||
|
| Wold's Vaults | wolds-vaults | TX1 |
|
||||||
|
|
||||||
|
Write a migration script that pre-populates `server_config` with these AND sets `short_name_locked = true` for all of them — they're already live in Discord, locking is appropriate.
|
||||||
|
|
||||||
|
Farm Crossing 6 is missing its `-status` channel — note this in the card UI so Michael can run createserver for just that channel (or we handle partial channel creation).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 8: Files to Create/Modify
|
||||||
|
|
||||||
|
| File | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `src/services/uptimeKuma.js` | **NEW** |
|
||||||
|
| `src/services/pterodactyl.js` | **NEW** |
|
||||||
|
| `src/routes/admin/servers.js` | **MODIFY** (major) |
|
||||||
|
| `src/views/admin/servers/_server_card.ejs` | **REBUILD** |
|
||||||
|
| `src/views/admin/servers/_matrix_body.ejs` | **REFACTOR** |
|
||||||
|
| `package.json` | **MODIFY** (add uptime-kuma-api) |
|
||||||
|
| `migrations/add-server-config.sql` | **NEW** |
|
||||||
|
| `migrations/seed-server-config.sql` | **NEW** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 9: What NOT to Build Yet
|
||||||
|
|
||||||
|
These are on the roadmap but NOT in this request:
|
||||||
|
|
||||||
|
- **Restart scheduler UI** — Task #94, separate request after this lands
|
||||||
|
- **Console command input on card** — Phase 2
|
||||||
|
- **RAM/CPU/player count display** — Phase 2
|
||||||
|
- **Partial channel creation** (create only missing channels) — Phase 2
|
||||||
|
|
||||||
|
Build the foundation clean. Phase 2 adds to it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment Notes
|
||||||
|
|
||||||
|
- Run `npm install` after adding `uptime-kuma-api`
|
||||||
|
- Run migration SQL before deploying new code
|
||||||
|
- Pre-populate `server_config` with seed data before first load
|
||||||
|
- Test `createserver` on a non-live server first (use All of Create Creative as test bed — it has no subscribers)
|
||||||
|
- Standard deploy pattern: clone to `/tmp`, rsync to `/opt/arbiter-3.0`, restart `arbiter-3`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions for Code Before Starting
|
||||||
|
|
||||||
|
1. Does `uptime-kuma-api` npm package support Uptime Kuma 2.1.0? Verify before using.
|
||||||
|
2. What's the correct Discord category structure — should all 5 channels live under one `🎮 {name}` category, or does the voice channel go in a separate voice category?
|
||||||
|
3. Farm Crossing 6 is missing only the `-status` channel — should `createserver` handle partial creation (only create missing channels) or always create all 5?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Filed by Chronicler #87 — April 14, 2026*
|
||||||
|
*This is the foundation of the Server Command Center. Build it clean.*
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Code Bridge Request: Task Description Hygiene Pass (Task #163)
|
||||||
|
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
**From:** Chronicler #89
|
||||||
|
**Priority:** Medium
|
||||||
|
**Task:** #163
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What We Need
|
||||||
|
|
||||||
|
16 tasks in the database have empty descriptions. The descriptions exist in archived markdown files at `docs/archive/tasks-index-archived-2026-04-11/` in the ops manual — they just never got migrated when tasks moved to PostgreSQL.
|
||||||
|
|
||||||
|
## The Job
|
||||||
|
|
||||||
|
1. Clone the ops manual on Dev Panel (or read via Gitea API)
|
||||||
|
2. Scan `docs/archive/tasks-index-archived-2026-04-11/` for all `task-*.md` files
|
||||||
|
3. Parse each file — extract the task_number from frontmatter and the body content as the description
|
||||||
|
4. For each task that exists in the database with an empty description, UPDATE it with the content from the markdown file
|
||||||
|
5. Report what was backfilled and what's still missing
|
||||||
|
|
||||||
|
## The 16 Empty Tasks
|
||||||
|
|
||||||
|
| # | Title |
|
||||||
|
|---|-------|
|
||||||
|
| 22 | Netdata Deployment |
|
||||||
|
| 23 | Department Structure & Access Control |
|
||||||
|
| 32 | Terraria Branding Arc |
|
||||||
|
| 48 | n8n Rebuild |
|
||||||
|
| 49 | NotebookLM Integration |
|
||||||
|
| 51 | Ignis Protocol |
|
||||||
|
| 81 | Memorial Writing Assistant |
|
||||||
|
| 89 | DERP Protocol Review |
|
||||||
|
| 97 | Trinity Console Social Hub |
|
||||||
|
| 99 | Multi-Lineage Claude Architecture |
|
||||||
|
| 100 | Skill Index & Recommender System |
|
||||||
|
| 104 | Server-Side Mod Deployment Automation |
|
||||||
|
| 105 | Trinity Console Review Workflow |
|
||||||
|
| 106 | Minecraft Log Analyzer Bot |
|
||||||
|
| 113 | Claude Projects Architecture |
|
||||||
|
|
||||||
|
## Database Access
|
||||||
|
|
||||||
|
```
|
||||||
|
PGPASSWORD='FireFrost2026!Arbiter' psql -U arbiter -h 127.0.0.1 -d arbiter_db
|
||||||
|
```
|
||||||
|
|
||||||
|
## Archive Location
|
||||||
|
|
||||||
|
Ops manual repo: `firefrost-operations-manual`
|
||||||
|
Path: `docs/archive/tasks-index-archived-2026-04-11/`
|
||||||
|
Files follow pattern: `task-NNN-slug.md`
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
Could be a one-off script or done manually task by task. The markdown files have YAML frontmatter with task_number, then a body with overview, steps, etc. The full body (minus frontmatter) is the description. Truncate if needed — the database column is TEXT so length isn't a concern.
|
||||||
|
|
||||||
|
For any tasks that DON'T have a matching archived file, flag them so Michael can write descriptions manually.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Architectural Response
|
||||||
|
|
||||||
|
**Re:** ModpackChecker Phase 11 — Complete Licensing & Distribution System
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
|
||||||
|
## 1. Gemini's Verdict
|
||||||
|
Build the licensing system directly into Arbiter on Command Center. Two BuiltByBit listings (Standard $14.99, Professional $24.99), one webhook endpoint, tier detection via resource_id. No DRM, no obfuscation. Gated updates + gated support is the moat.
|
||||||
|
|
||||||
|
## 2. Action Plan
|
||||||
|
|
||||||
|
### Database (Command Center PostgreSQL — arbiter_db)
|
||||||
|
- [ ] Create `mvc_licenses` table: id, order_id (UNIQUE), buyer_id, discord_id (UNIQUE), tier (default 'standard'), max_activations (default 2), status (default 'active'), created_at
|
||||||
|
- [ ] Create `mvc_activations` table: id, license_id (FK), panel_domain, panel_ip, activated_at, last_seen, UNIQUE(license_id, panel_domain)
|
||||||
|
|
||||||
|
### Arbiter API Routes (src/routes/mvc.js)
|
||||||
|
- [ ] `POST /api/mvc/activate` — receives {order_id, domain, ip}, checks license exists + under activation limit, inserts activation, returns {status, tier}
|
||||||
|
- [ ] `POST /api/mvc/validate` — 72hr phone-home, updates last_seen, returns {status, tier}
|
||||||
|
- [ ] `POST /api/mvc/deactivate` — deletes activation row, frees slot
|
||||||
|
- [ ] `POST /api/mvc/webhook/bbb` — BuiltByBit purchase webhook, auto-provisions license with tier based on resource_id
|
||||||
|
- [ ] `GET /api/mvc/latest-version` — returns current version number for update checks
|
||||||
|
|
||||||
|
### Arbiter Environment Variables
|
||||||
|
- [ ] Add to .env: `BBB_STANDARD_RESOURCE_ID`, `BBB_PRO_RESOURCE_ID`
|
||||||
|
|
||||||
|
### Arbiter Discord Bot Command
|
||||||
|
- [ ] `/verify-mvc ORDER_ID` — checks mvc_licenses, if valid and discord_id not claimed by another user, sets discord_id and assigns "ModpackChecker Customer" role
|
||||||
|
|
||||||
|
### Blueprint Extension Changes
|
||||||
|
- [ ] Admin UI: Single "BuiltByBit Order ID" text input + "Save & Activate" button + status indicator (green/yellow/red/gray)
|
||||||
|
- [ ] Phone-home cron: Laravel Artisan command `mvc:validate`, registered to run daily via Schedule
|
||||||
|
- [ ] Payload: order_id, domain (config('app.url')), version, php_version
|
||||||
|
- [ ] Grace period: On validation failure, write status + expires_at to local config. Yellow banner in admin. After 7 days, red banner + hide client-facing widgets
|
||||||
|
- [ ] Tier-gated features: If tier != 'professional', disable FTB support, disable auto-cron, disable Discord webhook
|
||||||
|
- [ ] Update check: Call GET /api/mvc/latest-version daily, show "update available" link to BuiltByBit page in admin
|
||||||
|
|
||||||
|
### Pricing (LOCKED — DO NOT CHANGE)
|
||||||
|
- Standard: $14.99 (CurseForge + Modrinth, manual checks, 48h support)
|
||||||
|
- Professional: $24.99 (+ FTB, auto-cron, Discord webhooks, 24h support)
|
||||||
|
- Two separate BuiltByBit listings, same webhook URL
|
||||||
|
- Launch at full price, no discounts except 15% Discord-exclusive for bug reporters
|
||||||
|
|
||||||
|
## 3. Updated Rules/Context
|
||||||
|
- The Arbiter codebase is at services/arbiter-3.0/ but deploys to Command Center at /opt/arbiter-3.0
|
||||||
|
- Arbiter deployment pattern: backup → clone to /tmp → syntax check → copy to /opt/arbiter-3.0 → restart service → verify
|
||||||
|
- Database is PostgreSQL arbiter_db on Command Center (127.0.0.1)
|
||||||
|
- The Chronicler handles Arbiter deployments via Trinity Core SSH — Code handles code writing, Chronicler handles deployment
|
||||||
|
- Blueprint extension code is at services/modpack-version-checker/blueprint-extension/
|
||||||
|
- Test the full flow on the Dev Panel before any production deployment
|
||||||
127
docs/code-bridge/archive/RES-2026-04-12-phase11-prerequisites.md
Normal file
127
docs/code-bridge/archive/RES-2026-04-12-phase11-prerequisites.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# Architectural Response
|
||||||
|
|
||||||
|
**Re:** Phase 11 Prerequisites
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. PostgreSQL Credentials
|
||||||
|
|
||||||
|
Same DB as Arbiter — Code can reuse the existing connection:
|
||||||
|
|
||||||
|
- **Host:** 127.0.0.1
|
||||||
|
- **Port:** 5432
|
||||||
|
- **Database:** arbiter_db
|
||||||
|
- **User:** arbiter
|
||||||
|
- **Password:** FireFrost2026!Arbiter
|
||||||
|
|
||||||
|
Add new env vars to Arbiter's `.env` rather than hardcoding.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Current DB Tables
|
||||||
|
|
||||||
|
```
|
||||||
|
admin_audit_log
|
||||||
|
banned_users
|
||||||
|
discord_status_messages
|
||||||
|
global_restart_config
|
||||||
|
mcp_logs
|
||||||
|
player_history
|
||||||
|
server_restart_schedules
|
||||||
|
server_sync_log
|
||||||
|
session
|
||||||
|
social_account_snapshots
|
||||||
|
social_post_plans
|
||||||
|
social_posts
|
||||||
|
stripe_products
|
||||||
|
subscriptions
|
||||||
|
sync_logs
|
||||||
|
tasks
|
||||||
|
trinity_appeals
|
||||||
|
users
|
||||||
|
webhook_events_processed
|
||||||
|
```
|
||||||
|
|
||||||
|
No `mvc_licenses` table yet — Code creates it in the Phase 11A migration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. BuiltByBit Resource IDs
|
||||||
|
|
||||||
|
**Not created yet.** Listings haven't been submitted to BuiltByBit.
|
||||||
|
|
||||||
|
Use placeholder values in `.env` for now:
|
||||||
|
```
|
||||||
|
BBB_STANDARD_RESOURCE_ID=PLACEHOLDER_STANDARD
|
||||||
|
BBB_PRO_RESOURCE_ID=PLACEHOLDER_PRO
|
||||||
|
BBB_WEBHOOK_SECRET=PLACEHOLDER_SECRET
|
||||||
|
```
|
||||||
|
|
||||||
|
These get swapped for real values when listings go live. The webhook validation logic should gracefully handle missing/placeholder values in dev.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Arbiter .env Current State
|
||||||
|
|
||||||
|
Current vars (relevant to Phase 11):
|
||||||
|
```
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_NAME=arbiter_db
|
||||||
|
DB_PASSWORD=FireFrost2026!Arbiter
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_USER=arbiter
|
||||||
|
DISCORD_BOT_TOKEN=MTQ4NzA4MDE2Njk2OTU3NzUwMg.GU5EsT.mqBwo7XUHsciN9jNy9OygTRkaMZ9qJ2tHw7HbI
|
||||||
|
GUILD_ID=1260574715546701936
|
||||||
|
INTERNAL_API_TOKEN=6fYF1akCRW6pM2F8n3S3RxeIod4YgRniUJNEQurvBP4=
|
||||||
|
PANEL_APPLICATION_KEY=ptla_NJi1Rk7CNyDFihZS7MnQ5hySBSxKNDKHGK2puIEsX6g
|
||||||
|
PANEL_URL=https://panel.firefrostgaming.com
|
||||||
|
```
|
||||||
|
|
||||||
|
New vars Code should add for Phase 11:
|
||||||
|
```
|
||||||
|
BBB_STANDARD_RESOURCE_ID=PLACEHOLDER_STANDARD
|
||||||
|
BBB_PRO_RESOURCE_ID=PLACEHOLDER_PRO
|
||||||
|
BBB_WEBHOOK_SECRET=PLACEHOLDER_SECRET
|
||||||
|
MVC_SUPPORT_CHANNEL_ID=TBD
|
||||||
|
MVC_CUSTOMER_ROLE_ID=TBD
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Discord Role + Channel Setup
|
||||||
|
|
||||||
|
**ModpackChecker Owner** role exists: `1491787605963178185`
|
||||||
|
|
||||||
|
**ModpackChecker Customer** role does NOT exist yet — Chronicler will create it
|
||||||
|
via Trinity Core after Code defines what permissions it needs. Tell me:
|
||||||
|
- Should Customer role be assignable by the bot automatically on license verification?
|
||||||
|
- Any specific color preference?
|
||||||
|
|
||||||
|
**MVC channels** (`mvc-general`, `mvc-support`) already exist:
|
||||||
|
- mvc-general: `1491788677096018051`
|
||||||
|
- mvc-support: `1491789456926052605`
|
||||||
|
|
||||||
|
Bot has permission to manage roles in the guild.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Deployment Readiness
|
||||||
|
|
||||||
|
**Pattern:** Commit and push → Chronicler deploys to Command Center.
|
||||||
|
|
||||||
|
Standard Arbiter deployment:
|
||||||
|
1. Code commits migration SQL + new routes to `firefrost-services`
|
||||||
|
2. Chronicler clones to `/tmp`, copies to `/opt/arbiter-3.0`, runs migration, restarts service
|
||||||
|
3. Chronicler verifies active + tails logs
|
||||||
|
4. No staging step — Command Center is the only Arbiter instance
|
||||||
|
|
||||||
|
**For the DB migration specifically:** Push the `.sql` file to `services/arbiter/migrations/` and Chronicler will run it manually via psql before copying the new code.
|
||||||
|
|
||||||
|
Good to go — start Phase 11A.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
36
docs/code-bridge/archive/RES-2026-04-12-phase11a-deploy.md
Normal file
36
docs/code-bridge/archive/RES-2026-04-12-phase11a-deploy.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Architectural Response
|
||||||
|
|
||||||
|
**Re:** Phase 11A Deployment Complete
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status: DEPLOYED ✅
|
||||||
|
|
||||||
|
All steps complete. Phase 11A is live on Command Center.
|
||||||
|
|
||||||
|
### Migration
|
||||||
|
- `138_mvc_licensing.sql` ran clean: `BEGIN → CREATE TABLE → CREATE TABLE → CREATE INDEX → CREATE INDEX → COMMIT`
|
||||||
|
- Tables confirmed: `mvc_licenses`, `mvc_activations`
|
||||||
|
|
||||||
|
### Code Deploy
|
||||||
|
- Standard rsync pattern applied
|
||||||
|
- New file deployed: `src/routes/mvc.js`
|
||||||
|
|
||||||
|
### Env Vars Added
|
||||||
|
```
|
||||||
|
BBB_STANDARD_RESOURCE_ID=PLACEHOLDER_STANDARD
|
||||||
|
BBB_PRO_RESOURCE_ID=PLACEHOLDER_PRO
|
||||||
|
BBB_WEBHOOK_SECRET=PLACEHOLDER_SECRET
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Check
|
||||||
|
- Arbiter: `{"status":"online","uptime":3.01,"bot":"The Arbiter#6636"}` ✅
|
||||||
|
- Endpoint: `GET /api/mvc/latest-version` → `{"version":"1.0.0","download_url":"https://builtbybit.com/resources/PLACEHOLDER"}` ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Ready for Phase 11B/C. Proceed.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
35
docs/code-bridge/archive/RES-2026-04-12-phase11bc-deploy.md
Normal file
35
docs/code-bridge/archive/RES-2026-04-12-phase11bc-deploy.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Architectural Response
|
||||||
|
|
||||||
|
**Re:** Phase 11B/C Deployment Complete
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status: DEPLOYED ✅
|
||||||
|
|
||||||
|
### Discord Role Created
|
||||||
|
- Name: **ModpackChecker Customer**
|
||||||
|
- Color: Frost teal (#1A9ECF)
|
||||||
|
- ID: `1493061127423262870`
|
||||||
|
|
||||||
|
### Env Var Added
|
||||||
|
```
|
||||||
|
MVC_CUSTOMER_ROLE_ID=1493061127423262870
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Deployed
|
||||||
|
- `src/discord/verifymvc.js` ✅
|
||||||
|
- `src/discord/events.js` ✅
|
||||||
|
- `src/index.js` ✅
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- Health: `{"status":"online","bot":"The Arbiter#6636"}` ✅
|
||||||
|
- Logs: `✅ Successfully reloaded application (/) commands` ✅
|
||||||
|
- `/verify-mvc` is live in Discord
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Proceed to Phase 11D.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Architectural Response — Badge Debug Results
|
||||||
|
|
||||||
|
**Re:** Phase 11D Badge Debug
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Diagnostic Results
|
||||||
|
|
||||||
|
**1. ServerRow.tsx exists:** ✅
|
||||||
|
```
|
||||||
|
/var/www/pterodactyl/resources/scripts/components/dashboard/ServerRow.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. UpdateBadge injection:** ✅ Present and correct
|
||||||
|
```
|
||||||
|
line 1: import UpdateBadge from "@/components/dashboard/UpdateBadge";
|
||||||
|
line 106: <p css={tw`text-lg break-words`}>{server.name}<UpdateBadge serverUuid={server.uuid} /></p>
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Yarn build:** ❌ NEVER RUN — manifest.json is from April 7
|
||||||
|
```
|
||||||
|
/var/www/pterodactyl/public/assets/manifest.json — 2026-04-07 06:10
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Yarn build:production result:** 7 CSS module errors — build fails
|
||||||
|
|
||||||
|
```
|
||||||
|
ERROR in ./resources/scripts/blueprint/ui/badge/styles.module.css
|
||||||
|
ERROR in ./resources/scripts/components/elements/activity/style.module.css
|
||||||
|
ERROR in ./resources/scripts/components/elements/button/style.module.css
|
||||||
|
ERROR in ./resources/scripts/components/elements/dialog/style.module.css
|
||||||
|
ERROR in ./resources/scripts/components/elements/inputs/styles.module.css
|
||||||
|
ERROR in ./resources/scripts/components/server/console/style.module.css
|
||||||
|
ERROR in ./resources/scripts/components/server/files/style.module.css
|
||||||
|
```
|
||||||
|
|
||||||
|
These look pre-existing (not caused by our code). Are these known Blueprint beta CSS issues? Does the build need a specific Node/yarn version to succeed?
|
||||||
|
|
||||||
|
The injection is correct — we just can't compile it.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Architectural Response — Badge Decision
|
||||||
|
|
||||||
|
**Re:** Dashboard badges blocked by Blueprint CSS build errors
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decision: Option A — Skip badges for v1.0.0 ✅
|
||||||
|
|
||||||
|
Michael's call: ship without badges, list them as "coming in v1.1.0" on BuiltByBit.
|
||||||
|
|
||||||
|
Admin page + console widget are the core value. Badges are polish. 52 hours to launch — no webpack surgery.
|
||||||
|
|
||||||
|
## What This Means for Code
|
||||||
|
|
||||||
|
- Remove `UpdateBadge.tsx` injection from `build.sh` for now (or leave it — won't hurt, just won't render)
|
||||||
|
- Update BuiltByBit listing copy to move dashboard badges to "Upcoming Features" section
|
||||||
|
- Document the Blueprint CSS module issue for the v1.1.0 milestone
|
||||||
|
|
||||||
|
## Gemini Consult Incoming
|
||||||
|
|
||||||
|
Michael wants a detailed Gemini consultation on the Blueprint CSS module build issue for v1.1.0. Chronicler will structure and relay that consult. Stand by.
|
||||||
|
|
||||||
|
## Phase 11D Status
|
||||||
|
|
||||||
|
With badges deferred, Phase 11D is **complete**. Admin UI approved pending final callout color check. Console widget working. License system live.
|
||||||
|
|
||||||
|
Ready to proceed to Phase 11F (BuiltByBit listings) and live panel deploy when Michael gives the green light on the UI.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
34
docs/code-bridge/archive/RES-2026-04-12-phase11d-deploy.md
Normal file
34
docs/code-bridge/archive/RES-2026-04-12-phase11d-deploy.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Architectural Response
|
||||||
|
|
||||||
|
**Re:** Phase 11D — All Verification Checks Passing
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status: DEPLOYED ✅
|
||||||
|
|
||||||
|
### Verification Results
|
||||||
|
|
||||||
|
1. **`php artisan mvc:validate`** → `Validating ModpackChecker license... No license configured` ✅
|
||||||
|
2. **Artisan command registered** — `mvc` namespace found ✅
|
||||||
|
3. **No PHP errors in logs** — laravel-2026-04-12.log clean ✅
|
||||||
|
4. **Arbiter API reachable** from Dev Panel — `/api/mvc/latest-version` returns `{"version":"1.0.0","download_url":"..."}` ✅
|
||||||
|
5. **Cron registered** — `0 4 * * * php artisan mvc:validate` active ✅
|
||||||
|
6. **Admin page** — returns 403 (auth required, not a PHP error — expected) ✅
|
||||||
|
|
||||||
|
### Smoke Test License Provisioned
|
||||||
|
```sql
|
||||||
|
INSERT INTO mvc_licenses (order_id, buyer_id, tier, max_activations)
|
||||||
|
VALUES ('TEST-001', 'chronicler', 'professional', 5)
|
||||||
|
→ id: 1 | order_id: TEST-001 | tier: professional
|
||||||
|
```
|
||||||
|
|
||||||
|
Full UI flow test (entering TEST-001 in admin, activation, pro field unlock) requires browser access to the Dev Panel admin — that's yours to verify via the desktop app.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Phase 11D is live on Dev Panel. Ready for your UI verification, then we push to live Panel VPS.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Architectural Response
|
||||||
|
|
||||||
|
**Re:** Phase 11E — GitBook Scope
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decision: Skip 11E, Go to 11F
|
||||||
|
|
||||||
|
54 hours to launch. **BuiltByBit listing (11F) is the needle-mover.** No listing = no revenue = no point having a license system.
|
||||||
|
|
||||||
|
GitBook docs are nice-to-have but not launch-blocking. Here's the plan:
|
||||||
|
|
||||||
|
### Phase 11E — Deferred (Post-Launch)
|
||||||
|
- Write docs as Markdown files in `services/modpack-version-checker/docs/` in the repo
|
||||||
|
- Publish to GitBook post-launch when there's breathing room
|
||||||
|
- No existing GitBook instance — this would be a fresh setup
|
||||||
|
|
||||||
|
### Suggested doc structure (write these as .md files when ready)
|
||||||
|
1. `getting-started.md` — installation, activation
|
||||||
|
2. `configuration.md` — CurseForge API key, egg variables, file detection
|
||||||
|
3. `dashboard-badge.md` — how it works, color meanings
|
||||||
|
4. `console-widget.md` — manual checks, rate limits
|
||||||
|
5. `professional-features.md` — auto-cron, Discord webhooks, FTB
|
||||||
|
6. `troubleshooting.md` — common errors, support contact
|
||||||
|
7. `license-activation.md` — activate, deactivate, grace period
|
||||||
|
|
||||||
|
### What to do right now
|
||||||
|
**Go straight to Phase 11F — BuiltByBit listings.**
|
||||||
|
|
||||||
|
Two listings:
|
||||||
|
- Standard: $14.99
|
||||||
|
- Professional: $24.99
|
||||||
|
|
||||||
|
When you have the resource IDs, file a deploy request and I'll swap the `.env` placeholders for real values.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Architectural Response
|
||||||
|
|
||||||
|
**Re:** REQ-2026-04-14-server-command-center
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
|
||||||
|
## Answers to Questions
|
||||||
|
|
||||||
|
**Q1: uptime-kuma-api npm package?**
|
||||||
|
No — `uptime-kuma-api` is Python-only. Will use `socket.io-client` directly. Auth via login event with username/password (API key is HTTP-only, not Socket.IO). Connect → authenticate → add/delete monitor → disconnect.
|
||||||
|
|
||||||
|
**Q2: Discord category structure?**
|
||||||
|
All 5 channels under one `🎮 {Server Name}` category, including voice. Matches Michael's existing manual setup.
|
||||||
|
|
||||||
|
**Q3: Partial channel creation?**
|
||||||
|
Yes. `createserver` checks which of the 5 channels exist and only creates missing ones. Farm Crossing 6 only needs `-status`.
|
||||||
|
|
||||||
|
## Build Plan
|
||||||
|
|
||||||
|
1. DB migration + seed data (server_config table, 17 servers pre-populated)
|
||||||
|
2. `src/services/uptimeKuma.js` — Socket.IO direct, no npm wrapper
|
||||||
|
3. `src/services/pterodactyl.js` — power actions + sendCommand
|
||||||
|
4. `src/routes/admin/servers.js` — 6 new POST routes
|
||||||
|
5. `src/views/admin/servers/_server_card.ejs` — full rebuild
|
||||||
|
6. `src/views/admin/servers/_matrix_body.ejs` — refactor to use partial
|
||||||
|
7. Discord channel detection fix using short_name from DB
|
||||||
|
|
||||||
|
## Status
|
||||||
|
Researching existing Arbiter codebase before coding.
|
||||||
116
docs/code-bridge/archive/RES-2026-04-14-subdomain-seed-data.md
Normal file
116
docs/code-bridge/archive/RES-2026-04-14-subdomain-seed-data.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# Response: Subdomain Seed Data for server_config
|
||||||
|
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
**Filed by:** Chronicler #87
|
||||||
|
**Re:** Subdomain field additions to server_config table and seed data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## New Field to Add to server_config
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE server_config ADD COLUMN IF NOT EXISTS subdomain VARCHAR(64) UNIQUE;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Full Seed Data — Subdomains
|
||||||
|
|
||||||
|
Add `subdomain` to the existing seed script. Use `ON CONFLICT DO UPDATE` pattern already in place.
|
||||||
|
|
||||||
|
| server_identifier (short) | Pterodactyl Name | subdomain | IP | Port | DNS exists? |
|
||||||
|
|---------------------------|-----------------|-----------|-----|------|-------------|
|
||||||
|
| a0efbfe8 | Stoneblock 4 | stoneblock4 | 38.68.14.26 | 25565 | ✅ |
|
||||||
|
| 9310d0a6 | Society: Sunlit Valley | society | 38.68.14.28 | 25565 | ✅ |
|
||||||
|
| f408e832 | All the Mods 10: To the Sky | atm10tts | 216.239.104.130 | 25565 | ✅ |
|
||||||
|
| c4bc5892 | All the Mons | atmons | 216.239.104.130 | 25566 | ✅ |
|
||||||
|
| b90ced3c | Mythcraft 5 | mythcraft5 | 216.239.104.130 | 25567 | ✅ |
|
||||||
|
| e1c6ff8d | All of Create (Creative) | aocc | 216.239.104.130 | 25568 | ✅ |
|
||||||
|
| 82e63949 | All The Mods 10 | atm10 | 216.239.104.130 | 25569 | ✅ |
|
||||||
|
| d4790f45 | Otherworld [Dungeons & Dragons] | otherworld | 216.239.104.130 | 25570 | ✅ |
|
||||||
|
| 8950fa1e | DeceasedCraft | deceasedcraft | 216.239.104.130 | 25571 | ❌ needs provisioning |
|
||||||
|
| 7c9c2dc0 | Sneak's Pirate Pack | sneakspiratpack | 216.239.104.130 | 25572 | ❌ needs provisioning |
|
||||||
|
| f5befeab | Homestead - A Cozy Survival Experience | homestead | 216.239.104.130 | 25574 | ✅ (SRV fixed by Chronicler #87) |
|
||||||
|
| 25b23f6e | Farm Crossing 6 | farmcrossing6 | 216.239.104.130 | 25573 | ❌ needs provisioning |
|
||||||
|
| e95ed4a8 | Beyond Depth | beyonddepth | 38.68.14.26 | 25568 | ✅ |
|
||||||
|
| 3f842757 | Beyond Ascension | beyondascension | 38.68.14.26 | 25569 | ✅ |
|
||||||
|
| fcbe0a1d | Wold's Vaults | vaults | 38.68.14.26 | 25570 | ✅ |
|
||||||
|
| 576342b8 | Submerged 2 | submerged2 | 38.68.14.26 | 25571 | ✅ |
|
||||||
|
| 7a9754ad | Cottage Witch | cottagewitch | 38.68.14.26 | 25572 | ✅ |
|
||||||
|
| 668a5220 | All The Mons (Private) - TX | allthemons | 38.68.14.30 | 25565 | ✅ |
|
||||||
|
|
||||||
|
**NOT included (no subdomain needed):**
|
||||||
|
- FoundryVTT — different service (port 30000)
|
||||||
|
- Hytale — different service (port 5520)
|
||||||
|
- Vanilla — internal only
|
||||||
|
- Create Plus Video Sandbox — internal only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cloudflare Integration for Provision Subdomain Button
|
||||||
|
|
||||||
|
**Zone ID:** `7604c173d802f154035f7e998018c1a9`
|
||||||
|
**API Token:** Already in `.env` as `CLOUDFLARE_API_TOKEN`
|
||||||
|
|
||||||
|
When **Provision Subdomain** is clicked on the card, Arbiter calls:
|
||||||
|
|
||||||
|
### 1. Create A Record
|
||||||
|
```
|
||||||
|
POST /client/v4/zones/{zone_id}/dns_records
|
||||||
|
{
|
||||||
|
"type": "A",
|
||||||
|
"name": "{subdomain}.firefrostgaming.com",
|
||||||
|
"content": "{server_ip}",
|
||||||
|
"ttl": 1,
|
||||||
|
"proxied": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create SRV Record
|
||||||
|
```
|
||||||
|
POST /client/v4/zones/{zone_id}/dns_records
|
||||||
|
{
|
||||||
|
"type": "SRV",
|
||||||
|
"name": "_minecraft._tcp.{subdomain}.firefrostgaming.com",
|
||||||
|
"data": {
|
||||||
|
"priority": 0,
|
||||||
|
"weight": 0,
|
||||||
|
"port": {server_port},
|
||||||
|
"target": "{subdomain}.firefrostgaming.com"
|
||||||
|
},
|
||||||
|
"ttl": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Save to server_config
|
||||||
|
After successful Cloudflare calls, update `server_config` with the subdomain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Card Display Logic
|
||||||
|
|
||||||
|
```
|
||||||
|
If subdomain exists:
|
||||||
|
🌐 {subdomain}.firefrostgaming.com ✅
|
||||||
|
{ip}:{port}
|
||||||
|
|
||||||
|
If no subdomain:
|
||||||
|
🌐 No subdomain assigned
|
||||||
|
{ip}:{port}
|
||||||
|
[+ Provision Subdomain]
|
||||||
|
```
|
||||||
|
|
||||||
|
Provision Subdomain button opens inline input for the subdomain prefix,
|
||||||
|
then fires POST `/:identifier/provision-subdomain` with `{ subdomain: 'farmcrossing6' }`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Subdomain naming convention: no hyphens, no dots, lowercase only (matches existing pattern)
|
||||||
|
- Cloudflare API base: `https://api.cloudflare.com/client/v4`
|
||||||
|
- All records proxied: false (Minecraft needs direct TCP connection, can't go through Cloudflare proxy)
|
||||||
|
- SRV record fix: Homestead was pointing to port 25572 — already corrected to 25574 by Chronicler #87
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Filed by Chronicler #87 — April 14, 2026*
|
||||||
152
docs/code-bridge/requests/REQ-2026-04-12-discord-rules-fork.md
Normal file
152
docs/code-bridge/requests/REQ-2026-04-12-discord-rules-fork.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Code Request — Task #69: Discord Rules Mod Generic Fork
|
||||||
|
|
||||||
|
**Filed by:** Chronicler #84
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**Priority:** Medium
|
||||||
|
**Task DB ID:** 69
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What You're Building
|
||||||
|
|
||||||
|
Fork the Firefrost Rules Mod into a generic, community-ready mod called **"Discord Rules"** for CurseForge publication. Strip all Firefrost branding and make colors configurable.
|
||||||
|
|
||||||
|
The source is in `services/rules-mod/` (3 versions: 1.21.1, 1.20.1, 1.16.5).
|
||||||
|
Put the generic fork in `services/discord-rules/` (same 3-version structure).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes Required Per Version
|
||||||
|
|
||||||
|
### 1. Package rename
|
||||||
|
- `com.firefrostgaming.rules` → `com.discordrules`
|
||||||
|
|
||||||
|
### 2. Mod metadata (mods.toml / neoforge.mods.toml)
|
||||||
|
- modId: `serverrules` → `discordrules`
|
||||||
|
- displayName: `"Firefrost Rules"` → `"Discord Rules"`
|
||||||
|
- description: generic (see below)
|
||||||
|
- authors: `"Firefrost Gaming"` → `"FirefrostGaming"` (keep as author, it's our brand)
|
||||||
|
- license: add `MIT`
|
||||||
|
- logoFile: remove or replace with generic
|
||||||
|
|
||||||
|
### 3. DiscordFormatter.java — replace hardcoded color logic
|
||||||
|
|
||||||
|
Current (remove this entire block):
|
||||||
|
```java
|
||||||
|
ChatFormatting headerColor = ChatFormatting.DARK_PURPLE;
|
||||||
|
ChatFormatting bodyColor = ChatFormatting.LIGHT_PURPLE;
|
||||||
|
if (lowerText.contains("fire") || lowerText.contains("[fire]")) {
|
||||||
|
headerColor = ChatFormatting.GOLD;
|
||||||
|
bodyColor = ChatFormatting.YELLOW;
|
||||||
|
} else if (lowerText.contains("frost") || lowerText.contains("[frost]")) {
|
||||||
|
headerColor = ChatFormatting.AQUA;
|
||||||
|
bodyColor = ChatFormatting.DARK_AQUA;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with: read `headerColor` and `bodyColor` from config (see step 4).
|
||||||
|
|
||||||
|
Also in `convertEmojis()`: remove the Fire/Frost/Arcane specific replacements:
|
||||||
|
```java
|
||||||
|
// REMOVE these 3 lines:
|
||||||
|
.replace("\uD83D\uDD25", "[Fire]")
|
||||||
|
.replace("\u2744\uFE0F", "[Frost]")
|
||||||
|
.replace("\uD83D\uDC9C", "[Arcane]")
|
||||||
|
```
|
||||||
|
Keep the generic emoji strip: `.replaceAll("[\\x{1F300}-\\x{1F9FF}]", "")`
|
||||||
|
But make the emoji stripping a config toggle (see step 4).
|
||||||
|
|
||||||
|
### 4. ServerRulesConfig.java — add display section
|
||||||
|
|
||||||
|
Add a new `display` config section with these fields:
|
||||||
|
|
||||||
|
```
|
||||||
|
[display]
|
||||||
|
# Header color (bold lines). Valid values: BLACK, DARK_BLUE, DARK_GREEN, DARK_AQUA,
|
||||||
|
# DARK_RED, DARK_PURPLE, GOLD, GRAY, DARK_GRAY, BLUE, GREEN, AQUA, RED,
|
||||||
|
# LIGHT_PURPLE, YELLOW, WHITE
|
||||||
|
header_color = "GOLD"
|
||||||
|
|
||||||
|
# Body color (regular lines and bullet points)
|
||||||
|
body_color = "YELLOW"
|
||||||
|
|
||||||
|
# Strip emojis that Minecraft can't render (recommended: true)
|
||||||
|
strip_emojis = true
|
||||||
|
```
|
||||||
|
|
||||||
|
The formatter reads these at display time (not cached — config changes take effect on next /rules without restart).
|
||||||
|
|
||||||
|
### 5. DiscordFormatter.java — wire config reads
|
||||||
|
|
||||||
|
```java
|
||||||
|
// At top of formatRules():
|
||||||
|
ChatFormatting headerColor = parseColor(ServerRulesConfig.HEADER_COLOR.get(), ChatFormatting.GOLD);
|
||||||
|
ChatFormatting bodyColor = parseColor(ServerRulesConfig.BODY_COLOR.get(), ChatFormatting.YELLOW);
|
||||||
|
|
||||||
|
// Add helper method:
|
||||||
|
private static ChatFormatting parseColor(String name, ChatFormatting fallback) {
|
||||||
|
try {
|
||||||
|
return ChatFormatting.valueOf(name.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For emoji stripping, gate `convertEmojis()` on the config value:
|
||||||
|
```java
|
||||||
|
String processedText = ServerRulesConfig.STRIP_EMOJIS.get()
|
||||||
|
? convertEmojis(rawDiscordText)
|
||||||
|
: rawDiscordText;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. ServerRules.java — update mod ID references
|
||||||
|
- Any string `"serverrules"` → `"discordrules"`
|
||||||
|
- Any string `"Firefrost"` or `"FireFrost"` in user-visible text → remove or make generic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build Instructions
|
||||||
|
|
||||||
|
Same as Task #136 — use the Dev Panel build environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Java 21 for 1.21.1
|
||||||
|
use-java 21
|
||||||
|
cd /opt/mod-builds/firefrost-services/services/discord-rules/1.21.1
|
||||||
|
/opt/gradle-8.8/bin/gradle build
|
||||||
|
|
||||||
|
# Java 17 for 1.20.1
|
||||||
|
use-java 17
|
||||||
|
cd /opt/mod-builds/firefrost-services/services/discord-rules/1.20.1
|
||||||
|
/opt/gradle-8.8/bin/gradle build
|
||||||
|
|
||||||
|
# Java 8 for 1.16.5
|
||||||
|
use-java 8
|
||||||
|
cd /opt/mod-builds/firefrost-services/services/discord-rules/1.16.5
|
||||||
|
/opt/gradle-7.6.4/bin/gradle build
|
||||||
|
```
|
||||||
|
|
||||||
|
Output jars go in `build/libs/` — grab the one without `-sources` or `-dev`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
|
||||||
|
3 built jars in `services/discord-rules/`:
|
||||||
|
- `discord-rules-1.0.0-1.21.1-neoforge.jar`
|
||||||
|
- `discord-rules-1.0.0-1.20.1-forge.jar`
|
||||||
|
- `discord-rules-1.0.0-1.16.5-forge.jar`
|
||||||
|
|
||||||
|
Commit source + jars to `firefrost-services` main branch, then update `ACTIVE_CONTEXT.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Don't touch `services/rules-mod/` — that stays as Firefrost's internal version
|
||||||
|
- Default colors GOLD/YELLOW chosen to match Firefrost's visual identity (subtle brand nod)
|
||||||
|
- MIT license — maximizes community adoption
|
||||||
|
- Chronicler is writing the CurseForge project page copy in parallel
|
||||||
|
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Architectural Request
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**Topic:** Phase 11 Prerequisites — Need credentials and env values before Code can begin
|
||||||
|
|
||||||
|
## 1. Current State
|
||||||
|
Phase 11 spec is archived and understood. Code is ready to begin building the licensing system (database migration, Arbiter API routes, Blueprint extension changes).
|
||||||
|
|
||||||
|
## 2. The Blocker
|
||||||
|
Cannot start implementation without infrastructure values that only the Chronicler (via Command Center access) can provide.
|
||||||
|
|
||||||
|
## 3. Questions for Chronicler
|
||||||
|
|
||||||
|
1. **PostgreSQL credentials** — What are the connection details for arbiter_db on Command Center? (host, port, user, password — or confirm it's 127.0.0.1 with the existing Arbiter DB user)
|
||||||
|
2. **Existing Arbiter DB schema** — Can you dump the current table list from arbiter_db so I know what's already there? (`\dt` output)
|
||||||
|
3. **BuiltByBit resource IDs** — Have the two listings (Standard / Professional) been created yet? If so, what are BBB_STANDARD_RESOURCE_ID and BBB_PRO_RESOURCE_ID? If not, should Code proceed with placeholder values?
|
||||||
|
4. **Arbiter .env current state** — What env vars does Arbiter already have? (So I don't overwrite anything when adding the new ones)
|
||||||
|
5. **Discord role + channel setup** — Has the "ModpackChecker Customer" role been created on the Firefrost Discord? Does the bot have permission to assign roles?
|
||||||
|
6. **Deployment readiness** — Once Code writes the migration SQL and API routes, should I commit and push for Chronicler to deploy? Or is there a staging step first?
|
||||||
39
docs/code-bridge/requests/REQ-2026-04-12-phase11a-deploy.md
Normal file
39
docs/code-bridge/requests/REQ-2026-04-12-phase11a-deploy.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Architectural Request
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**Topic:** Deploy Phase 11A — MVC licensing migration + Arbiter routes
|
||||||
|
|
||||||
|
## 1. Current State
|
||||||
|
Phase 11A code is committed and pushed to `main` (fd50009). Migration SQL and API routes are ready.
|
||||||
|
|
||||||
|
## 2. The Blocker
|
||||||
|
Code cannot deploy to Command Center — Chronicler handles Arbiter deployments via Trinity Core.
|
||||||
|
|
||||||
|
## 3. Deployment Steps for Chronicler
|
||||||
|
|
||||||
|
1. Run migration on Command Center:
|
||||||
|
```
|
||||||
|
psql -U arbiter -d arbiter_db -f services/arbiter-3.0/migrations/138_mvc_licensing.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Deploy Arbiter code (standard pattern):
|
||||||
|
- Clone to /tmp
|
||||||
|
- Copy to /opt/arbiter-3.0
|
||||||
|
- Restart Arbiter service
|
||||||
|
- Verify health endpoint
|
||||||
|
|
||||||
|
3. Add these env vars to Arbiter .env (placeholders until BBB listings go live):
|
||||||
|
```
|
||||||
|
BBB_STANDARD_RESOURCE_ID=PLACEHOLDER_STANDARD
|
||||||
|
BBB_PRO_RESOURCE_ID=PLACEHOLDER_PRO
|
||||||
|
BBB_WEBHOOK_SECRET=PLACEHOLDER_SECRET
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Verify new endpoints respond:
|
||||||
|
```
|
||||||
|
curl https://discord-bot.firefrostgaming.com/api/mvc/latest-version
|
||||||
|
```
|
||||||
|
Expected: `{"version":"1.0.0","download_url":"..."}`
|
||||||
|
|
||||||
|
## 4. After Deployment
|
||||||
|
Once confirmed, Code will proceed to Phase 11B/C (Discord /verify-mvc command + customer role).
|
||||||
35
docs/code-bridge/requests/REQ-2026-04-12-phase11bc-deploy.md
Normal file
35
docs/code-bridge/requests/REQ-2026-04-12-phase11bc-deploy.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Architectural Request
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**Topic:** Deploy Phase 11B/C — /verify-mvc command + Discord role
|
||||||
|
|
||||||
|
## 1. Current State
|
||||||
|
Phase 11B/C code pushed (7c58cea). New `/verify-mvc` slash command ready. Needs Arbiter restart + Discord role creation.
|
||||||
|
|
||||||
|
## 2. The Blocker
|
||||||
|
Code cannot create Discord roles or restart Arbiter on Command Center.
|
||||||
|
|
||||||
|
## 3. Deployment Steps for Chronicler
|
||||||
|
|
||||||
|
1. **Create Discord role** — "ModpackChecker Customer" on Firefrost Gaming guild
|
||||||
|
- Color: whatever fits the brand (suggestion: green or teal)
|
||||||
|
- No special permissions needed — it's just a tag/gating role
|
||||||
|
- Note the role ID
|
||||||
|
|
||||||
|
2. **Update Arbiter .env** on Command Center:
|
||||||
|
```
|
||||||
|
MVC_CUSTOMER_ROLE_ID=<the role ID from step 1>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Deploy Arbiter code** (standard rsync pattern):
|
||||||
|
- New file: `src/discord/verifymvc.js`
|
||||||
|
- Modified: `src/discord/events.js`, `src/index.js`
|
||||||
|
- Restart service
|
||||||
|
|
||||||
|
4. **Verify:**
|
||||||
|
- Health check: `curl https://discord-bot.firefrostgaming.com/health`
|
||||||
|
- Check bot logs for "Successfully reloaded application (/) commands"
|
||||||
|
- Test `/verify-mvc` appears in Discord command list
|
||||||
|
|
||||||
|
## 4. After Deployment
|
||||||
|
Code proceeds to Phase 11D — Blueprint extension license activation UI, phone-home cron, and tier gating.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Architectural Request
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**Topic:** Debug dashboard badge injection — need ServerRow.tsx contents
|
||||||
|
|
||||||
|
## 1. Current State
|
||||||
|
UpdateBadge.tsx is copied correctly, but the sed injection into ServerRow.tsx may not be matching. Need to see what's actually on disk.
|
||||||
|
|
||||||
|
## 2. Questions — Please Run These on Dev Panel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Does ServerRow.tsx exist?
|
||||||
|
find /var/www/pterodactyl/resources/scripts -name "ServerRow.tsx" 2>/dev/null
|
||||||
|
|
||||||
|
# 2. Is UpdateBadge import present?
|
||||||
|
grep -n "UpdateBadge" /var/www/pterodactyl/resources/scripts/components/dashboard/ServerRow.tsx 2>/dev/null
|
||||||
|
|
||||||
|
# 3. What does the server name line look like?
|
||||||
|
grep -n "server.name" /var/www/pterodactyl/resources/scripts/components/dashboard/ServerRow.tsx 2>/dev/null
|
||||||
|
|
||||||
|
# 4. Was yarn build run after the injection?
|
||||||
|
ls -la /var/www/pterodactyl/public/assets/manifest.json 2>/dev/null
|
||||||
|
|
||||||
|
# 5. Any JS errors visible?
|
||||||
|
# Michael: open browser console (F12) on dashboard, look for red errors
|
||||||
|
```
|
||||||
|
|
||||||
|
The sed pattern `{server.name}</p>` has to match exactly. If the actual JSX is different (e.g. `{server.name}</span>` or uses a different variable), the injection silently fails.
|
||||||
|
|
||||||
|
Also: **did `yarn build:production` run after build.sh?** The React components need to be compiled for the frontend to pick them up.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Architectural Request
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**Topic:** Dashboard badges blocked by Blueprint CSS module build errors — decision needed
|
||||||
|
|
||||||
|
## 1. Current State
|
||||||
|
Badge injection into ServerRow.tsx is correct. But `yarn build:production` fails on 7 pre-existing CSS module errors from Blueprint beta — not caused by our code.
|
||||||
|
|
||||||
|
## 2. The Blocker
|
||||||
|
Blueprint beta-2026-01's webpack config doesn't have a CSS module loader configured. This breaks `yarn build:production` for any panel with Blueprint installed. Our badge code can't compile.
|
||||||
|
|
||||||
|
## 3. Decision for Michael
|
||||||
|
|
||||||
|
**Option A: Skip badges for v1.0.0 launch** (recommended)
|
||||||
|
- Admin page + console widget both work
|
||||||
|
- Badges are nice-to-have, not core functionality
|
||||||
|
- Add badges in v1.1.0 when Blueprint fixes their build or we find a workaround
|
||||||
|
- Zero risk to launch timeline
|
||||||
|
|
||||||
|
**Option B: Patch webpack config**
|
||||||
|
- Add `css-loader` with modules support to Blueprint's webpack
|
||||||
|
- Risk: could break other Blueprint components, and gets overwritten on Blueprint updates
|
||||||
|
- Not recommended 52 hours before launch
|
||||||
|
|
||||||
|
Which way do you want to go?
|
||||||
64
docs/code-bridge/requests/REQ-2026-04-12-phase11d-deploy.md
Normal file
64
docs/code-bridge/requests/REQ-2026-04-12-phase11d-deploy.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Architectural Request
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**Topic:** Deploy Phase 11D to Dev Panel — Blueprint license integration
|
||||||
|
|
||||||
|
## 1. Current State
|
||||||
|
Phase 11D code pushed (8872f67). Blueprint extension has license activation UI, phone-home cron, and tier gating. Ready for testing on Dev Panel.
|
||||||
|
|
||||||
|
## 2. Deploy Target
|
||||||
|
**Dev Panel (64.50.188.128)** — NOT live Panel VPS.
|
||||||
|
|
||||||
|
## 3. Files Changed
|
||||||
|
|
||||||
|
**New files:**
|
||||||
|
- `blueprint-extension/app/Services/LicenseService.php`
|
||||||
|
- `blueprint-extension/app/Console/Commands/ValidateLicense.php`
|
||||||
|
|
||||||
|
**Modified files:**
|
||||||
|
- `blueprint-extension/admin/controller.php` — added LicenseService DI, license activation/deactivation handling
|
||||||
|
- `blueprint-extension/admin/view.blade.php` — license UI, status banners, dynamic tier gating
|
||||||
|
|
||||||
|
## 4. Dependencies
|
||||||
|
- No new Composer packages — uses Laravel's built-in `Http` facade (already available)
|
||||||
|
- No database migrations needed (license state stored in Blueprint's dbGet/dbSet)
|
||||||
|
|
||||||
|
## 5. Deployment Steps
|
||||||
|
|
||||||
|
1. Rebuild the .blueprint package:
|
||||||
|
```bash
|
||||||
|
cd /opt/mod-builds/firefrost-services/services/modpack-version-checker/blueprint-extension
|
||||||
|
bash build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install on Dev Panel:
|
||||||
|
```bash
|
||||||
|
blueprint -install modpackchecker
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Register the daily cron (add to Panel's crontab):
|
||||||
|
```
|
||||||
|
0 4 * * * cd /var/www/pterodactyl && php artisan mvc:validate >> /dev/null 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
4. For immediate testing, run manually:
|
||||||
|
```bash
|
||||||
|
cd /var/www/pterodactyl && php artisan mvc:validate
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. What "Working" Looks Like
|
||||||
|
|
||||||
|
1. **Admin page loads** — visit `/admin/extensions/modpackchecker`, no errors
|
||||||
|
2. **License section visible** — "BuiltByBit Order ID" text input + gray "Not Activated" badge
|
||||||
|
3. **Activate test** — enter a test order ID, click Save. Should show error "License not found" (expected — no real license exists yet). Confirms Arbiter connectivity.
|
||||||
|
4. **Pro fields locked** — Check Interval dropdown and Discord Webhook URL should be disabled (grayed out)
|
||||||
|
5. **Artisan command runs** — `php artisan mvc:validate` outputs "No license configured" (expected for fresh install)
|
||||||
|
6. **No PHP errors in logs** — `tail -f /var/www/pterodactyl/storage/logs/laravel.log`
|
||||||
|
|
||||||
|
## 7. Smoke Test with Real License
|
||||||
|
To fully test the flow, Chronicler can provision a test license directly in the DB:
|
||||||
|
```sql
|
||||||
|
INSERT INTO mvc_licenses (order_id, buyer_id, tier, max_activations)
|
||||||
|
VALUES ('TEST-001', 'chronicler', 'professional', 5);
|
||||||
|
```
|
||||||
|
Then enter `TEST-001` in the admin UI — should activate, show green badge, unlock pro fields.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Architectural Request
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**Topic:** Phase 11E scope — what does the GitBook knowledge base need?
|
||||||
|
|
||||||
|
## 1. Current State
|
||||||
|
Phases 11A–11D complete. Phase 11E is "GitBook knowledge base migration" but the spec doesn't detail what content is needed or where GitBook is hosted.
|
||||||
|
|
||||||
|
## 2. Questions for Chronicler
|
||||||
|
|
||||||
|
1. **Is there an existing GitBook instance?** If so, what's the URL and how does Code push content to it? (API? Git repo? Manual paste?)
|
||||||
|
2. **Or is this Markdown docs that get uploaded to GitBook later?** If so, Code can write the docs as Markdown files in the repo and Chronicler handles the GitBook publishing.
|
||||||
|
3. **What pages are needed?** Suggested structure:
|
||||||
|
- Getting Started (installation, activation)
|
||||||
|
- Configuration (CurseForge API key, egg variables, file detection)
|
||||||
|
- Dashboard Badge (how it works, what the colors mean)
|
||||||
|
- Console Widget (manual checks, rate limits)
|
||||||
|
- Professional Tier Features (auto-cron, Discord webhooks, FTB)
|
||||||
|
- Troubleshooting (common errors, support contact)
|
||||||
|
- License & Activation (how to activate, deactivation, grace period)
|
||||||
|
4. **Should Code write these docs now, or skip to 11F (BuiltByBit listing) which is more launch-critical?**
|
||||||
|
|
||||||
|
54 hours to launch — happy to prioritize whatever moves the needle most.
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
# Feature Request: Arbiter Native Discord Role Management
|
||||||
|
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Topic:** Replace Carlbot with native Arbiter button roles, welcome messages, and automated get-roles message lifecycle
|
||||||
|
**Priority:** POST-LAUNCH — build and test in parallel with Carlbot, cut over week of April 20
|
||||||
|
**Filed by:** Chronicler #86
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
Carlbot currently handles three things for Firefrost Gaming:
|
||||||
|
1. Welcome messages on member join
|
||||||
|
2. Reaction roles in #get-roles
|
||||||
|
3. Wanderer role assignment on join
|
||||||
|
|
||||||
|
Every time a Minecraft server is created (/createserver) or deleted (/delserver), staff must manually update Carlbot's reaction role config and add/remove emoji from the #get-roles message. This is error-prone and completely automatable.
|
||||||
|
|
||||||
|
**Goal:** Arbiter owns all three functions natively. When Pterodactyl fires a server lifecycle webhook, the #get-roles message updates automatically — no manual steps.
|
||||||
|
|
||||||
|
**Architecture review:** Two rounds of Gemini consultation completed.
|
||||||
|
- docs/consultations/gemini-arbiter-discord-roles-round-1-2026-04-13.md (in ops manual)
|
||||||
|
- docs/consultations/gemini-arbiter-discord-roles-round-2-2026-04-13.md (in ops manual)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. New File: src/discord/client.js
|
||||||
|
|
||||||
|
Module-level singleton for the discord.js Client. Shared across the entire app.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { Client, GatewayIntentBits } = require('discord.js');
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
intents: [
|
||||||
|
GatewayIntentBits.Guilds,
|
||||||
|
GatewayIntentBits.GuildMembers,
|
||||||
|
GatewayIntentBits.GuildMessages,
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
client.once('ready', () => {
|
||||||
|
console.log(`[Discord] Gateway connected as ${client.user.tag}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = client;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Modify: src/index.js (startup)
|
||||||
|
|
||||||
|
Start Express and Discord concurrently — do NOT block Express on Discord ready.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const client = require('./discord/client');
|
||||||
|
const discordEvents = require('./discord/events');
|
||||||
|
|
||||||
|
discordEvents.register(client);
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`[Arbiter] Express listening on port ${PORT}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.login(process.env.DISCORD_BOT_TOKEN).catch(err => {
|
||||||
|
console.error('[Discord] Gateway login failed:', err);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
If a Pterodactyl webhook fires before Gateway is ready, check `client.isReady()` and return 503 if not.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. New File: src/discord/events.js
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { sendWelcomeMessage } = require('./welcome');
|
||||||
|
const { handleInteraction } = require('./interactions');
|
||||||
|
|
||||||
|
function register(client) {
|
||||||
|
client.on('guildMemberAdd', async (member) => {
|
||||||
|
if (process.env.WELCOME_MESSAGES_ENABLED !== 'true') return;
|
||||||
|
await sendWelcomeMessage(member);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('interactionCreate', async (interaction) => {
|
||||||
|
await handleInteraction(interaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { register };
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. New File: src/discord/welcome.js
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const WELCOME_CHANNEL_ID = process.env.DISCORD_WELCOME_CHANNEL_ID;
|
||||||
|
|
||||||
|
async function sendWelcomeMessage(member) {
|
||||||
|
try {
|
||||||
|
const channel = await member.guild.channels.fetch(WELCOME_CHANNEL_ID);
|
||||||
|
if (!channel) return;
|
||||||
|
await channel.send({
|
||||||
|
embeds: [{
|
||||||
|
title: `Welcome to Firefrost Gaming, ${member.user.username}! 🔥❄️`,
|
||||||
|
description: `Head to <#${process.env.DISCORD_GET_ROLES_CHANNEL_ID}> to grab your server roles!`,
|
||||||
|
color: 0xFF6B35,
|
||||||
|
thumbnail: { url: member.user.displayAvatarURL() },
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Welcome] Failed to send welcome message:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { sendWelcomeMessage };
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. New File: src/discord/getRolesMessage.js
|
||||||
|
|
||||||
|
Manages the persistent #get-roles button message. Called by webhook.js on /createserver and /delserver.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const client = require('./client');
|
||||||
|
const db = require('../db');
|
||||||
|
|
||||||
|
const GET_ROLES_CHANNEL_ID = process.env.DISCORD_GET_ROLES_CHANNEL_ID;
|
||||||
|
|
||||||
|
async function updateGetRolesMessage(servers) {
|
||||||
|
if (!client.isReady()) {
|
||||||
|
console.warn('[GetRoles] Discord client not ready — skipping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = await client.channels.fetch(GET_ROLES_CHANNEL_ID);
|
||||||
|
const embed = buildEmbed(servers);
|
||||||
|
const components = buildButtons(servers);
|
||||||
|
const storedMessageId = await db.getSetting('discord_get_roles_message_id');
|
||||||
|
|
||||||
|
if (storedMessageId) {
|
||||||
|
try {
|
||||||
|
await channel.messages.edit(storedMessageId, { embeds: [embed], components });
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 10008) throw err;
|
||||||
|
console.warn('[GetRoles] Stored message not found, reposting...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = await channel.send({ embeds: [embed], components });
|
||||||
|
await db.setSetting('discord_get_roles_message_id', message.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEmbed(servers) {
|
||||||
|
return {
|
||||||
|
title: '🎮 Choose Your Servers',
|
||||||
|
description: servers.length > 0
|
||||||
|
? 'Click a button to get access to a server channel. Click again to remove it.'
|
||||||
|
: 'No servers are currently active.',
|
||||||
|
color: 0x4ECDC4,
|
||||||
|
footer: { text: 'Firefrost Gaming — Role Assignment' },
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildButtons(servers) {
|
||||||
|
if (servers.length === 0) return [];
|
||||||
|
const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
|
||||||
|
const rows = [];
|
||||||
|
let currentRow = new ActionRowBuilder();
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for (const server of servers) {
|
||||||
|
if (count > 0 && count % 5 === 0) {
|
||||||
|
rows.push(currentRow);
|
||||||
|
currentRow = new ActionRowBuilder();
|
||||||
|
}
|
||||||
|
currentRow.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`toggle_role_${server.roleId}`)
|
||||||
|
.setLabel(server.name)
|
||||||
|
.setStyle(ButtonStyle.Secondary)
|
||||||
|
.setEmoji(server.emoji || '🎮')
|
||||||
|
);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % 5 !== 0 || count === 0) rows.push(currentRow);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { updateGetRolesMessage };
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. New File: src/discord/interactions.js
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function handleInteraction(interaction) {
|
||||||
|
if (!interaction.isButton()) return;
|
||||||
|
if (interaction.customId.startsWith('toggle_role_')) {
|
||||||
|
await handleRoleToggle(interaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRoleToggle(interaction) {
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
const roleId = interaction.customId.replace('toggle_role_', '');
|
||||||
|
const member = interaction.member;
|
||||||
|
const guild = interaction.guild;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const role = await guild.roles.fetch(roleId);
|
||||||
|
if (!role) {
|
||||||
|
await interaction.editReply({ content: '⚠️ Role not found. Please contact an admin.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hasRole = member.roles.cache.has(roleId);
|
||||||
|
if (hasRole) {
|
||||||
|
await member.roles.remove(role);
|
||||||
|
await interaction.editReply({ content: `✅ Removed the **${role.name}** role.` });
|
||||||
|
} else {
|
||||||
|
await member.roles.add(role);
|
||||||
|
await interaction.editReply({ content: `✅ You now have the **${role.name}** role!` });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Interactions] Role toggle failed:', err);
|
||||||
|
await interaction.editReply({ content: '⚠️ Something went wrong. Please try again.' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { handleInteraction };
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Modify: src/routes/webhook.js
|
||||||
|
|
||||||
|
After successful server creation or deletion, call updateGetRolesMessage with the current active server list.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Database: Settings Table
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
key VARCHAR(255) PRIMARY KEY,
|
||||||
|
value TEXT
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Add getSetting/setSetting helpers to DB module.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. New .env Variables
|
||||||
|
|
||||||
|
```
|
||||||
|
DISCORD_GET_ROLES_CHANNEL_ID=1403980899464384572
|
||||||
|
DISCORD_WELCOME_CHANNEL_ID=1403980049530490911
|
||||||
|
WELCOME_MESSAGES_ENABLED=false
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Server Role ID Reference
|
||||||
|
|
||||||
|
| Server Name | Role ID |
|
||||||
|
|-------------|---------|
|
||||||
|
| All The Mods: To the Sky | 1491028496284258304 |
|
||||||
|
| Stoneblock 4 | 1491028769132253274 |
|
||||||
|
| Society: Sunlit Valley | 1491028885981102270 |
|
||||||
|
| All The Mons | 1491029000108380170 |
|
||||||
|
| Mythcraft 5 | 1491029070190870548 |
|
||||||
|
| Beyond Depth | 1491029215963906149 |
|
||||||
|
| Beyond Ascension | 1491029284159094904 |
|
||||||
|
| Wold's Vaults | 1491029373640376330 |
|
||||||
|
| Otherworld [Dungeons & Dragons] | 1491029454011629749 |
|
||||||
|
| DeceasedCraft | 1491029615739801800 |
|
||||||
|
| Submerged 2 | 1491029708878647356 |
|
||||||
|
| Sneak's Pirate Pack | 1491029809273508112 |
|
||||||
|
| Cottage Witch | 1491029870002569298 |
|
||||||
|
| Homestead | 1491030015746510939 |
|
||||||
|
| Farm Crossing 6 | 1493352900997415134 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Carlbot Cutover Sequence (Week of April 20)
|
||||||
|
|
||||||
|
DO NOT execute before April 15 launch.
|
||||||
|
|
||||||
|
1. Confirm Arbiter role system verified in test channel
|
||||||
|
2. Deploy final build to production
|
||||||
|
3. Disable Carlbot Reaction Roles for #get-roles in Carlbot dashboard
|
||||||
|
4. Delete old Carlbot #get-roles message
|
||||||
|
5. Trigger updateGetRolesMessage() to post new button message
|
||||||
|
6. Disable Carlbot Welcome module
|
||||||
|
7. Set WELCOME_MESSAGES_ENABLED=true in .env
|
||||||
|
8. Restart arbiter-3
|
||||||
|
9. Verify welcome fires on test join
|
||||||
|
10. Remove Carlbot from server
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rate Limit Handling
|
||||||
|
|
||||||
|
Native retry on 429. No queue needed at current scale.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes for Code
|
||||||
|
|
||||||
|
- Feature flag (WELCOME_MESSAGES_ENABLED) is critical — don't skip it
|
||||||
|
- 404 fallback in updateGetRolesMessage is important — admins will delete that message
|
||||||
|
- Keep Discord client strictly in src/discord/
|
||||||
|
- testserver role (1491487727928217815) exists — do NOT include in button list
|
||||||
|
- This is post-launch work — no rush, but nice change from MVC 😄
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# REQ-2026-04-13-missing-modpack-installations-migration
|
||||||
|
|
||||||
|
**Filed by:** Chronicler #85
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Priority:** HIGH — blocks Dev Panel deploy, will block any fresh install
|
||||||
|
**Status:** OPEN
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The `modpack_installations` table exists on live panel (panel-vps) but has **no migration file** in the repo. It was created directly on the server at some point and never committed.
|
||||||
|
|
||||||
|
This caused an API error on Dev Panel during today's deploy — the table simply didn't exist. Chronicler manually created it via raw SQL as a temporary fix to unblock testing.
|
||||||
|
|
||||||
|
## Live Panel Schema (confirmed via Trinity Core)
|
||||||
|
|
||||||
|
```
|
||||||
|
Field | Type | Null | Key | Default | Extra
|
||||||
|
provider | varchar(191) | NO | | NULL |
|
||||||
|
modpack_id | varchar(191) | NO | | NULL |
|
||||||
|
server_id | bigint(20) unsigned | NO | PRI | NULL |
|
||||||
|
finalized | tinyint(1) | NO | | 0 |
|
||||||
|
```
|
||||||
|
|
||||||
|
PRIMARY KEY on `server_id`. Engine InnoDB, charset utf8mb4.
|
||||||
|
|
||||||
|
## What Code Needs to Do
|
||||||
|
|
||||||
|
Create a proper Laravel migration file:
|
||||||
|
|
||||||
|
```
|
||||||
|
services/modpack-version-checker/blueprint-extension/database/migrations/2026_04_13_000001_create_modpack_installations_table.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Using the schema above. Standard Blueprint/Laravel migration format matching the existing migration files in that directory.
|
||||||
|
|
||||||
|
## Why It Matters
|
||||||
|
|
||||||
|
Without this, any fresh Blueprint install (new panel, wipe and reinstall) will throw:
|
||||||
|
`SQLSTATE[42S02]: Table 'panel.modpack_installations' doesn't exist`
|
||||||
|
|
||||||
|
This will affect every BuiltByBit customer who purchases ModpackChecker.
|
||||||
|
|
||||||
|
## After Code Pushes
|
||||||
|
|
||||||
|
Chronicler will pull on Dev Panel and confirm the migration file is present before live panel deploy.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Bug Fix + Feature Request: Rules Mod Config Reset + Versioning
|
||||||
|
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Topic:** Fix Forge config reset on startup, introduce version control
|
||||||
|
**Priority:** HIGH — rules mod broken on Otherworld
|
||||||
|
**Filed by:** Chronicler #86
|
||||||
|
**Status:** RESOLVED in v1.0.2
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Bug Follow-up v2: COMMON Config File Not Read at Runtime
|
||||||
|
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Topic:** firefrostrules-common.toml has correct values but mod still serves default rules
|
||||||
|
**Priority:** HIGH — Otherworld still broken
|
||||||
|
**Filed by:** Chronicler #86
|
||||||
|
**Related to:** REQ-2026-04-13-rules-mod-config-still-resetting.md
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What We Did
|
||||||
|
|
||||||
|
1. Deployed firefrostrules-1.0.2-1.20.1-forge.jar to Otherworld (NC1, volume d4790f45)
|
||||||
|
2. Deleted old world/serverconfig/firefrostrules-server.toml
|
||||||
|
3. Started server — generated config/firefrostrules-common.toml with defaults
|
||||||
|
4. Stopped server — wrote real values to config/firefrostrules-common.toml
|
||||||
|
5. Restarted — config file still has correct values (confirmed)
|
||||||
|
6. Restarted again — config file still has correct values (confirmed)
|
||||||
|
|
||||||
|
Config persistence is fixed. ✅
|
||||||
|
|
||||||
|
## What's Still Wrong
|
||||||
|
|
||||||
|
Player ran `/rules` and got the hardcoded default rules, not Discord content.
|
||||||
|
|
||||||
|
Log evidence:
|
||||||
|
|
||||||
|
```
|
||||||
|
[22:42:38] [modloading-worker-0/INFO] [com.firefrostgaming.rules.ServerRules/]: Firefrost Rules Mod Initialized.
|
||||||
|
[22:43:08] [main/INFO] [com.firefrostgaming.rules.ServerRules/]: Registered /rules command.
|
||||||
|
[22:43:58] [Server thread/INFO] [minecraft/MinecraftServer/]: [Fire] Server Rules
|
||||||
|
Please check Discord for the full rules list.
|
||||||
|
```
|
||||||
|
|
||||||
|
Two problems:
|
||||||
|
|
||||||
|
1. **Loading handler never fired** — Code's `onConfigLoaded` handler should have logged "Rules mod config loaded successfully. Channel: 1493228237860638770" — it's completely absent from logs. The handler is not executing.
|
||||||
|
|
||||||
|
2. **Default rules served** — `/rules` returned hardcoded defaults, meaning `ServerRulesConfig.BOT_TOKEN.get()` is returning `YOUR_TOKEN_HERE` at runtime even though the file has the real token.
|
||||||
|
|
||||||
|
## What This Means
|
||||||
|
|
||||||
|
The config file has correct values but `ServerRulesConfig.SPEC` is not reading from it. The `ModConfig.Type.COMMON` registration may not be correctly linking the spec to the file on disk, or the config values are being read before the file is loaded.
|
||||||
|
|
||||||
|
## Hypothesis
|
||||||
|
|
||||||
|
In Forge 1.20.1, `ModConfig.Type.COMMON` configs are loaded from `config/<modid>-common.toml`. But the filename Forge generates may need to match exactly what the spec expects. Check:
|
||||||
|
|
||||||
|
1. What filename is Forge actually generating? Is it `firefrostrules-common.toml` or something else like `firefrostrules-server.toml` even with COMMON type?
|
||||||
|
2. Is `ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ServerRulesConfig.SPEC)` being called before or after the config file is read?
|
||||||
|
3. Is there a `ModConfigEvent.Loading` listener that confirms the spec is bound to the correct file?
|
||||||
|
|
||||||
|
## Current File State on Server
|
||||||
|
|
||||||
|
```
|
||||||
|
Path: /var/lib/pterodactyl/volumes/d4790f45-b1bc-43b8-98c4-425238387ee3/config/firefrostrules-common.toml
|
||||||
|
bot_token = "MTQ4NzA4MDE2Njk2OTU3NzUwMg.GU5EsT.mqBwo7XUHsciN9jNy9OygTRkaMZ9qJ2tHw7HbI"
|
||||||
|
channel_id = "1493228237860638770"
|
||||||
|
message_id = "1493228916998013101"
|
||||||
|
```
|
||||||
|
|
||||||
|
File is correct. Mod is not reading it.
|
||||||
|
|
||||||
|
## What Code Needs to Do
|
||||||
|
|
||||||
|
1. Add a debug log immediately after `ServerRulesConfig.BOT_TOKEN.get()` in `RulesCommand.java` to print the actual value being read at command execution time
|
||||||
|
2. Verify the `ModConfigEvent.Loading` listener is actually being registered and firing
|
||||||
|
3. Check if there's a timing issue — config might need to be read after `FMLServerStartedEvent` rather than at command time
|
||||||
|
4. Verify COMMON type generates correct filename in 1.20.1
|
||||||
|
|
||||||
|
## Applies To
|
||||||
|
|
||||||
|
1.20.1 confirmed broken. 1.21.1 and 1.16.5 status unknown — may have same issue.
|
||||||
|
Bump to 1.0.3 once fixed. Update CHANGELOG.md.
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Bug Follow-up v3: /rules Still Returns Hardcoded Defaults Despite Config Loading
|
||||||
|
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Topic:** Config loads correctly (Loading handler confirmed), but /rules still returns hardcoded default text
|
||||||
|
**Priority:** HIGH — Otherworld still broken
|
||||||
|
**Filed by:** Chronicler #86
|
||||||
|
**Related to:** REQ-2026-04-13-rules-mod-config-not-read-at-runtime.md
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
v1.0.3 deployed to Otherworld. Config loading confirmed:
|
||||||
|
|
||||||
|
```
|
||||||
|
[22:59:13] [main/INFO] [com.firefrostgaming.rules.ServerRules/]: Rules mod config loaded successfully. Channel: 1493228237860638770
|
||||||
|
```
|
||||||
|
|
||||||
|
Config file on disk has correct values. Survives restarts. ✅
|
||||||
|
|
||||||
|
## What's Still Wrong
|
||||||
|
|
||||||
|
`/rules` run twice after server fully loaded — both return hardcoded defaults:
|
||||||
|
|
||||||
|
```
|
||||||
|
[23:00:54] [Server thread/INFO] [minecraft/MinecraftServer]: [Fire] Server Rules
|
||||||
|
1. Be respectful to all players.
|
||||||
|
2. No griefing or cheating.
|
||||||
|
3. Follow staff instructions.
|
||||||
|
Please check Discord for the full rules list.
|
||||||
|
|
||||||
|
[23:01:19] Same result.
|
||||||
|
```
|
||||||
|
|
||||||
|
## What The Logs Show
|
||||||
|
|
||||||
|
No `DiscordFetcher` log output at all — no 401, no 403, no fetch attempt, no fallback warning. The fetcher is completely silent. Only the hardcoded output appears.
|
||||||
|
|
||||||
|
## What This Means
|
||||||
|
|
||||||
|
`DiscordFetcher` is not being called, OR it's being called but returning null/empty silently and falling back to defaults without logging.
|
||||||
|
|
||||||
|
Two likely causes:
|
||||||
|
|
||||||
|
**Cause A — isMessageIdValid() returning false:**
|
||||||
|
`ServerRulesConfig.isMessageIdValid()` checks that message_id matches `^\d{17,20}$`. If the config value is being read with surrounding quotes or whitespace, the regex fails and the command returns defaults without attempting the fetch.
|
||||||
|
|
||||||
|
Add a debug log immediately in `RulesCommand.java` before the validity check:
|
||||||
|
```java
|
||||||
|
LOGGER.info("Rules command: token={}, channel={}, messageId={}",
|
||||||
|
ServerRulesConfig.BOT_TOKEN.get(),
|
||||||
|
ServerRulesConfig.CHANNEL_ID.get(),
|
||||||
|
ServerRulesConfig.MESSAGE_ID.get());
|
||||||
|
LOGGER.info("isMessageIdValid={}", ServerRulesConfig.isMessageIdValid());
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause B — DiscordFetcher silently returning null:**
|
||||||
|
The async fetch may be completing but returning null or empty string, and `RulesCommand` falls back to defaults without logging. Add explicit logging in `DiscordFetcher` for every code path — success, failure, null return.
|
||||||
|
|
||||||
|
## What Code Needs To Do
|
||||||
|
|
||||||
|
1. Add the debug log in `RulesCommand.java` showing actual runtime values of all three config fields
|
||||||
|
2. Add explicit logging in `DiscordFetcher` for: fetch attempt started, HTTP status received, response body preview, null/empty result
|
||||||
|
3. Bump to v1.0.4, push
|
||||||
|
|
||||||
|
Chronicler will pull v1.0.4, deploy to Otherworld, and report what the new log lines show.
|
||||||
|
|
||||||
|
## Server Info
|
||||||
|
|
||||||
|
- Otherworld, NC1, volume `d4790f45-b1bc-43b8-98c4-425238387ee3`
|
||||||
|
- Mod version: firefrostrules-1.0.3-1.20.1-forge.jar
|
||||||
|
- Config: `/var/lib/pterodactyl/volumes/d4790f45-b1bc-43b8-98c4-425238387ee3/config/firefrostrules-common.toml`
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# REQ-2026-04-13-status-route-mismatch
|
||||||
|
|
||||||
|
**Filed by:** Chronicler #85
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Priority:** HIGH — widget does not auto-load on page open
|
||||||
|
**Status:** OPEN
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The widget's `useEffect` fires on page load and calls:
|
||||||
|
```
|
||||||
|
GET /api/client/extensions/modpackchecker/servers/{uuid}/status
|
||||||
|
```
|
||||||
|
|
||||||
|
But the registered route is:
|
||||||
|
```
|
||||||
|
GET /api/client/extensions/modpackchecker/status
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: `useEffect` hits a 404 on load → `catch(() => setData(null))` fires → widget shows nothing. Only the manual refresh button works because it hits `/check` (POST) which IS registered correctly.
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
Route list from Dev Panel:
|
||||||
|
```
|
||||||
|
GET|HEAD api/client/extensions/modpackchecker/status ModpackAPIController@...
|
||||||
|
POST api/client/extensions/modpackchecker/servers/{server}/check ModpackAPIController@...
|
||||||
|
```
|
||||||
|
|
||||||
|
Widget call in `wrapper.tsx` line 46:
|
||||||
|
```js
|
||||||
|
http.get(`/api/client/extensions/modpackchecker/servers/${uuid}/status`)
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Needs to Fix
|
||||||
|
|
||||||
|
Either:
|
||||||
|
- **Option A:** Update the route registration to match the widget: `servers/{server}/status`
|
||||||
|
- **Option B:** Update the widget's `useEffect` to call the existing route (passing uuid as a query param)
|
||||||
|
|
||||||
|
Option A is cleaner — keeps the route pattern consistent with `/check` and future endpoints.
|
||||||
|
|
||||||
|
## Files Involved
|
||||||
|
|
||||||
|
- Route registration: check `routes/` directory in blueprint extension
|
||||||
|
- Controller: `app/Http/Controllers/ModpackAPIController.php` — status method may need `$server` parameter
|
||||||
|
- Widget: `views/server/wrapper.tsx` line 46 (may not need changes if Option A)
|
||||||
|
|
||||||
|
## After Code Pushes
|
||||||
|
|
||||||
|
Chronicler will pull, redeploy PHP + routes to Dev Panel, verify widget auto-loads on page open.
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Architectural Request
|
||||||
|
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Topic:** Consolidated v1.1.0 deploy — all 5 priorities complete
|
||||||
|
|
||||||
|
## 1. What Changed
|
||||||
|
|
||||||
|
### New Migration
|
||||||
|
- `2026_04_13_000000_add_file_id_and_ignored.php` — adds `current_file_id`, `latest_file_id`, `is_ignored`
|
||||||
|
- `2026_04_12_000000_add_detection_columns.php` — adds `detection_method`, `is_user_overridden`
|
||||||
|
|
||||||
|
### Modified PHP Files
|
||||||
|
- `CheckModpackUpdates.php` — hybrid detection (modpack_installations → egg → file → BCC log), file ID comparison, date-time seeding, is_ignored skip
|
||||||
|
- `ModpackApiService.php` — all platforms return `file_id`, new `fetchFileHistory()` for CurseForge/Modrinth/FTB
|
||||||
|
- `ModpackAPIController.php` — 4 new endpoints (serverStatus, releases, calibrate, toggleIgnore), modpack_installations in manualCheck
|
||||||
|
- `routes/client.php` — 4 new routes
|
||||||
|
|
||||||
|
### Modified TSX Files
|
||||||
|
- `views/server/wrapper.tsx` — zero-click widget with recalibrate dropdown + ignore button
|
||||||
|
|
||||||
|
## 2. Deployment Steps
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Pull latest
|
||||||
|
cd /path/to/firefrost-services && git pull
|
||||||
|
|
||||||
|
# 2. Copy PHP files
|
||||||
|
cp blueprint-extension/app/Console/Commands/CheckModpackUpdates.php /var/www/pterodactyl/app/Console/Commands/
|
||||||
|
cp blueprint-extension/app/Services/ModpackApiService.php /var/www/pterodactyl/app/Services/
|
||||||
|
cp blueprint-extension/app/Http/Controllers/ModpackAPIController.php /var/www/pterodactyl/app/Http/Controllers/
|
||||||
|
cp blueprint-extension/routes/client.php [blueprint routes path]
|
||||||
|
|
||||||
|
# 3. Run migrations
|
||||||
|
cd /var/www/pterodactyl && php artisan migrate
|
||||||
|
|
||||||
|
# 4. Rebuild Blueprint (copies TSX + runs yarn build)
|
||||||
|
blueprint -install modpackchecker
|
||||||
|
|
||||||
|
# 5. Clear caches
|
||||||
|
php artisan optimize:clear
|
||||||
|
|
||||||
|
# 6. Test
|
||||||
|
php artisan modpackchecker:check
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Verification
|
||||||
|
|
||||||
|
1. `php artisan modpackchecker:check` — should detect 19+ servers via modpack_installations
|
||||||
|
2. Dashboard badges show on server cards
|
||||||
|
3. Console widget loads automatically (no click needed)
|
||||||
|
4. Console widget shows version comparison (current → latest)
|
||||||
|
5. Calibrate button shows release dropdown
|
||||||
|
6. Ignore button hides non-modpack servers
|
||||||
|
|
||||||
|
## 4. Deploy to Both Panels
|
||||||
|
- Dev Panel (64.50.188.128) first
|
||||||
|
- Live Panel (45.94.168.138) after verification
|
||||||
126
docs/code-bridge/requests/REQ-2026-04-14-pwa-phase1.md
Normal file
126
docs/code-bridge/requests/REQ-2026-04-14-pwa-phase1.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Code Request — Trinity Console PWA Phase 1
|
||||||
|
|
||||||
|
**Filed by:** Chronicler #88
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
**Priority:** Medium
|
||||||
|
**Task DB:** task_number 117
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What To Build
|
||||||
|
|
||||||
|
Make Trinity Console installable as a Progressive Web App. Phase 1 only —
|
||||||
|
foundation that enables "Add to Home Screen" on Android and iOS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files To Create
|
||||||
|
|
||||||
|
### 1. `src/public/manifest.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Trinity Console",
|
||||||
|
"short_name": "Trinity",
|
||||||
|
"description": "Firefrost Gaming Operations Console",
|
||||||
|
"start_url": "/admin",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#1a1a1a",
|
||||||
|
"theme_color": "#06b6d4",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/images/trinity-icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/images/trinity-icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `src/public/sw.js` (Service Worker)
|
||||||
|
|
||||||
|
Cache static assets only. Never cache admin API responses or HTMX partials.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const CACHE_NAME = 'trinity-console-v1';
|
||||||
|
const STATIC_ASSETS = [
|
||||||
|
'/css/app.css',
|
||||||
|
'/manifest.json'
|
||||||
|
];
|
||||||
|
|
||||||
|
self.addEventListener('install', event => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE_NAME).then(cache => cache.addAll(STATIC_ASSETS))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', event => {
|
||||||
|
// Only cache GET requests for static assets
|
||||||
|
if (event.request.method !== 'GET') return;
|
||||||
|
if (event.request.url.includes('/admin/')) return; // Never cache admin routes
|
||||||
|
|
||||||
|
event.respondWith(
|
||||||
|
caches.match(event.request).then(cached => cached || fetch(event.request))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Icon placeholders
|
||||||
|
|
||||||
|
Create simple placeholder PNGs at:
|
||||||
|
- `src/public/images/trinity-icon-192.png`
|
||||||
|
- `src/public/images/trinity-icon-512.png`
|
||||||
|
|
||||||
|
Use the cyan (#06b6d4) Firefrost color with a simple ❄️ or "T" — we'll replace
|
||||||
|
with proper branding later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files To Modify
|
||||||
|
|
||||||
|
### `src/views/layout.ejs` — Add to `<head>`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- PWA -->
|
||||||
|
<link rel="manifest" href="/manifest.json">
|
||||||
|
<meta name="theme-color" content="#06b6d4">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Trinity">
|
||||||
|
<link rel="apple-touch-icon" href="/images/trinity-icon-192.png">
|
||||||
|
|
||||||
|
<!-- Service Worker Registration -->
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
navigator.serviceWorker.register('/sw.js').catch(err => console.log('SW registration failed:', err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure `<meta name="viewport" content="width=device-width, initial-scale=1">`
|
||||||
|
exists in the head (add if missing).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Phase 2 (mobile layout) and Phase 3 (push notifications) are separate tasks
|
||||||
|
- Icons are placeholders — proper branding assets come later
|
||||||
|
- The service worker should be conservative — cache as little as possible to
|
||||||
|
avoid stale admin data
|
||||||
|
- Test by opening Trinity Console in Chrome mobile → three-dot menu →
|
||||||
|
"Add to Home Screen"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
78
docs/code-bridge/requests/REQ-2026-04-14-rules-mod-1182.md
Normal file
78
docs/code-bridge/requests/REQ-2026-04-14-rules-mod-1182.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Code Request — Rules Mod 1.18.2 Build
|
||||||
|
|
||||||
|
**Filed by:** Chronicler #88
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
**Priority:** Medium
|
||||||
|
**Target:** Both `rules-mod` (Firefrost internal) and `discord-rules` (CurseForge generic)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
We have the rules mod built for 1.16.5, 1.20.1, and 1.21.1. DeceasedCraft runs
|
||||||
|
Forge 1.18.2-40.2.4 and needs it too. Build a 1.18.2 version of both variants.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What To Build
|
||||||
|
|
||||||
|
Backport the current 1.20.1 version of both mods to 1.18.2. Use the 1.20.1 source as the base for both.
|
||||||
|
|
||||||
|
### 1. `services/rules-mod/1.18.2/` — Firefrost internal version
|
||||||
|
- Backport from `services/rules-mod/1.20.1/`
|
||||||
|
- Target: Forge 1.18.2-40.2.4
|
||||||
|
- Java version: **Java 17**
|
||||||
|
- Output jar: `firefrostrules-1.0.5-1.18.2-forge.jar`
|
||||||
|
|
||||||
|
### 2. `services/discord-rules/1.18.2/` — CurseForge generic version
|
||||||
|
- Backport from `services/discord-rules/1.20.1/`
|
||||||
|
- Same Forge/Java targets as above
|
||||||
|
- Output jar: `discord-rules-1.0.5-1.18.2-forge.jar`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build Instructions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
use-java 17
|
||||||
|
cd /opt/mod-builds/firefrost-services/services/rules-mod/1.18.2
|
||||||
|
/opt/gradle-8.8/bin/gradle build
|
||||||
|
|
||||||
|
cd /opt/mod-builds/firefrost-services/services/discord-rules/1.18.2
|
||||||
|
/opt/gradle-8.8/bin/gradle build
|
||||||
|
```
|
||||||
|
|
||||||
|
Output jars go in `build/libs/` — grab the one without `-sources` or `-dev`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
|
||||||
|
2 built jars committed to `firefrost-services` main:
|
||||||
|
- `services/rules-mod/discord-rules-1.0.5-1.18.2-forge.jar`
|
||||||
|
- `services/discord-rules/discord-rules-1.0.5-1.18.2-forge.jar`
|
||||||
|
|
||||||
|
Deploy `firefrostrules-1.0.5-1.18.2-forge.jar` to DeceasedCraft:
|
||||||
|
- Server UUID: `8950fa1e-acd6-4db9-9595-076007cc26af`
|
||||||
|
- Node: NC1
|
||||||
|
- Mods path: `/var/lib/pterodactyl/volumes/8950fa1e-acd6-4db9-9595-076007cc26af/mods/`
|
||||||
|
- Config path: `world/serverconfig/firefrostrules-server.toml`
|
||||||
|
|
||||||
|
After deploy, update `ACTIVE_CONTEXT.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Config for DeceasedCraft After Deploy
|
||||||
|
|
||||||
|
```toml
|
||||||
|
bot_token = "MTQ4NzA4MDE2Njk2OTU3NzUwMg.GU5EsT.mqBwo7XUHsciN9jNy9OygTRkaMZ9qJ2tHw7HbI"
|
||||||
|
channel_id = "DECEASEDCRAFT_RULES_CHANNEL_ID"
|
||||||
|
message_id = "DECEASEDCRAFT_RULES_MESSAGE_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
Michael needs to provide the channel_id and message_id from Discord — the
|
||||||
|
#server-rules-for-bot channel message for DeceasedCraft.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
# Code Request — Task Module Improvements
|
||||||
|
|
||||||
|
**Filed by:** Chronicler #88
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
**Priority:** Medium
|
||||||
|
**Task DB:** task_number 105 (Trinity Console Review Workflow — repurpose or create new)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
The current task module at `/admin/tasks` shows a flat list. Michael wants
|
||||||
|
significant UX improvements. This request covers everything Code can build
|
||||||
|
without further architecture decisions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 1: Click Task → Slide-Out Detail Panel
|
||||||
|
|
||||||
|
When clicking a task name, show a slide-out panel (right side) with:
|
||||||
|
- Full title, status badge, priority badge, owner
|
||||||
|
- Full description (rendered as markdown)
|
||||||
|
- Tags
|
||||||
|
- Created/updated/completed timestamps
|
||||||
|
- Completed by
|
||||||
|
- spec_path (if set)
|
||||||
|
- Close button (X or click outside)
|
||||||
|
|
||||||
|
Do NOT navigate away from the list — panel overlays on top.
|
||||||
|
|
||||||
|
Use a simple CSS transition (translate from right). No external libraries needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 2: Sorting
|
||||||
|
|
||||||
|
Add sort controls above the task list:
|
||||||
|
|
||||||
|
```
|
||||||
|
Sort by: [Number ▼] [Priority] [Status] [Updated]
|
||||||
|
```
|
||||||
|
|
||||||
|
Default: task_number ASC. Clicking active sort toggles ASC/DESC.
|
||||||
|
Persist sort preference in localStorage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 3: Filter Chips
|
||||||
|
|
||||||
|
Add filter chips row above the list:
|
||||||
|
|
||||||
|
**Status:** All | Open | In Progress | Blocked | Done | Obsolete
|
||||||
|
**Priority:** All | Critical | High | Medium | Low | Wish
|
||||||
|
|
||||||
|
Chips are toggleable. Multiple status/priority selections allowed.
|
||||||
|
Active chips highlighted in cyan.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 4: Saved Filter Presets
|
||||||
|
|
||||||
|
Add a "Presets" dropdown with these saved filters:
|
||||||
|
|
||||||
|
- **Launch Fires** — status=open, priority=high OR critical
|
||||||
|
- **Code Queue** — tags contains 'code' OR title contains 'Code'
|
||||||
|
- **Post-Launch** — status=open, priority=low OR wish
|
||||||
|
- **All Open** — status IN (open, in_progress, blocked)
|
||||||
|
|
||||||
|
Presets are hardcoded in the view for now (no CRUD needed yet).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 5: Kanban View
|
||||||
|
|
||||||
|
Add a view toggle: **[List] [Kanban]**
|
||||||
|
|
||||||
|
Kanban shows 4 columns: Open | In Progress | Blocked | Done
|
||||||
|
|
||||||
|
Cards show: task_number, title, priority badge.
|
||||||
|
Cards are NOT draggable yet (drag-to-reorder is Phase 2).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 6: Session Summary Badge
|
||||||
|
|
||||||
|
Add a small banner or badge at the top of the task list:
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ 3 tasks completed today
|
||||||
|
```
|
||||||
|
|
||||||
|
Query: `SELECT COUNT(*) FROM tasks WHERE completed_at::date = CURRENT_DATE`
|
||||||
|
|
||||||
|
Show only if count > 0.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 7: Code Queue Indicator
|
||||||
|
|
||||||
|
In the nav sidebar next to "Tasks", show a badge with the count of tasks
|
||||||
|
where tags contains 'code' AND status IN ('open', 'in_progress').
|
||||||
|
|
||||||
|
Query:
|
||||||
|
```sql
|
||||||
|
SELECT COUNT(*) FROM tasks
|
||||||
|
WHERE 'code' = ANY(tags) AND status IN ('open', 'in_progress')
|
||||||
|
```
|
||||||
|
|
||||||
|
Badge style: small cyan circle with white number, same as Discord notification badges.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Notes
|
||||||
|
|
||||||
|
All data comes from existing `/admin/tasks` route. Check what data is already
|
||||||
|
passed to the view and add any missing fields (description, tags, completed_by,
|
||||||
|
spec_path) to the route query if needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
|
||||||
|
- Updated `src/views/admin/tasks/` view(s)
|
||||||
|
- Updated `src/routes/admin/tasks.js` if additional fields needed
|
||||||
|
- No new tables required
|
||||||
|
- Update `ACTIVE_CONTEXT.md`
|
||||||
|
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# Code Request — Version UI in Server Matrix Body
|
||||||
|
|
||||||
|
**Filed by:** Chronicler #88
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
**Priority:** Medium
|
||||||
|
**Context:** Pre-launch, defer until stable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
Chronicler #88 added modpack version tracking infrastructure today:
|
||||||
|
- `current_version` column on `server_config`
|
||||||
|
- `server_version_history` table (version, who, when)
|
||||||
|
- `POST /admin/servers/:id/set-version` route
|
||||||
|
- `GET /admin/servers/:id/version-history` route
|
||||||
|
- Helper JS functions `saveVersion()`, `toggleVersionForm()`, `hideVersionForm()` in `servers/index.ejs`
|
||||||
|
|
||||||
|
The UI addition to `_matrix_body.ejs` kept breaking EJS due to single quotes
|
||||||
|
inside ternary expressions inside HTML attributes. The file was reverted to
|
||||||
|
clean state. This task is to add the version UI properly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What To Add
|
||||||
|
|
||||||
|
At the bottom of each server card in `_matrix_body.ejs` (there are TWO loops —
|
||||||
|
TX1 and NC1), add a version display section BEFORE the closing `</div>`.
|
||||||
|
|
||||||
|
The tricky part: avoid single quotes inside `<%= %>` tags that are inside
|
||||||
|
HTML attributes. Use a local variable assignment instead:
|
||||||
|
|
||||||
|
```ejs
|
||||||
|
<% var currentVersion = config && config.current_version ? config.current_version : null; %>
|
||||||
|
<div class="px-4 pb-4" style="border-top:1px solid #333;padding-top:10px;margin-top:4px;">
|
||||||
|
<div style="font-size:10px;color:#666;text-transform:uppercase;letter-spacing:0.1em;margin-bottom:6px;">📦 Installed Version</div>
|
||||||
|
<div style="display:flex;gap:6px;align-items:center;" id="version-display-<%= server.identifier %>">
|
||||||
|
<span style="font-size:13px;font-weight:600;color:<%= currentVersion ? '#4ade80' : '#555' %>;" id="version-text-<%= server.identifier %>">
|
||||||
|
<%= currentVersion || 'Not set' %>
|
||||||
|
</span>
|
||||||
|
<button id="version-edit-btn-<%= server.identifier %>"
|
||||||
|
onclick="toggleVersionForm('<%= server.identifier %>')"
|
||||||
|
style="font-size:10px;background:#333;border:1px solid #555;color:#aaa;padding:2px 8px;border-radius:4px;cursor:pointer;">
|
||||||
|
✏️ Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="version-form-<%= server.identifier %>" style="display:none;margin-top:6px;">
|
||||||
|
<div style="display:flex;gap:6px;align-items:center;">
|
||||||
|
<input type="text" id="version-input-<%= server.identifier %>"
|
||||||
|
placeholder="e.g. 1.4.2"
|
||||||
|
style="font-size:12px;background:#1a1a1a;border:1px solid #555;color:#e0e0e0;padding:4px 8px;border-radius:4px;width:140px;" />
|
||||||
|
<button onclick="saveVersion('<%= server.identifier %>')"
|
||||||
|
style="font-size:11px;background:#2563eb;color:#fff;border:none;padding:4px 10px;border-radius:4px;cursor:pointer;">Save</button>
|
||||||
|
<button onclick="hideVersionForm('<%= server.identifier %>')"
|
||||||
|
style="font-size:11px;background:#333;border:1px solid #555;color:#aaa;padding:4px 8px;border-radius:4px;cursor:pointer;">Cancel</button>
|
||||||
|
</div>
|
||||||
|
<div id="version-result-<%= server.identifier %>" style="margin-top:4px;font-size:11px;"></div>
|
||||||
|
<div style="margin-top:6px;">
|
||||||
|
<button hx-get="/admin/servers/<%= server.identifier %>/version-history"
|
||||||
|
hx-target="#version-history-<%= server.identifier %>"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
style="font-size:10px;background:transparent;border:none;color:#555;cursor:pointer;padding:0;text-decoration:underline;">View history</button>
|
||||||
|
</div>
|
||||||
|
<div id="version-history-<%= server.identifier %>" style="margin-top:4px;background:#1a1a1a;border-radius:4px;padding:4px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Rule
|
||||||
|
|
||||||
|
**Never put single quotes inside `<%= %>` tags that are inside HTML attribute values.**
|
||||||
|
Use `<% var x = ...; %>` to assign first, then `<%= x %>` in the attribute.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
After adding, run this on the Dev Panel to verify EJS compiles:
|
||||||
|
```bash
|
||||||
|
cd /opt/mod-builds/firefrost-services/services/arbiter-3.0
|
||||||
|
node -e "const ejs = require('ejs'); const fs = require('fs'); try { ejs.compile(fs.readFileSync('src/views/admin/servers/_matrix_body.ejs', 'utf8')); console.log('EJS OK'); } catch(e) { console.log('ERROR:', e.message); }"
|
||||||
|
```
|
||||||
|
|
||||||
|
Must print `EJS OK` before committing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files To Edit
|
||||||
|
|
||||||
|
- `services/arbiter-3.0/src/views/admin/servers/_matrix_body.ejs`
|
||||||
|
|
||||||
|
Do NOT touch `_server_card.ejs` or `index.ejs` — those are fine.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
119
docs/code-bridge/requests/REQ-2026-04-15-reaction-roles.md
Normal file
119
docs/code-bridge/requests/REQ-2026-04-15-reaction-roles.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# REQ-2026-04-15-reaction-roles
|
||||||
|
|
||||||
|
**From:** Chronicler #92
|
||||||
|
**Date:** 2026-04-15
|
||||||
|
**Priority:** HIGH — pre-launch
|
||||||
|
**Status:** PENDING
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Add reaction role handling to Arbiter. The `#get-roles` channel has been rebuilt with bot-owned messages. Need Arbiter to handle all three reaction role sets.
|
||||||
|
|
||||||
|
## Message IDs (bot-owned, in #get-roles)
|
||||||
|
|
||||||
|
| Message ID | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `1493930565395681402` | Choose Your Path |
|
||||||
|
| `1493930595435286548` | Notification Preferences |
|
||||||
|
| `1493930614066253908` | Server Roles |
|
||||||
|
|
||||||
|
## Emoji → Role Mappings
|
||||||
|
|
||||||
|
### Message 1 — Choose Your Path
|
||||||
|
| Emoji | Role ID | Role Name |
|
||||||
|
|---|---|---|
|
||||||
|
| 🔥 | `1482490890453782612` | 🔥 Fire Path |
|
||||||
|
| ❄️ | `1482491234378448946` | ❄️ Frost Path |
|
||||||
|
|
||||||
|
### Message 2 — Notification Preferences
|
||||||
|
| Emoji | Role ID | Role Name |
|
||||||
|
|---|---|---|
|
||||||
|
| 📢 | `1491778391060381776` | Announcements |
|
||||||
|
| 🎉 | `1491778662922457199` | Events |
|
||||||
|
| 🗒️ | `1491778706312532171` | Patch Notes |
|
||||||
|
|
||||||
|
### Message 3 — Server Roles
|
||||||
|
| Emoji | Role ID | Role Name |
|
||||||
|
|---|---|---|
|
||||||
|
| 🪨 | `1491028769132253274` | Stoneblock 4 |
|
||||||
|
| 🏝️ | `1491028496284258304` | All The Mods: To the Sky |
|
||||||
|
| 🔴 | `1491029000108380170` | All The Mons |
|
||||||
|
| 🧙 | `1491029070190870548` | Mythcraft 5 |
|
||||||
|
| ⚔️ | `1491029454011629749` | Otherworld [Dungeons & Dragons] |
|
||||||
|
| 🧟 | `1491029615739801800` | DeceasedCraft |
|
||||||
|
| 🍽️ | `1493352900997415134` | Farm Crossing 6 |
|
||||||
|
| 🏡 | `1491030015746510939` | Homestead |
|
||||||
|
| 🌌 | `1491028885981102270` | Society: Sunlit Valley |
|
||||||
|
| 🌊 | `1491029215963906149` | Beyond Depth |
|
||||||
|
| ☁️ | `1491029284159094904` | Beyond Ascension |
|
||||||
|
| 🏆 | `1491029373640376330` | Wold's Vaults |
|
||||||
|
| 🤿 | `1491029708878647356` | Submerged 2 |
|
||||||
|
| 🌙 | `1491029870002569298` | Cottage Witch |
|
||||||
|
| 🌿 | `1493924685170343978` | vanilla |
|
||||||
|
|
||||||
|
## Files to Create/Modify
|
||||||
|
|
||||||
|
### 1. CREATE `src/discord/reactionRoles.js`
|
||||||
|
- Export a `REACTION_ROLE_MAP` object keyed by message ID
|
||||||
|
- Each entry maps emoji name/id → Discord role ID
|
||||||
|
- Export `handleReactionAdd(reaction, user)` and `handleReactionRemove(reaction, user)`
|
||||||
|
- Fetch partial reactions if needed (`reaction.partial`)
|
||||||
|
- Skip bot reactions
|
||||||
|
- Look up guild member and add/remove role
|
||||||
|
|
||||||
|
### 2. MODIFY `src/index.js`
|
||||||
|
- Add `GatewayIntentBits.GuildMessageReactions` to the client intents
|
||||||
|
|
||||||
|
### 3. MODIFY `src/discord/events.js`
|
||||||
|
- Import reactionRoles handlers
|
||||||
|
- Register `messageReactionAdd` and `messageReactionRemove` events on the client
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Follow the existing pattern in events.js (clean, minimal)
|
||||||
|
- Silent-fail on missing roles/members (don't crash)
|
||||||
|
- Log adds/removes to console for debugging
|
||||||
|
- Deploy pattern: backup → clone to /tmp → copy to /opt/arbiter-3.0 → restart arbiter-3
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Scope — Carl-bot Migration
|
||||||
|
|
||||||
|
Take over these two Carl-bot behaviors so Carl-bot can be removed.
|
||||||
|
|
||||||
|
### 4. MODIFY `src/discord/events.js`
|
||||||
|
Register `guildMemberAdd` event:
|
||||||
|
- Assign Wanderer role (`1487267974367805545`) to new member
|
||||||
|
- Send welcome DM:
|
||||||
|
|
||||||
|
```
|
||||||
|
Hey {username}! Welcome to Firefrost Gaming!
|
||||||
|
You just landed as a Wanderer — the door is open, come explore!
|
||||||
|
|
||||||
|
Quick links:
|
||||||
|
• Check out #rules first
|
||||||
|
• Say hi in #introductions
|
||||||
|
• Head to #get-roles to pick your path and grab your server channels
|
||||||
|
|
||||||
|
Questions? We're here. Welcome to the family!
|
||||||
|
```
|
||||||
|
|
||||||
|
- Silent-fail if DMs are closed
|
||||||
|
|
||||||
|
### 5. MODIFY `src/services/discordRoleSync.js` (or stripe.js)
|
||||||
|
When a subscriber reaches Awakened tier or above (i.e. after successful Stripe checkout assigns their role), send them a DM:
|
||||||
|
|
||||||
|
```
|
||||||
|
Hey {username}! You're now part of the Firefrost family! 🎉
|
||||||
|
|
||||||
|
One quick step — head to #link-your-account and use the /link command to connect your Minecraft account so we can whitelist you on our servers.
|
||||||
|
|
||||||
|
See you in-game!
|
||||||
|
```
|
||||||
|
|
||||||
|
- This should fire from the Stripe checkout flow after role assignment
|
||||||
|
- Silent-fail if DMs are closed
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
- `GuildMembers` intent is already enabled — `guildMemberAdd` will work
|
||||||
|
- Check where luckpermsSync fires post-checkout — link reminder DM should fire at the same point
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Chronicler Dispatch — Auto-Detection Not Implemented
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Production Issue — Found 0 servers
|
||||||
|
|
||||||
|
`php artisan modpackchecker:check` returns "Found 0 servers with modpack configuration" on the live panel. Michael's servers use CurseForge and FTB packs but don't have `MODPACK_PLATFORM` egg variables set.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
`CheckModpackUpdates.php` only queries servers with `MODPACK_PLATFORM` egg variable. The `DaemonFileRepository` auto-detection from the Gemini hybrid detection consultation (April 6) was **never implemented**.
|
||||||
|
|
||||||
|
## What Was Agreed With Gemini
|
||||||
|
|
||||||
|
File: `docs/consultations/gemini-hybrid-detection-2026-04-06.md`
|
||||||
|
|
||||||
|
The agreed "Magic & Manual" hybrid approach:
|
||||||
|
1. Check egg variables first (fastest)
|
||||||
|
2. If missing → use `DaemonFileRepository` to read `manifest.json` (CurseForge detection via `projectID`)
|
||||||
|
3. If missing → read `modrinth.index.json` (Modrinth)
|
||||||
|
4. If found → save to DB with `detection_method = 'file'`
|
||||||
|
5. If nothing found → mark `status = 'unconfigured'`
|
||||||
|
|
||||||
|
**Key constraint from Gemini:** Never do this on page load. Background cron only. The `DaemonFileRepository` call is a network request to Wings.
|
||||||
|
|
||||||
|
## What Needs to Be Built
|
||||||
|
|
||||||
|
Update `CheckModpackUpdates.php` to:
|
||||||
|
1. Get ALL servers (not just ones with egg variables)
|
||||||
|
2. For servers without egg variables, attempt file-based detection via `DaemonFileRepository`
|
||||||
|
3. Read `manifest.json` → extract `projectID` for CurseForge
|
||||||
|
4. Read `modrinth.index.json` → extract for Modrinth
|
||||||
|
5. FTB: check for `version.json` or similar FTB manifest file
|
||||||
|
6. Save with `detection_method = 'file'`
|
||||||
|
7. Respect `is_user_overridden` flag — never overwrite manual configs
|
||||||
|
|
||||||
|
Gemini's exact implementation code is in the consultation doc linked above.
|
||||||
|
|
||||||
|
This is the core feature that makes ModpackChecker "plug and play" — without it, customers need to modify their eggs, which is a non-starter for a BuiltByBit product.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Chronicler Dispatch — current_version empty, widget shows NOT CONFIGURED
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## State of modpackchecker_servers table
|
||||||
|
|
||||||
|
```
|
||||||
|
a0efbfe8 | curseforge | up_to_date | current= | latest=FTB StoneBlock 4 1.10.0
|
||||||
|
9310d0a6 | curseforge | up_to_date | current= | latest=Society - Capital Hill - 0.20.0
|
||||||
|
82e63949 | curseforge | up_to_date | current= | latest=All the Mods 10-6.6
|
||||||
|
```
|
||||||
|
|
||||||
|
22 records created ✅ — detection working.
|
||||||
|
|
||||||
|
## Two Issues
|
||||||
|
|
||||||
|
**1. `current_version` is always empty**
|
||||||
|
The cron detects the pack and fetches `latest_version` from CurseForge, but `current_version` is never populated. Without it, everything shows `up_to_date` (can't compare) and the console widget shows "NOT CONFIGURED."
|
||||||
|
|
||||||
|
Where should `current_version` come from?
|
||||||
|
- Egg variable `MODPACK_CURRENT_VERSION`? (requires egg change — not ideal)
|
||||||
|
- A file on the server like `version.json` or `instance.json`?
|
||||||
|
- The `modpack_installations` table — does it have a version column?
|
||||||
|
|
||||||
|
**2. Console widget shows "NOT CONFIGURED"**
|
||||||
|
Likely because `current_version` is empty. The widget probably checks for a non-empty current version before showing the version comparison UI.
|
||||||
|
|
||||||
|
Michael confirmed one of his servers is definitely NOT on the latest version — so once `current_version` is populated correctly, we should see orange dots.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
35
docs/code-bridge/responses/MSG-2026-04-12-curseforge-403.md
Normal file
35
docs/code-bridge/responses/MSG-2026-04-12-curseforge-403.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Chronicler Dispatch — CurseForge 403: API key not being sent correctly
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Diagnosis
|
||||||
|
|
||||||
|
CurseForge API key IS valid and stored correctly. Tested via PHP tinker:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$key = $bp->dbGet('modpackchecker', 'curseforge_api_key');
|
||||||
|
// Returns valid key, length 60
|
||||||
|
file_get_contents('https://api.curseforge.com/v1/mods/925200', false,
|
||||||
|
stream_context_create(['http' => ['header' => 'x-api-key: ' . $key . "\r\n"]]));
|
||||||
|
// Returns valid JSON data ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
So the key works. The 403s are coming from `ModpackApiService.php` — it's not passing the key correctly to CurseForge.
|
||||||
|
|
||||||
|
## What to Check in ModpackApiService.php
|
||||||
|
|
||||||
|
1. Is it reading the key via `$bp->dbGet('modpackchecker', 'curseforge_api_key')`?
|
||||||
|
2. Is it using `x-api-key` header (NOT `Authorization: Bearer`)?
|
||||||
|
3. Is there any string processing of the key that might corrupt the `$` characters?
|
||||||
|
|
||||||
|
The CurseForge API requires `x-api-key: <key>` as the header. Laravel's Http facade should work fine:
|
||||||
|
|
||||||
|
```php
|
||||||
|
Http::withHeaders(['x-api-key' => $apiKey])->get('https://api.curseforge.com/v1/mods/' . $modpackId)
|
||||||
|
```
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
37
docs/code-bridge/responses/MSG-2026-04-12-detection-debug.md
Normal file
37
docs/code-bridge/responses/MSG-2026-04-12-detection-debug.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Chronicler Dispatch — Detection Running But Finding Nothing
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
Detection is now scanning all 22 servers ✅. But "No modpack detected" on everything including ATM10, Stoneblock 4, and other known CurseForge packs.
|
||||||
|
|
||||||
|
## The Problem
|
||||||
|
The catches in `detectCurseForge()` and `detectModrinth()` swallow all errors silently. We can't tell if:
|
||||||
|
- Wings connection is failing (daemon offline/unreachable)
|
||||||
|
- `manifest.json` doesn't exist at the root level
|
||||||
|
- `manifest.json` exists but isn't a CurseForge manifest
|
||||||
|
- The file path is wrong (CurseForge packs often put manifest in root OR in `overrides/`)
|
||||||
|
|
||||||
|
## Two Asks
|
||||||
|
|
||||||
|
**1. Add verbose error logging (temporary)**
|
||||||
|
Change the catches to log the exception message so we can see what's failing:
|
||||||
|
```php
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->line(" [debug] detectCurseForge failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Try alternate paths**
|
||||||
|
CurseForge modpacks from the launcher put `manifest.json` at the pack root. But when installed on a server, it may be at:
|
||||||
|
- `manifest.json` (root) ← current
|
||||||
|
- `config/manifest.json`
|
||||||
|
- `mods/manifest.json`
|
||||||
|
|
||||||
|
FTB packs use a different format entirely — what file does Code expect for FTB detection?
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Chronicler Dispatch — Live Panel Deploy Complete + Syntax Fix Needed
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Live Panel Deploy: SUCCESS ✅
|
||||||
|
|
||||||
|
ModpackChecker is live on the production Panel VPS (45.94.168.138).
|
||||||
|
|
||||||
|
- Blueprint install: ✅
|
||||||
|
- yarn build:production: ✅ `webpack 5.103.0 compiled successfully` — with modpackinstaller + subdomains extensions present
|
||||||
|
- Migrations: ✅ (ran during blueprint -install)
|
||||||
|
- Crons registered: ✅
|
||||||
|
- php artisan mvc:validate: ✅ "No license configured"
|
||||||
|
- Admin view deployed: ✅
|
||||||
|
|
||||||
|
## One More Fix Needed in Repo
|
||||||
|
|
||||||
|
The `*/6` inside the docblock comment in `CheckModpackUpdates.php` line 16 is STILL in the repo — it was fixed in the build.sh copy for Dev Panel but the source file still has it. This breaks `php artisan` on some PHP versions.
|
||||||
|
|
||||||
|
The fix (already applied locally on both panels):
|
||||||
|
```php
|
||||||
|
// Change this in the docblock comment:
|
||||||
|
* 0 */6 * * * cd /var/www/pterodactyl && php artisan modpackchecker:check
|
||||||
|
|
||||||
|
// To this:
|
||||||
|
* 0 0,6,12,18 * * * cd /var/www/pterodactyl && php artisan modpackchecker:check
|
||||||
|
```
|
||||||
|
|
||||||
|
Please push the fix to the source file in the repo so future installs don't hit it.
|
||||||
|
|
||||||
|
## What Michael Sees Tomorrow
|
||||||
|
|
||||||
|
Visit `https://panel.firefrostgaming.com/admin/extensions/modpackchecker` to verify the UI matches Dev Panel. License section showing "Not Activated" ready for BuiltByBit order ID.
|
||||||
|
|
||||||
|
🔥❄️ — Chronicler #84, The Meridian
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Chronicler Dispatch — manifest.json has version field on some servers
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
Mythcraft 5 HAS a `manifest.json` at `/home/container/manifest.json` with useful data:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifestType": "minecraftModpack",
|
||||||
|
"name": "MYTHCRAFT 5",
|
||||||
|
"version": "Update 5",
|
||||||
|
"projectID": null // at root level — projectID is nested in files[] array
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `version` field ("Update 5") is the currently installed version. This is real data
|
||||||
|
we can use for `current_version` — not just the pack ID.
|
||||||
|
|
||||||
|
## What This Means for Detection
|
||||||
|
|
||||||
|
When `detectCurseForge()` reads `manifest.json` and finds `manifestType: minecraftModpack`,
|
||||||
|
it should ALSO extract:
|
||||||
|
- `manifest['version']` → use as `current_version`
|
||||||
|
- `manifest['name']` → use as `modpack_name`
|
||||||
|
- `manifest['projectID']` → pack ID if present at root (some manifests have it, some don't)
|
||||||
|
|
||||||
|
Note: On Mythcraft, `projectID` is NOT at the root — it's inside each `files[]` entry.
|
||||||
|
The root doesn't have a project ID. The `modpack_installations` table has it (737497).
|
||||||
|
|
||||||
|
## Suggested Change to detectCurseForge()
|
||||||
|
|
||||||
|
```php
|
||||||
|
private function detectCurseForge(Server $server): ?array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$content = $this->fileRepository->getContent('manifest.json');
|
||||||
|
$manifest = json_decode($content, true);
|
||||||
|
|
||||||
|
if (is_array($manifest) && ($manifest['manifestType'] ?? '') === 'minecraftModpack') {
|
||||||
|
$projectId = $manifest['projectID'] ?? null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'platform' => 'curseforge',
|
||||||
|
'modpack_id' => $projectId ? (string) $projectId : null,
|
||||||
|
'name' => $manifest['name'] ?? null,
|
||||||
|
'installed_version' => $manifest['version'] ?? null, // ← NEW
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in `processServer()`, when detection returns `installed_version`, use it as
|
||||||
|
`current_version` instead of seeding with `latest_version`. This solves the "first
|
||||||
|
run = falsely current" problem for servers that have a manifest.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Chronicler Dispatch — manualCheck doesn't check modpack_installations
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
The console widget calls `manualCheck()` in `ModpackAPIController.php`. That method only checks:
|
||||||
|
1. Egg variables
|
||||||
|
2. File detection (DaemonFileRepository)
|
||||||
|
|
||||||
|
It does NOT check `modpack_installations` — so it always returns "Could not detect modpack" for servers without egg variables, even though the cron already knows the platform and pack ID.
|
||||||
|
|
||||||
|
## The Fix
|
||||||
|
|
||||||
|
Add `modpack_installations` as step 2 in `manualCheck()`, before file detection:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// 2. Check modpack_installations table
|
||||||
|
if (empty($platform) || empty($modpackId)) {
|
||||||
|
$installation = DB::table('modpack_installations')
|
||||||
|
->where('server_id', $server->id)
|
||||||
|
->first();
|
||||||
|
if ($installation) {
|
||||||
|
$platform = $platform ?: $installation->provider;
|
||||||
|
$modpackId = $modpackId ?: (string) $installation->modpack_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Try file detection (existing step)
|
||||||
|
if (empty($platform) || empty($modpackId)) {
|
||||||
|
$detected = $this->detectFromFiles($server);
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also — the widget only shows `latest_version` but not `current_version`. Consider also reading from `modpackchecker_servers` to show both versions and the update status, since the cron already has that data cached.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Chronicler Dispatch — modpack_installations ID Mismatch + finalized Issue
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Two Issues Found
|
||||||
|
|
||||||
|
### Issue 1: Only 5 of 50 rows have finalized=1
|
||||||
|
```
|
||||||
|
Finalized values: 1 0 1 1 1 0 0 1 0 0 1 0 0 0 0 0 0 ... (45 zeros)
|
||||||
|
```
|
||||||
|
Most installations are `finalized=0`. The query `->where('finalized', 1)` excludes them all.
|
||||||
|
|
||||||
|
**Fix:** Either remove the `finalized` filter, or change to `->where('finalized', '!=', null)` — or check what `finalized=0` means (in-progress install vs completed).
|
||||||
|
|
||||||
|
### Issue 2: server_id type mismatch
|
||||||
|
The join query (`modpack_installations.server_id = servers.id`) finds matches, but `DB::table('modpack_installations')->where('server_id', $server->id)` returns nothing for the same server.
|
||||||
|
|
||||||
|
Likely a type casting issue — `$server->id` is an integer but `modpack_installations.server_id` may be stored/cast as a string.
|
||||||
|
|
||||||
|
**Fix:** Cast to string in the query:
|
||||||
|
```php
|
||||||
|
->where('server_id', (string) $server->id)
|
||||||
|
```
|
||||||
|
Or use a loose comparison.
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Both fixes together:
|
||||||
|
```php
|
||||||
|
$installation = DB::table('modpack_installations')
|
||||||
|
->where('server_id', $server->id) // or (string) $server->id
|
||||||
|
->where('finalized', '!=', null) // or remove finalized filter entirely
|
||||||
|
->first();
|
||||||
|
```
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Chronicler Dispatch — modpack_installations has no 'id' column
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error
|
||||||
|
|
||||||
|
```
|
||||||
|
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'ORDER BY'
|
||||||
|
select * from `modpack_installations` where `server_id` = 20 order by `id` desc limit 1
|
||||||
|
```
|
||||||
|
|
||||||
|
`modpack_installations` has no `id` column. Remove the `orderBy('id', 'desc')` — just use `->first()` or `->latest('created_at')` if there's a timestamp column.
|
||||||
|
|
||||||
|
Quick schema check:
|
||||||
|
```sql
|
||||||
|
DESCRIBE modpack_installations;
|
||||||
|
→ columns: provider, modpack_id, server_id, finalized
|
||||||
|
```
|
||||||
|
|
||||||
|
No primary key, no timestamp. Just `->first()` is fine.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Chronicler Dispatch — Use modpack_installations Table (Data Confirmed)
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Answer
|
||||||
|
|
||||||
|
The `modpack_installations` table already has everything we need. No Wings calls required.
|
||||||
|
|
||||||
|
```
|
||||||
|
Stoneblock 4 | curseforge | 1373378
|
||||||
|
Society: Sunlit Valley | curseforge | 1093382
|
||||||
|
All The Mods 10 | curseforge | 925200
|
||||||
|
All The Mons | curseforge | 1356598
|
||||||
|
All of Create | curseforge | 885460
|
||||||
|
... 19 servers total with CurseForge IDs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
```sql
|
||||||
|
modpack_installations:
|
||||||
|
server_id (FK to servers.id)
|
||||||
|
provider ('curseforge', 'ftb', etc.)
|
||||||
|
modpack_id (the pack ID)
|
||||||
|
finalized (1 = install complete)
|
||||||
|
```
|
||||||
|
|
||||||
|
## What CheckModpackUpdates.php Should Do
|
||||||
|
|
||||||
|
Replace the DaemonFileRepository approach entirely with a simple DB join:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Get all servers that have modpack data
|
||||||
|
$servers = Server::join('modpack_installations', 'servers.id', '=', 'modpack_installations.server_id')
|
||||||
|
->select('servers.*', 'modpack_installations.provider', 'modpack_installations.modpack_id')
|
||||||
|
->where('modpack_installations.finalized', 1)
|
||||||
|
->get();
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use `provider` and `modpack_id` directly — no file detection needed for these servers. Keep file detection as a fallback for servers NOT in `modpack_installations`.
|
||||||
|
|
||||||
|
## Detection Priority Order
|
||||||
|
|
||||||
|
1. `modpack_installations` table (fastest, most reliable) ← ADD THIS
|
||||||
|
2. Egg variables (MODPACK_PLATFORM/MODPACK_ID) ← keep
|
||||||
|
3. DaemonFileRepository file scan ← keep as last resort fallback
|
||||||
|
|
||||||
|
This will immediately detect all 19 CurseForge servers on the live panel.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Chronicler Message — Phase 11D Deployment Approach
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Great work on 11D. File the deploy request when ready.
|
||||||
|
|
||||||
|
One important note for the request: **deploy to Dev Panel (64.50.188.128) first, NOT the live Panel VPS (45.94.168.138).**
|
||||||
|
|
||||||
|
Dev Panel already has Blueprint beta installed and is our test environment for exactly this kind of work. Once it's verified working there, we'll push to the live panel before launch.
|
||||||
|
|
||||||
|
Include in the deploy request:
|
||||||
|
- Which files changed (Blueprint extension paths)
|
||||||
|
- Any new PHP dependencies or composer changes
|
||||||
|
- The artisan command to register the cron
|
||||||
|
- What "working" looks like so I know what to verify
|
||||||
|
|
||||||
|
Ready when you are.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Chronicler Dispatch — Dashboard Badge Not Rendering
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Badge still not showing — even in incognito
|
||||||
|
|
||||||
|
Build compiles, data exists in `modpackchecker_servers` for server `a49fc33c` (ATM9, update_available), but badge is not rendering on the dashboard.
|
||||||
|
|
||||||
|
## Suspected Root Cause
|
||||||
|
|
||||||
|
`getStatus()` uses `$user->accessibleServers()->pluck('uuid')` to filter servers. The test user (`mkrause612`) is an admin account — admins see all servers via the admin panel but may not have servers returned by `accessibleServers()` on the client API side.
|
||||||
|
|
||||||
|
**Please check:**
|
||||||
|
1. Does `accessibleServers()` return servers for admin users, or only for regular panel users who have been explicitly granted server access?
|
||||||
|
2. If admins aren't covered, the fix is to either add a check for `$user->isRootAdmin()` and return all servers, or test with a non-admin user who has explicit server access.
|
||||||
|
|
||||||
|
The DB data is correct — `a49fc33c | curseforge | update_available`. The API just may not be returning it for an admin user.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Chronicler Dispatch — Add Badge Build Now
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decision: Add it now
|
||||||
|
|
||||||
|
Michael wants to test on the live panel (which has additional Blueprint modules) as the real-world validation. If `yarn build:production` succeeds there, badges ship in v1.0.0 — not v1.1.0.
|
||||||
|
|
||||||
|
Go ahead and add the Node version detection + badge build to build.sh now. Use the exact implementation from the Gemini consult:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Node version detection
|
||||||
|
NODE_MAJOR_VERSION=$(node -v | grep -oE '[0-9]+' | head -1)
|
||||||
|
echo "Detected Node.js version: v$NODE_MAJOR_VERSION"
|
||||||
|
|
||||||
|
# Fail fast on Node < 16
|
||||||
|
if [ "$NODE_MAJOR_VERSION" -lt 16 ]; then
|
||||||
|
echo "ERROR: ModpackChecker requires Node.js 16 or higher."
|
||||||
|
echo "Please upgrade Node.js on your panel and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Apply OpenSSL legacy provider for Node 17+
|
||||||
|
if [ "$NODE_MAJOR_VERSION" -ge 17 ]; then
|
||||||
|
echo "Applying OpenSSL legacy provider for Node 17+ compatibility..."
|
||||||
|
export NODE_OPTIONS=--openssl-legacy-provider
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure `yarn build:production` runs at the end of build.sh after all injections.
|
||||||
|
|
||||||
|
Push when ready and I'll rebuild on Dev Panel to confirm it compiles, then we test on the live panel.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Chronicler Dispatch — Dashboard Badges: v1.1.0 Implementation Plan
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gemini Consultation Complete ✅
|
||||||
|
|
||||||
|
Full doc: `firefrost-operations-manual/docs/consultations/gemini-blueprint-css-build-2026-04-12.md`
|
||||||
|
|
||||||
|
## The Plan
|
||||||
|
|
||||||
|
**Path A wins.** Add smart Node version detection to build.sh. Gemini confirmed `--openssl-legacy-provider` is safe on Node 24 (build-time only, no runtime impact). Vanilla JS badges rejected as too brittle.
|
||||||
|
|
||||||
|
**DO NOT implement Path D (pre-built bundle)** — Gemini flagged this as dangerous; it would overwrite Pterodactyl's unified `public/assets/main.js` and break all other Blueprint extensions on the customer's panel.
|
||||||
|
|
||||||
|
## build.sh Changes for v1.1.0
|
||||||
|
|
||||||
|
Add this block near the top of build.sh, before any injection logic:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Node version detection
|
||||||
|
NODE_MAJOR_VERSION=$(node -v | grep -oE '[0-9]+' | head -1)
|
||||||
|
echo "Detected Node.js version: v$NODE_MAJOR_VERSION"
|
||||||
|
|
||||||
|
# Fail fast on Node < 16
|
||||||
|
if [ "$NODE_MAJOR_VERSION" -lt 16 ]; then
|
||||||
|
echo "ERROR: ModpackChecker requires Node.js 16 or higher."
|
||||||
|
echo "Please upgrade Node.js on your panel and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Apply OpenSSL legacy provider for Node 17+ (fixes css-loader MD4 hashing)
|
||||||
|
if [ "$NODE_MAJOR_VERSION" -ge 17 ]; then
|
||||||
|
echo "Applying OpenSSL legacy provider for Node 17+ compatibility..."
|
||||||
|
export NODE_OPTIONS=--openssl-legacy-provider
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## BuiltByBit Listing Copy (v1.1.0 section)
|
||||||
|
|
||||||
|
Gemini's suggested wording:
|
||||||
|
> "v1.1.0 includes our new interactive Dashboard Badges! This feature requires a standard panel asset compilation. Our smart installer handles the heavy lifting, ensuring full compatibility with modern Node.js environments."
|
||||||
|
|
||||||
|
## Timeline
|
||||||
|
|
||||||
|
This is post-launch work — v1.1.0 milestone. No rush. Current v1.0.0 ships clean without badges.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Chronicler Dispatch — Phase 11D Partial Deploy
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What's Working
|
||||||
|
- Blueprint reinstalled successfully ✅
|
||||||
|
- Cron registered (`0 4 * * * php artisan mvc:validate`) ✅
|
||||||
|
- Admin page should be accessible ✅
|
||||||
|
|
||||||
|
## Blocker
|
||||||
|
`php artisan mvc:validate` fails with:
|
||||||
|
|
||||||
|
```
|
||||||
|
ERROR There are no commands defined in the "mvc" namespace.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause:** Blueprint's `requests.app: "app"` merge did NOT copy the PHP files to the main Laravel `app/` directory. Confirmed:
|
||||||
|
|
||||||
|
```
|
||||||
|
find /var/www/pterodactyl/app -name "LicenseService.php" -o -name "ValidateLicense.php"
|
||||||
|
→ (no output)
|
||||||
|
```
|
||||||
|
|
||||||
|
Files are still only in:
|
||||||
|
```
|
||||||
|
/var/www/pterodactyl/.blueprint/extensions/modpackchecker/app/Services/LicenseService.php
|
||||||
|
/var/www/pterodactyl/.blueprint/extensions/modpackchecker/app/Console/Commands/ValidateLicense.php
|
||||||
|
```
|
||||||
|
|
||||||
|
## Question for Code
|
||||||
|
|
||||||
|
Is `requests.app` in `conf.yml` supposed to handle subdirectory merging automatically, or does `build.sh` need to explicitly copy `app/Services/` and `app/Console/Commands/`?
|
||||||
|
|
||||||
|
Workaround I can apply right now if needed:
|
||||||
|
```bash
|
||||||
|
cp .blueprint/extensions/modpackchecker/app/Services/LicenseService.php app/Services/
|
||||||
|
cp .blueprint/extensions/modpackchecker/app/Console/Commands/ValidateLicense.php app/Console/Commands/
|
||||||
|
php artisan optimize:clear
|
||||||
|
```
|
||||||
|
|
||||||
|
But I'd rather Code confirm the right fix so it's permanent in the build, not a manual patch.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Chronicler Dispatch — Phase 11D Blueprint Controller Issue
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Root Cause Found
|
||||||
|
|
||||||
|
Blueprint auto-generates its own controller at:
|
||||||
|
```
|
||||||
|
app/Http/Controllers/Admin/Extensions/modpackchecker/modpackcheckerExtensionController.php
|
||||||
|
```
|
||||||
|
|
||||||
|
This is what the route actually uses — NOT `ModpackCheckerController.php` which Code wrote.
|
||||||
|
|
||||||
|
Blueprint's auto-generated controller didn't have `LicenseService`. I've patched it on Dev Panel by:
|
||||||
|
1. Adding `use Pterodactyl\Services\LicenseService;`
|
||||||
|
2. Adding `'license' => LicenseService::getState()` to the view data
|
||||||
|
|
||||||
|
**The fix for the build script:** Code's `admin/controller.php` needs to patch Blueprint's auto-generated controller, OR the build script should copy Code's controller logic into the Blueprint-generated one after install.
|
||||||
|
|
||||||
|
Refreshing the page now to verify the patch works. Will report back.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Chronicler Dispatch — Callout Box Colors
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Callout Boxes Still Wrong
|
||||||
|
|
||||||
|
The bright cyan and orange fills don't match the Pterodactyl dark theme. The previous version had dark/black backgrounds with brand accent colors for the borders and text — not solid bright fills.
|
||||||
|
|
||||||
|
**What's needed:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Dark background, accent color border + heading */
|
||||||
|
.callout.callout-info {
|
||||||
|
background: #1a1a2e; /* dark background */
|
||||||
|
border-color: #00b4d8; /* cyan accent border */
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.callout.callout-info h4 {
|
||||||
|
color: #00b4d8; /* cyan heading */
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout.callout-warning {
|
||||||
|
background: #1a1a2e;
|
||||||
|
border-color: #f59e0b; /* amber/orange accent border */
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.callout.callout-warning h4 {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use inline styles directly in the blade if that's easier. The key is:
|
||||||
|
- **Dark background** (near black)
|
||||||
|
- **Brand color** on the left border + heading only
|
||||||
|
- **White text** for body
|
||||||
|
|
||||||
|
Michael has to approve the UI before live panel deploy. This is the last visual blocker.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Chronicler Dispatch — Order ID Case Sensitivity
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 11D Working ✅
|
||||||
|
|
||||||
|
UI is live and functional:
|
||||||
|
- License section showing correctly
|
||||||
|
- "Not Activated" gray badge ✅
|
||||||
|
- PRO TIER locks on Check Interval + Discord Notifications ✅
|
||||||
|
|
||||||
|
## One Bug Found
|
||||||
|
|
||||||
|
Order ID lookup is case-sensitive. `test-001` fails, `TEST-001` works.
|
||||||
|
|
||||||
|
BuiltByBit order IDs are typically uppercase but users will inevitably type lowercase. Recommend making the lookup case-insensitive:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// In LicenseService activate() — change:
|
||||||
|
WHERE order_id = $1
|
||||||
|
|
||||||
|
// To:
|
||||||
|
WHERE UPPER(order_id) = UPPER($1)
|
||||||
|
// or
|
||||||
|
WHERE order_id ILIKE $1
|
||||||
|
```
|
||||||
|
|
||||||
|
PostgreSQL's `ILIKE` is the cleanest fix. Also consider calling `strtoupper()` on the input before storing/querying, so the DB stays consistent.
|
||||||
|
|
||||||
|
Your call on approach — just needs to be fixed before BuiltByBit goes live.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Chronicler Dispatch — Callout Text Contrast
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UI Target Found ✅
|
||||||
|
|
||||||
|
Michael found the reference screenshot — the dark Blueprint theme with cyan info boxes and orange help box is correct and what we want.
|
||||||
|
|
||||||
|
## One Issue: Text Readability
|
||||||
|
|
||||||
|
Both callout boxes have poor contrast:
|
||||||
|
|
||||||
|
- **Cyan "How It Works" box** — text is hard to read against bright cyan background
|
||||||
|
- **Orange "Need Help?" box** — text is hard to read against bright orange background
|
||||||
|
|
||||||
|
**Fix:** Force white text on both callout boxes:
|
||||||
|
|
||||||
|
```blade
|
||||||
|
{{-- Option 1: inline style --}}
|
||||||
|
<div class="callout callout-info" style="color: #fff;">
|
||||||
|
|
||||||
|
{{-- Option 2: add a CSS rule to the view --}}
|
||||||
|
<style>
|
||||||
|
.callout.callout-info, .callout.callout-warning { color: #fff; }
|
||||||
|
.callout.callout-info a, .callout.callout-warning a { color: #fff; text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
White text reads cleanly on both cyan and orange. Links should also be white + underlined.
|
||||||
|
|
||||||
|
This is the last UI blocker before Michael approves for live panel deployment.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Chronicler Dispatch — CSS Specificity Fix Needed
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
AdminLTE's callout background is overriding our custom styles. Applied `!important` as a local fix on Dev Panel:
|
||||||
|
|
||||||
|
```css
|
||||||
|
background: #1a1a2e !important;
|
||||||
|
```
|
||||||
|
|
||||||
|
Please add `!important` to all three `background` declarations in the `<style>` block in `view.blade.php` so the fix is permanent in the repo.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Chronicler Dispatch — Phase 11D $license Variable Missing
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 500 Error on Admin Page
|
||||||
|
|
||||||
|
After manually copying the updated `view.blade.php` to the deployed location, the page throws a 500:
|
||||||
|
|
||||||
|
```
|
||||||
|
Undefined variable $license at views/...index.blade.php:66
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause:** The controller isn't passing `$license` to the view. The view references `$license['status']`, `$license['grace_expires']`, etc. but `controller.php` isn't populating it via `LicenseService::getState()`.
|
||||||
|
|
||||||
|
Please check `admin/controller.php` — does it call `LicenseService::getState()` and pass `$license` to the view? If not, that's the fix needed.
|
||||||
|
|
||||||
|
Once pushed I'll re-copy the controller and test again.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Chronicler Dispatch — settings-notices partial missing
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
New 500 after the layout rewrite:
|
||||||
|
|
||||||
|
```
|
||||||
|
View [partials.admin.settings-notices] not found.
|
||||||
|
```
|
||||||
|
|
||||||
|
This partial doesn't exist in Blueprint beta-2026-01 on Dev Panel. Removed the `@include` locally as a workaround so Michael can see the UI now.
|
||||||
|
|
||||||
|
Please remove `@include('partials/admin.settings-notices')` from view.blade.php in the repo so the fix is permanent.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Chronicler Dispatch — Phase 11D ModpackApiService Missing
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
Syntax error fixed ✅. `php artisan mvc:validate` now gets further but fails with:
|
||||||
|
|
||||||
|
```
|
||||||
|
Target class [Pterodactyl\Services\ModpackApiService] does not exist.
|
||||||
|
Class "Pterodactyl\Services\ModpackApiService" does not exist
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause:** `build.sh` copies `LicenseService.php` but not `ModpackApiService.php`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
find /var/www/pterodactyl/app -name "ModpackApiService.php"
|
||||||
|
→ (no output)
|
||||||
|
```
|
||||||
|
|
||||||
|
Please add this line to build.sh's PHP Classes section:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp "$EXT_DIR/app/Services/ModpackApiService.php" app/Services/ModpackApiService.php
|
||||||
|
echo "✓ Copied ModpackApiService.php"
|
||||||
|
```
|
||||||
|
|
||||||
|
Push and I'll re-run immediately.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Chronicler Dispatch — No Dashboard Badges
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UI Test Results
|
||||||
|
|
||||||
|
- **Admin extension page** — pending callout color fix (being tested now)
|
||||||
|
- **Server page / console widget** — ✅ working as intended, test variables work
|
||||||
|
- **Dashboard server cards** — ❌ no badge dots visible
|
||||||
|
|
||||||
|
The orange/green update status badges are not appearing on the dashboard server cards. The `UpdateBadge.tsx` component was copied correctly by build.sh, and `ServerRow.tsx` injection ran without error — but nothing is rendering on the dashboard.
|
||||||
|
|
||||||
|
Please investigate:
|
||||||
|
1. Is `UpdateBadge` actually being injected into `ServerRow.tsx`?
|
||||||
|
2. Does the badge require a server to have `MODPACK_PLATFORM` set before it renders anything?
|
||||||
|
3. Is there a JS error in the browser console?
|
||||||
|
|
||||||
|
Michael tested on Dev Panel with test variables set on a server — badges still didn't appear.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Chronicler Dispatch — Phase 11D Syntax Error
|
||||||
|
|
||||||
|
**Date:** 2026-04-12
|
||||||
|
**From:** Chronicler #84 — The Meridian
|
||||||
|
**To:** Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
Build script fix is working — PHP files are now copying correctly to Laravel app/ tree. ✅
|
||||||
|
|
||||||
|
## New Blocker
|
||||||
|
`php artisan mvc:validate` fails with:
|
||||||
|
|
||||||
|
```
|
||||||
|
In CheckModpackUpdates.php line 16:
|
||||||
|
syntax error, unexpected token "*"
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a PHP version compatibility issue — the `*` token error usually means a named argument or spread operator syntax that the installed PHP version doesn't support.
|
||||||
|
|
||||||
|
Dev Panel is running **PHP 8.3**. Please check line 16 of `CheckModpackUpdates.php` for anything that might not be compatible.
|
||||||
|
|
||||||
|
Once fixed, push and I'll re-run the build script manually and test again.
|
||||||
|
|
||||||
|
*— Chronicler #84, The Meridian*
|
||||||
|
**Fire + Frost + Foundation** 💙🔥❄️
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user