chore: initial commit of foundryvtt-admin

Spun out of the hbm-books working folder into its own repo. A small
backend+frontend+COBOL admin tool for managing Foundry VTT worlds
(socket inspection, join/shutdown debugging).
This commit is contained in:
2026-08-17 21:46:42 +02:00
commit 460d136d21
28 changed files with 2494 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
FROM alpine:latest
RUN apk add --no-cache gnucobol build-base curl
WORKDIR /app
CMD ["sh", "-c", "echo 'COBOL Container Ready. Shared files are in /app/shared.' && sh"]
+190
View File
@@ -0,0 +1,190 @@
# COBOL Admin Panel Bridge Integration
Welcome to your COBOL learning project! This container is equipped with **GnuCOBOL** (cobc) and mounts the shared folder at /app/shared.
## How the Sync Interface Works
The Node.js proxy server generates /app/shared/ACTORS.DAT containing actor records.
Each record is exactly **80 bytes** (including a trailing newline \n), with the following fixed-width layout:
| Field | Size (PIC) | Type | Offset | Description |
| :--- | :--- | :--- | :--- | :--- |
| **ID** | PIC X(a6) | Alphanumeric | 0 - 15 | Foundry Actor Unique ID |
| **NAME** | PIC X(20) | Alphanumeric | 16 - 35 | Character Name |
| **TYPE** | PIC X(10) | Alphanumeric | 36 - 45 | "character" or "npc" |
| **HP-CUR** | PIC 9(3) | Numeric | 46 - 48 | Current HP (zero-padded, e.g. 045) |
| **HP-MAX** | PIC 9(3) | Numeric | 49 - 51 | Max HP (zero-padded, e.g. 100) |
| **LEVEL** | PIC 9(2) | Numeric | 52 - 53 | Character Level (zero-padded, e.g. 05) |
| **PADDING** | PIC X(25) | Alphanumeric | 54 - 78 | Empty padding spaces |
| **NEWLINK** | PIC X | Alphanumeric | 79 | Carriage return / line feed \n
### To Submit Updates
When your COBOL program wants to edit a sheet, it writes the updated 80-byte record into /app/shared/UPDATES.DAT.
The Node.js/Bun proxy detects writes to this file, parses the record, pushes the edit to Foundry VTT via WebSocket, and then clears UPDATES.DAT.
---
## COBOL Snippets for your Project
### 1. Declaring the Files in your Program
````cobol
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
* The source database generated by Node.js
SELECT ACTORS-FILE ASSIGN TO "/app/shared/ACTORS.DAT"
ORGANIZATION IS LINE SEQUENTIAL.
* The transaction file you write to send updates to Foundry
SELECT UPDATES-FILE ASSIGN TO "/app/shared/UPDATES.DAT"
ORGANIZATION IS LINE SEQUENTIAL.
```
### 2. Declaring the Data Record Structure
````cobol
DATA DIVISION.
FILE SECTION.
FD ACTORS-FILE.
01 ACTOR-RECORD.
05 ACTOR-ID PIC X(16).
05 ACTOR-NAME PIC X(20).
05 ACTOR-TYPE PIC X(10).
05 ACTOR-HP-CUR PIC 9(3).
05 ACTOR-HP-MAX PIC 9(3).
05 ACTOR-LEVEL PIC 9(2).
05 FILLER PIC X(25). *> Filler for padding
```
### 3. Reading the File Loop
````cobol
WORKING-STORAGE SECTION.
01 WS-EOF-FLAG PIC X VALUE 'N'.
88 EOF-REACHED VALUE 'Y'.
PROCEDURE DIVISION.
READ-DATABASE.
OPEN INPUT ACTORS-FILE.
PERFORM UNTIL EOF-REACHED
READ ACTORS-FILE
AT END
SET EOF-REACHED TO TRUE
NOT AT END
DISPLAY "Found: " ACTOR-NAME " (HP: " ACTOR-HP-CUR ")"
END-READ
END-PERFORM.
CLOSE ACTORS-FILE.
```
### 4. Writing an Update (Transaction)
````cobol
PROCEDURE DIVISION.
SEND-UPDATE.
OPEN OUTPUT UPDATES-FILE.
* Fill the record fields (ensure numeric values are zero-padded)
MOVE "abc123xyz789" TO ACTOR-ID.
MOVE "Gimli" TO ACTOR-NAME.
MOVE "character" TO ACTOR-TYPE.
MOVE 45 TO ACTOR-HP-CUR.
MOVE 100 TO ACTOR-HP-MAX.
MOVE 5 TO ACTOR-LEVEL.
WRITE ACTOR-RECORD.
CLOSE UPDATES-FILE.
```
### 5. Alternative: Calling curl Directly from COBOL (REST API Access)
If your want to bypass files and hit the Node.js REST API directly from your COBOL program, GnuCOBOL provides the SYSTEM command:
```cobol
WORKING-STORAGE SECTION.
01 API-COMMAND PIC X(200).
PROCEDURE DIVISION.
TRIGGER-API.
* Using curl inside the Docker network to fetch the JSON list
STRING "curl -s http://backend:3000/api/actors > /app/shared/actors.json"
DELIMITED BY SIZE INTO API-COMMAND.
CALL "SYSTEM" USING API-COMMAND.
```
---
## Compiling & Running Inside Docker
Attach to the running COBOL container shell:
```bash
docker compose exec -it cobol sh
```
Create your file hbm-admin.cob, then compile it:
```bash
cobc -x -o hbm-admin hbm-admin.cob
```
Run the compiled executable:
```bash
./hbm-admin
```
---
### 6. Native C Bindings (Interfacing with libcurl)
Since GnuCOBOL translates COBOL code to C, you can interface with C libraries directly using the `CALL` statement. This allows you to perform real HTTP requests natively without spawning an external shell process.
1. **Write a C Helper (`http_client.c`)**:
```c
#include <stdio.h>
#include <curl/curl.h>
int send_actor_update(const char* url, const char* json_data) {
CURL *curl;
CURLcode res;
int success = 1;
curl = curl_easy_init();
if(curl) {
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
success = 0;
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
return success;
}
```
2. **Call it from COBOL (`hbm-admin.cob`)**:
```cobol
WORKING-STORAGE SECTION.
01 URL-STRING PIC X(100) VALUE "http://backend:3000/api/actors/update".
01 JSON-PAYLOAD PIC X(500).
01 RESULT-CODE PIC S9(9) BINARY.
PROCEDURE DIVISION.
MOVE "{\"id\":\"abc\",\"updates\":{\"system\":{\"attributes\":{\"hp\":{\"value\":45}}}}}"
TO JSON-PAYLOAD.
CALL "send_actor_update" USING BY REFERENCE URL-STRING
BY REFERENCE JSON-PAYLOAD
RETURNING RESULT-CODE.
IF RESULT-CODE = 1
DISPLAY "Update successful!"
ELSE
DISPLAY "Update failed."
END-IF.
```
3. **Compile and link together**:
```bash
gcc -c http_client.c -o http_client.o
cobc -x -o hbm-admin hbm-admin.cob http_client.o -lcurl
```
+26
View File
@@ -0,0 +1,26 @@
IDENTIFICATION DIVISION.
PROGRAM-ID. HBM-ADMIN.
*
DATA DIVISION.
WORKING-STORAGE SECTION.
01 URL-STRING PIC X(100)
VALUE "http://backend:3000/api/actors/update".
01 JSON-PAYLOAD PIC X(200).
01 RESULT-CODE PIC S9(9) BINARY.
*
PROCEDURE DIVISION.
MAIN-LOGIC.
MOVE "Gimli" TO JSON-PAYLOAD.
DISPLAY "Calling API for: " JSON-PAYLOAD.
CALL "send_actor_update" USING BY REFERENCE URL-STRING
BY REFERENCE JSON-PAYLOAD
RETURNING RESULT-CODE.
IF RESULT-CODE = 1
DISPLAY "Update successful!"
ELSE
DISPLAY "Update failed."
END-IF.
STOP RUN.
+28
View File
@@ -0,0 +1,28 @@
#include <stdio.h>
#include <curl/curl.h>
int send_actor_update(const char* url, const char* json_data) {
CURL *curl;
CURLcode res;
int success = 1;
curl = curl_easy_init();
if(curl) {
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
success = 0;
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
return success;
}
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
gcc -c http_client.c -o http_client.o
cobc -x -o hbm-admin hbm-admin.cbl http_client.o -lcurl