Ragnarok rodecomp deployment on memento
Changes to upstream: - git.js: coordinator commit identity from TD_GIT_AUTHOR_NAME/EMAIL. - build.js: docker runner can share job dirs through a named volume (jobVolume/jobVolumeRoot) for containerized workers. - install.js: staged installer output with logs, start prompt for regions. - web: feed layout, cache-busting asset versions, multi-agent help section. - build-td: fetch checksummed Windows runtime when building on non-Windows. Additions: - deploy/rodecomp: compose stack, Dockerfiles, Caddyfile, server.json, bulk import/verify scripts, and README.md with the deployment notes (the root README stays identical to upstream). - toolchain/msvc-v142: MSVC v142 under Wine (Docker), SDK 10.0.19041.0. - tools/rodecomp: project preparation, Forgejo provisioning, history and identity tools.
This commit is contained in:
parent
804ebd15c2
commit
022e0a2c78
39 changed files with 1232 additions and 33 deletions
8
tools/rodecomp/configure.sh
Normal file
8
tools/rodecomp/configure.sh
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
. /opt/td/env.sh
|
||||
TC=/opt/toolchain/VC/Tools/MSVC/14.29.30133
|
||||
SDK='/opt/toolchain/Program Files/Windows Kits/10/Include/10.0.19041.0'
|
||||
export INCLUDE="Z:\\opt\\toolchain\\VC\\Tools\\MSVC\\14.29.30133\\include;Z:\\opt\\toolchain\\VC\\Tools\\MSVC\\14.29.30133\\atlmfc\\include;$(printf '%s' "$SDK" | sed 's|/|\\|g' | sed 's/^/Z:/')\\ucrt;$(printf '%s' "$SDK" | sed 's|/|\\|g' | sed 's/^/Z:/')\\shared;$(printf '%s' "$SDK" | sed 's|/|\\|g' | sed 's/^/Z:/')\\um"
|
||||
cmake -S /data/repo -B /tmp/rodecomp-cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=/opt/td/cmake-toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||
python3 /tools/export-compile.py /tmp/rodecomp-cmake/compile_commands.json /data/repo/compile.json --source /data/repo --compiler "$TC/bin/Hostx64/x86/cl.exe" --include "$INCLUDE"
|
||||
49
tools/rodecomp/export-compile.py
Normal file
49
tools/rodecomp/export-compile.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"""Convert CMake's compile_commands.json to the upstream Clank compile schema.
|
||||
Run after configuring with the container's MSVC/Wine toolchain.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import pathlib
|
||||
import shlex
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('commands')
|
||||
parser.add_argument('output')
|
||||
parser.add_argument('--source', required=True)
|
||||
parser.add_argument('--toolchain', default='/opt/toolchain')
|
||||
parser.add_argument('--compiler', required=True)
|
||||
parser.add_argument('--include', required=True, help='Semicolon-separated MSVC/SDK include paths')
|
||||
args = parser.parse_args()
|
||||
source = pathlib.Path(args.source).resolve()
|
||||
|
||||
def replace(value):
|
||||
return value.replace('Z:' + str(source).replace('/', '\\'), '${SRC}').replace(str(source), '${SRC}').replace('Z:' + args.toolchain.replace('/', '\\'), '${TC}').replace(args.toolchain, '${TC}')
|
||||
|
||||
shapes, units = [], {}
|
||||
for row in json.loads(pathlib.Path(args.commands).read_text()):
|
||||
file = pathlib.Path(row['file']).resolve().relative_to(source).as_posix()
|
||||
command = row.get('arguments') or shlex.split(row['command'])
|
||||
# CMake's compiler launcher may precede cl.exe.
|
||||
compiler_index = next(i for i, arg in enumerate(command) if arg.lower().endswith(('cl.exe', 'cl-wine.sh')))
|
||||
flags = []
|
||||
skip = False
|
||||
for arg in command[compiler_index + 1:]:
|
||||
if skip:
|
||||
skip = False
|
||||
continue
|
||||
if arg in ('-o', '-MF', '-MT'):
|
||||
skip = True
|
||||
continue
|
||||
if arg.lower().startswith(('/fo', '/fd', '/fa')) or arg in ('/showIncludes',):
|
||||
continue
|
||||
if arg.replace('\\', '/').removeprefix('Z:') == str(pathlib.Path(row['file']).resolve()):
|
||||
continue
|
||||
flags.append(replace(arg))
|
||||
if '/c' not in flags and '-c' not in flags:
|
||||
flags.append('/c')
|
||||
if flags not in shapes:
|
||||
shapes.append(flags)
|
||||
units[file] = shapes.index(flags)
|
||||
result = {'compiler': replace(args.compiler), 'env': {'INCLUDE': replace(args.include)}, 'shapes': shapes, 'units': dict(sorted(units.items()))}
|
||||
pathlib.Path(args.output).write_text(json.dumps(result, indent=2) + '\n')
|
||||
print(f'Exported {len(units)} units in {len(shapes)} compile shapes')
|
||||
8
tools/rodecomp/import-admin.mjs
Normal file
8
tools/rodecomp/import-admin.mjs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import fs from 'node:fs';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
const db = new DatabaseSync(process.argv[2]);
|
||||
const u = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
|
||||
if(u.name !== 'ragnarok-clanker' || u.role !== 'admin' || u.status !== 'active') throw new Error('Unexpected administrator record');
|
||||
db.prepare('INSERT INTO users (name,password_hash,role,status,created_at,approved_at) VALUES (?,?,?,?,?,?)').run(u.name,u.password_hash,u.role,u.status,u.created_at,u.approved_at);
|
||||
console.log('Imported active administrator',u.name);
|
||||
db.close();
|
||||
28
tools/rodecomp/import-confirmations.mjs
Normal file
28
tools/rodecomp/import-confirmations.mjs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import fs from 'node:fs';
|
||||
import crypto from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
const [data, input] = process.argv.slice(2);
|
||||
const evidence = JSON.parse(fs.readFileSync(input, 'utf8'));
|
||||
const db = new DatabaseSync(path.join(data, 'td.sqlite'));
|
||||
const version = db.prepare('SELECT * FROM versions WHERE is_current=1').get();
|
||||
if(version.sha256 !== evidence.targetSha256) throw new Error('Target identity mismatch');
|
||||
const eligible = [], deferred = [];
|
||||
for(const row of evidence.confirmations) {
|
||||
const source = fs.readFileSync(path.join(data, 'repo', row.file), 'utf8').replaceAll('\r\n','\n');
|
||||
if(crypto.createHash('sha256').update(source).digest('hex') !== row.sourceSha256) throw new Error(`Source changed: ${row.file}`);
|
||||
const current = db.prepare('SELECT * FROM func_status WHERE symbol_id=?').get(row.symbol_id);
|
||||
if(!current || !['exact','match_unresolved','mismatch'].includes(current.status)) { deferred.push(row.symbol_id); continue; }
|
||||
eligible.push(row);
|
||||
}
|
||||
db.exec('BEGIN IMMEDIATE');
|
||||
try {
|
||||
for(const row of eligible) {
|
||||
db.prepare('UPDATE func_status SET confirmed=1 WHERE symbol_id=?').run(row.symbol_id);
|
||||
db.prepare("UPDATE tasks SET state='done',updated_at=? WHERE symbol_id=? AND state IN ('open','needs_cleanup','hard')").run(Date.now(),row.symbol_id);
|
||||
}
|
||||
db.prepare('INSERT INTO events(at,type,user_id,data) VALUES(?,?,NULL,?)').run(Date.now(),'historical_confirmations_imported',JSON.stringify({source:evidence.sourceDatabase,accepted:eligible.map(r=>r.symbol_id),deferred,method:'unchanged full source at historical confirmed commit; current compiler measurement retained'}));
|
||||
db.exec('COMMIT');
|
||||
} catch(error) { db.exec('ROLLBACK'); throw error; }
|
||||
console.log(JSON.stringify({imported:eligible.length,deferred:deferred.length,deferredIds:deferred}));
|
||||
db.close();
|
||||
9
tools/rodecomp/inspect-history.mjs
Normal file
9
tools/rodecomp/inspect-history.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import {DatabaseSync} from 'node:sqlite';
|
||||
const d=new DatabaseSync('/var/lib/docker/volumes/rodecomp_data/_data/td.sqlite',{readOnly:true});
|
||||
for(const t of d.prepare("SELECT name FROM sqlite_master WHERE type='table'").all()) {
|
||||
const cols=d.prepare(`PRAGMA table_info("${t.name}")`).all().filter(c=>/commit/.test(c.name));
|
||||
if(cols.length) console.log(t.name,JSON.stringify(cols.map(c=>c.name)));
|
||||
}
|
||||
console.log('attempts',d.prepare('SELECT state,count(*) n FROM attempts GROUP BY state').all());
|
||||
console.log('tasks',d.prepare('SELECT state,count(*) n FROM tasks GROUP BY state').all());
|
||||
d.close();
|
||||
78
tools/rodecomp/latest-commit-map
Normal file
78
tools/rodecomp/latest-commit-map
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
old new
|
||||
bb5dfeb7539efd7582332c19aa31ed2b16011c32 bb5dfeb7539efd7582332c19aa31ed2b16011c32
|
||||
fccca0f07a5f4a74ab8580940ca41cc4ce5a5fe3 fccca0f07a5f4a74ab8580940ca41cc4ce5a5fe3
|
||||
42e136cc165964abe83c38276b2b9d5008486a2f 42e136cc165964abe83c38276b2b9d5008486a2f
|
||||
f32ab174a5d9d8cd0a9fbfe9a9c69c203b9d4960 f32ab174a5d9d8cd0a9fbfe9a9c69c203b9d4960
|
||||
36cdedcff64b5e008bcb11ca6886ebc009cd2f62 36cdedcff64b5e008bcb11ca6886ebc009cd2f62
|
||||
44cd58b0a53d3046f834608131e2c99265b8c87b 44cd58b0a53d3046f834608131e2c99265b8c87b
|
||||
900df586ce1aab3a4e79036022cb43cfc1f6177c 900df586ce1aab3a4e79036022cb43cfc1f6177c
|
||||
e1a43312ab8c1edf63840e09c0d787c8a5f02e56 e1a43312ab8c1edf63840e09c0d787c8a5f02e56
|
||||
bbba01d4a710b6c37ec569b203eada453c6be940 2c3a07e1370c6d7f8d34f6c51f56b23a0d30b59e
|
||||
0ab026769728aef6b5c55cf8626efa10ed7a7458 bcc85a5ac6c3bb1db830fe96cfb4f9bd6c591928
|
||||
677141da932c457115e46e82579e41d801fcbf35 06d59f48ae4407a7ef43a9e39425a0ba7afd21c3
|
||||
a417b7deb79d2ca4aa689b4e90d74cff90af38c5 009c4419738d4c01e8687b851eaab50c75c52aa1
|
||||
3f1cb49c948e1d4089cd818fdbb10d613bdfb393 a299f80cfdf8d19b7de923da44d3c12e8e0dc6b9
|
||||
b5695645de003ec455e045b520ce71aa282f2a87 86c211e75683980269e5f14b295744d51f24e4a5
|
||||
e7b52df92b6a58911b77b0d5378e950db03e9758 542610ef3b9483e9d97e0a067f9312a2704bc100
|
||||
b3a6fde0b6c32c91d214a497e4adb2e2d1746b88 95bfcc63bd22fc910a87c1cb35f0833f445de100
|
||||
7c02f156c6ad3dab5dbf3d59212095ee782ee5a9 218df9f7380496351333f186ff5cd662af5f4d17
|
||||
6cf5f9d7bf5552c674171645e7a42d78e6d26503 e809f0b6909653ca9311a577a1cc7b2b3ac0cdbe
|
||||
5c8376d1d9aef10f6a6225a6fe87af5f64768755 16d978a98304f75ea3efee9f240c3aeeb5fdece1
|
||||
e8aad3180c861228bf01de07884cc4efd405b461 811fe72f054faf6442ba916f80489823c53fb09a
|
||||
ee9a53b14e7275003db8be2272d949fbfa073a99 5cd5924f2d43b054a5084a0a909ce68f6a1f0c5e
|
||||
02bda87b59d323a3264cf09496342d37cce8b6ae 608c465be9294d0f423e9f2de19ebbd1713dede1
|
||||
9e6e75ed01539cedbf3205380005b22af9adda83 41fc04b96844d19e3db712c4e71883d2930ed13f
|
||||
ae08409cdee56f4ecefb21a52b3ce3bdc4417bfc 9ff47a21d361100c5eeb080c1fce9c2ddab4a124
|
||||
dd530437ed1383f629030fe65321a4f0e30dcbb1 c9a831430a2406b985cd59ee47af7edffef8f7f4
|
||||
45c9089ee965c667f6d2914dc4d4720d9bd55d29 decfa50aa86f333035e5933e700ed2345f0fa2e7
|
||||
384af1063c88a14c5810538cb921e48c6bd901c4 9a842a452c0a70de16e3d2c6f3f98e0ea9812e67
|
||||
1dec64215f1df4b580ca20fca4a94f448dd22d42 3d1c885429f9ec11c3e0157256dbe6df839057be
|
||||
a341c48dc6a7c642c125b2009833090a17fbd210 75d9e4ab265be15c52b47088063ce4f40b3237f7
|
||||
5bc4450913afb42d9069d190cd9100de21a36eaa 84f1336564cb8617887d6e274e3d5e1b8efe4414
|
||||
2344f58ea46a9d820d8f36143e8de8261918697e 7dc69b8d9abed38dc0faf5c5fceeb7e576bf0da4
|
||||
cb72c354435b8b1f47b5cb348762dca1e4452a16 a890bcaf378f6dea18d927500ecce00f592b4f3a
|
||||
e827884d67d62b2ac0b1b7a8fcf3e92841fd2219 3a8f5f27d1c1f261a00e7057e69c7dc476f95ad6
|
||||
2ea6810686117f9c6ae6ae61b412f5255065ca52 c39bff50f46462c5428389867d003693fec2cf1c
|
||||
905272f64c07b98426981b9c64ca074a90d436b5 9b6a91f896fa8943047cf21f147c9cc793e8319a
|
||||
f5e31ed41c6944f4c1b96a9ba839148c0056e858 90784471a329905402a073c4b9d7b64a97e6dd2c
|
||||
6e61f1856cfd6d26527d553af3a84c0d62068fc6 4b0ebd89023b663ad63755f03bf291f44d992d2a
|
||||
446d9a05f1d46c3b90db1eff89fddf2eb54295ac 7253e44ee9ed73fbadd9b7cb89c2748b7a3e4eed
|
||||
9cf9d964268b0ccc906c8e7eb218c4dd009f28b3 3ac4853f152076c88147e8d338c1a08b14b63688
|
||||
f7e68749c47a3d75ffc327fb80c3ebb78e0c0edd 0c6592e128b7ac2a0f2a6de15b434ef2198e48d7
|
||||
9fa44f28e097c00a521429c936bcc2c74b486e0b de8c1653a32a396f074a8949e471483563ea5850
|
||||
323157d16b257e722fa0b5eb2aaaa23b086f9a30 6932b58a04a9dcc70f8caf69814fa7d94381c288
|
||||
8beea9315e2d9c74d279b99dfb3f7984e00005cf 774012e9e527e686d02b2824e94c125795743576
|
||||
2d6ff71d0bfaa488ea73ead7f1d73ae86ebb0588 11502f7787510fa05bbbdd2f5e4901728efe3cca
|
||||
33c0ceff16ff1e322e1cbe28cef7dd0738d56f70 285aad41ac259d2ced3d1073038dabeb742433f6
|
||||
8be837c06da826927f56e13d130766de1e55df37 5acb0a179eb543cb5258b2010b178634e0fa9ba3
|
||||
080b8a022592e0a3381a6a2dcd555fe035ca6557 7649a62b97b9c9a3c8467630dfead5c165dad535
|
||||
0e232733e71f234fc0201ceb20a372ba5ad3edf4 78bd93acb2c811ab70c27234a133e95c87a4476f
|
||||
a0ae08405c033ae41df05cfa80b78d345da46adf 31cf3b7a1b02ea8109101b7bef089038cb65ed7b
|
||||
2f1535092a5f065c3e27f1b8647b3367374cd450 c4130ce6528bcec09b249637f095ec69923bc489
|
||||
14fb6aa84120f8042d8cb39863114f14d38f23af d583b3461036c357326c697536bbf62c275ad10c
|
||||
3d3f8a432d0cc97909e3e27c87aac734eb08a134 ecbb015f5268e92628536eb081620dfaad447637
|
||||
0b0f39f980c2d0767130c5803e66b78328d10186 5841de39c9b74af02cd42c1ab99db83e414bb1f7
|
||||
da94c9385d845bffc5be504846536956b808bfc7 3f966658a2fb98e4537b9b07c937d3d17dc484d9
|
||||
163ff913eea8602ac380ec67fff1ce97fda79c9a 3ce9c4f7363ea7306e8d5dfcbe4117e4e0a42497
|
||||
ba9de6fa1f504e9ba6c65ccd10eda0e16c5cd222 f46515f5d8dd2aff45950c594092f89dd1abd3d7
|
||||
2da097c0292b335ba5c6e512351af40098c96538 58e7d31c2c2731dda08315eb6728fd7cc2bf1bd9
|
||||
74e4cd6fb4bff7d14cd981ec04f71b065e8811eb 3f1051e316a30d9dadaea03297455d70486d9006
|
||||
a7bafe9c8b90cefc1c47d5193ac7a5098d21c861 f823eeb9ec0f0e6089552ca3bf7a4242f6879bca
|
||||
9e0f3ecf18902a0401d718f2b83fe8931c67ac97 9a9b90dcbf5bcfba2dca1517f5d0f0bcb6c958ef
|
||||
4b960a15c9924e1f8875e67699348debbc58d2e1 ed5d006e2d3bd41ecb5bf079f2cf4fb22d1aa4b3
|
||||
8ff20fc80175395a019e5856cda0dd37dc090270 601cd5b7ccd4ec5dcbd7314b7fb9f614de8c0269
|
||||
5c44c4e376be4cb08e0721c170dcfbf892d039aa 49dc0a316d5924b71734dbb19d58ac911d6d864f
|
||||
a463c789dcef9f814b1b743c7463fae0a6e75879 7b6a0e099260a5f34b955908caf4a31fa476ec05
|
||||
c440c3ba10800e8976e322ae3e7fec4f6bbfb437 7d223430d37aa4d5fc751af72a80a93ba7163e50
|
||||
baa92caaac97c8410df189e7ff0170a52f4c08fa 9a96cfc477c915f1a66e30dcd3f8d9b7c5d3ced4
|
||||
7fcdbf2b932a4efc0d29887f5e74e91c691639ea 272a130de9adf37d4996f172437c562b8f062d2f
|
||||
a2909e08f1156bbce3d5ff161c963c537994e6f9 8332553b6a14ae92a6c91797006a5900ff53c323
|
||||
d60454407d83ae9c0ec2f3f6394117afe39f4a5f 01ba4af88ce8f7d33bc62c905c57e7b8c6cebe4d
|
||||
6c66031e8edd7d9da8756ad38a5761b7e4af9446 88f21dc6ffd2203e9ae671e1d00242195325860e
|
||||
05f35e32190e0d451fbd1f425287a60b0eaf96e8 ecb398f9e27cbf55b889d094547393022c371cfb
|
||||
3118d71a2e7d7945fc91d20a315a8fc6b9ede069 1ac1a6e27a9565d58ac47423960b45c835297c88
|
||||
0a29da58516f1a49a9b5730d60e4ec3350eea1df 1c3625a5cb55868e7efcd7786909ed8fd944f954
|
||||
e542c6ed80befaeb0c8fb1684f6450497d41a806 344bd14ebf148883fa7361f0b52d3676a7841d44
|
||||
63c8e679d0b49934c890d57b11235985af00f48e 685d0a819940ee51941f9dff0783a123e87a9409
|
||||
a3dc53369ca4310fb7b904311d0508bce14bfe9e 69323b7be14ec027ff116ea9b0b7795a9ba6b3f5
|
||||
f7f25ca3647d68b52d94764ad4c547e361f4915c 0c8d621bd1122747716cd0fded077207eaca3bc5
|
||||
21
tools/rodecomp/prepare-project.mjs
Normal file
21
tools/rodecomp/prepare-project.mjs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import fs from 'node:fs';
|
||||
import crypto from 'node:crypto';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
const repo = process.argv[2];
|
||||
const compilerHash = process.argv[3];
|
||||
if (!repo || !/^[a-f0-9]{64}$/.test(compilerHash ?? '')) throw new Error('usage: prepare-project.mjs <repo> <compiler-sha256>');
|
||||
const file = `${repo}/decomp.json`;
|
||||
const p = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
p.name = 'rodecomp';
|
||||
p.compileCommands = 'compile.json';
|
||||
delete p.verification;
|
||||
delete p.build;
|
||||
p.matchPolicy = { autoMergeIdentical: true, approvals: 1, approvalsWithHeaderChanges: 2, maxCleanupCycles: 5, peerReviews: 3, peerMargin: 3, peerRejectMargin: 2 };
|
||||
fs.writeFileSync(file, JSON.stringify(p, null, 2) + '\n');
|
||||
fs.writeFileSync(`${repo}/toolchain.lock.json`, JSON.stringify({ragnarok:{family:'msvc',version:'19.29.30159',machine:'x86',image:'rodecomp/compiler:v142',clSha256:compilerHash,sdkVersion:'10.0.19041.0'}}, null, 2) + '\n');
|
||||
let guide = fs.readFileSync('/app/template/AGENTS.md', 'utf8').replace('a C++ program built with MSVC 2010', 'Ragnarok Online Ragexe 2026-02-19 (x86), using MSVC v142 19.29.30159').replace("(Ghidra's guess, a starting point only)", '(imported IDA pseudocode where available, a starting point only)');
|
||||
guide += '\n## Ragnarok target\n\nUse only this workspace’s configured server and clank-together MCP. The project is rodecomp, version 2026-02-19. Preserve existing stable IDs and OFFSET/STATIC provenance comments. Missing original evidence is a blocker to a verdict, not permission to invent behavior. The source contains subsystem libraries, not a completed standalone game. Clank compiles individual units from compile.json; it does not run CMake or CTest during checks.\n';
|
||||
fs.writeFileSync(`${repo}/AGENTS.md`, guide);
|
||||
execFileSync('git',['-c',`safe.directory=${repo}`,'add','decomp.json','compile.json','toolchain.lock.json','AGENTS.md'],{cwd:repo,stdio:'inherit'});
|
||||
execFileSync('git',['-c',`safe.directory=${repo}`,'-c','user.name=Clank Together','-c','user.email=bot@clank-together.invalid','commit','-m','Configure rodecomp v142 x86 contributor builds'],{cwd:repo,stdio:'inherit'});
|
||||
console.log('Configured rodecomp with pinned compiler',compilerHash);
|
||||
30
tools/rodecomp/provision-forgejo.mjs
Normal file
30
tools/rodecomp/provision-forgejo.mjs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import fs from 'node:fs';
|
||||
import crypto from 'node:crypto';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
const data='/var/lib/docker/volumes/rodecomp_data/_data';
|
||||
const secretsFile=data+'/forgejo-admin.json';
|
||||
const secrets=fs.existsSync(secretsFile)?JSON.parse(fs.readFileSync(secretsFile,'utf8')):{username:'clank-admin',password:crypto.randomBytes(30).toString('base64url')};
|
||||
if(!fs.existsSync(secretsFile))fs.writeFileSync(secretsFile,JSON.stringify(secrets),{mode:0o600});
|
||||
const fj=(args)=>execFileSync('docker',['exec','--user','git','rodecomp-forgejo-1','forgejo','--config','/data/gitea/conf/app.ini',...args],{encoding:'utf8'});
|
||||
const users=fj(['admin','user','list']);
|
||||
if(!users.includes(secrets.username))fj(['admin','user','create','--admin','--username',secrets.username,'--email','admin@rodecomp.online','--password',secrets.password,'--must-change-password=false']);
|
||||
if(!users.includes('clank-bot'))fj(['admin','user','create','--username','clank-bot','--email','bot@rodecomp.online','--random-password','--must-change-password=false']);
|
||||
async function api(method,url,body){const r=await fetch('http://forgejo:3000/api/v1'+url,{method,headers:{Authorization:'Basic '+Buffer.from(secrets.username+':'+secrets.password).toString('base64'),'content-type':'application/json'},body:body?JSON.stringify(body):undefined});if(!r.ok)throw Error(method+' '+url+' '+r.status+' '+await r.text());return r.status===204?null:r.json();}
|
||||
let r=await fetch('http://forgejo:3000/api/v1/orgs/clank');
|
||||
if(r.status===404)await api('POST','/orgs',{username:'clank',full_name:'Clank Together',visibility:'public'});
|
||||
r=await fetch('http://forgejo:3000/api/v1/repos/clank/rodecomp');
|
||||
if(r.status===404)await api('POST','/orgs/clank/repos',{name:'rodecomp',description:'Ragnarok Online reconstruction through Clank Together',private:false,default_branch:'main'});
|
||||
await api('PATCH','/repos/clank/rodecomp',{has_pull_requests:false,has_actions:false,has_packages:false,has_releases:false,has_projects:false,website:'https://clank.rodecomp.online/'});
|
||||
await api('PUT','/repos/clank/rodecomp/collaborators/clank-bot',{permission:'write'});
|
||||
const git=(args)=>execFileSync('git',['-C',data+'/repo',...args],{encoding:'utf8',stdio:['ignore','pipe','pipe']});
|
||||
if(!git(['remote']).split('\n').includes('forgejo')){
|
||||
const token=fj(['admin','user','generate-access-token','--username','clank-bot','--token-name','coordinator','--scopes','write:repository','--raw']).trim();
|
||||
git(['remote','add','forgejo',`http://clank-bot:${token}@forgejo:3000/clank/rodecomp.git`]);
|
||||
}
|
||||
git(['push','forgejo','HEAD:refs/heads/main']);
|
||||
const protections=await api('GET','/repos/clank/rodecomp/branch_protections');
|
||||
if(!protections.some(p=>p.rule_name==='main'))await api('POST','/repos/clank/rodecomp/branch_protections',{rule_name:'main',enable_push:true,enable_push_whitelist:true,push_whitelist_usernames:['clank-bot'],block_on_rejected_reviews:false});
|
||||
const file=data+'/server.json';const config=JSON.parse(fs.readFileSync(file,'utf8'));
|
||||
Object.assign(config,{pushRemote:'forgejo',gitCloneUrl:'https://clank.rodecomp.online/git/clank/rodecomp.git',gitWebUrl:'https://clank.rodecomp.online/git/clank/rodecomp'});
|
||||
fs.writeFileSync(file,JSON.stringify(config,null,2)+'\n');
|
||||
console.log('Published clank/rodecomp; main protected; coordinator push enabled. Forgejo admin credential stored privately in '+secretsFile);
|
||||
27
tools/rodecomp/publish-history.mjs
Normal file
27
tools/rodecomp/publish-history.mjs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import fs from 'node:fs';
|
||||
import {DatabaseSync} from 'node:sqlite';
|
||||
import {execFileSync} from 'node:child_process';
|
||||
const data='/var/lib/docker/volumes/rodecomp_data/_data';
|
||||
const map=new Map(fs.readFileSync(data+'/repo/.git/filter-repo/commit-map','utf8').trim().split('\n').slice(1).map(l=>l.trim().split(/\s+/)));
|
||||
map.set('41ea356f358ba643231157a498fff099f077f9d6', execFileSync('git',['-C',data+'/repo','rev-parse','HEAD'],{encoding:'utf8'}).trim());
|
||||
const db=new DatabaseSync(data+'/td.sqlite');
|
||||
db.exec('BEGIN IMMEDIATE');
|
||||
let updates=0;
|
||||
try {
|
||||
for(const table of db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all()) {
|
||||
const cols=db.prepare(`PRAGMA table_info("${table.name}")`).all().filter(c=>c.name.includes('commit'));
|
||||
for(const col of cols)for(const [old,next] of map){if(old===next)continue;if(/^0+$/.test(next))continue;updates+=Number(db.prepare(`UPDATE "${table.name}" SET "${col.name}"=? WHERE "${col.name}"=?`).run(next,old).changes);}
|
||||
}
|
||||
db.exec('COMMIT');
|
||||
} catch(e){db.exec('ROLLBACK');throw e;}
|
||||
db.close();
|
||||
const admin=JSON.parse(fs.readFileSync(data+'/forgejo-admin.json','utf8'));
|
||||
async function api(method,path,body){const r=await fetch('http://forgejo:3000/api/v1'+path,{method,headers:{Authorization:'Basic '+Buffer.from(admin.username+':'+admin.password).toString('base64'),'content-type':'application/json'},body:body?JSON.stringify(body):undefined});if(!r.ok)throw Error(method+' '+path+' '+r.status+' '+await r.text());return r.status===204?null:r.json();}
|
||||
const route='/repos/clank/rodecomp/branch_protections';
|
||||
const protections=await api('GET',route);
|
||||
const main=protections.find(p=>p.rule_name==='main');
|
||||
if(!main)throw Error('Expected main protection');
|
||||
await api('DELETE',route+'/main');
|
||||
try {execFileSync('git',['-C',data+'/repo','push','--force-with-lease=refs/heads/main:41ea356f358ba643231157a498fff099f077f9d6','forgejo','HEAD:refs/heads/main'],{stdio:'pipe'});}
|
||||
finally {await api('POST',route,{rule_name:'main',enable_push:true,enable_push_whitelist:true,push_whitelist_usernames:['clank-bot'],block_on_rejected_reviews:false});}
|
||||
console.log('Updated commit references:',updates,'; rewritten main published; protection restored');
|
||||
12
tools/rodecomp/remove-verification.mjs
Normal file
12
tools/rodecomp/remove-verification.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import fs from 'node:fs';
|
||||
import {execFileSync} from 'node:child_process';
|
||||
const repo='/data/repo';
|
||||
const cmake=repo+'/CMakeLists.txt';
|
||||
const text=fs.readFileSync(cmake,'utf8');
|
||||
if(!text.includes('add_subdirectory(Verification)'))throw Error('Expected Verification target');
|
||||
fs.writeFileSync(cmake,text.replace(/^add_subdirectory\(Verification\)\r?\n?/m,''));
|
||||
const file=repo+'/compile.json';const cfg=JSON.parse(fs.readFileSync(file,'utf8'));
|
||||
for(const unit of Object.keys(cfg.units))if(unit.startsWith('Verification/'))delete cfg.units[unit];
|
||||
fs.writeFileSync(file,JSON.stringify(cfg,null,2)+'\n');
|
||||
execFileSync('git',['-C',repo,'add','CMakeLists.txt','compile.json']);
|
||||
execFileSync('git',['-C',repo,'-c','user.name=Clank Together','-c','user.email=bot@clank-together.invalid','commit','-m','Remove build references to deleted verification sources'],{stdio:'inherit'});
|
||||
21
tools/rodecomp/reset-history.mjs
Normal file
21
tools/rodecomp/reset-history.mjs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import fs from 'node:fs';
|
||||
import {execFileSync} from 'node:child_process';
|
||||
import {DatabaseSync} from 'node:sqlite';
|
||||
const data='/var/lib/docker/volumes/rodecomp_data/_data',repo=data+'/repo';
|
||||
const git=(args,opts={})=>execFileSync('git',['-C',repo,...args],{encoding:'utf8',...opts}).trim();
|
||||
if(git(['status','--porcelain']))throw Error('Canonical checkout has uncommitted changes');
|
||||
const previous=git(['rev-parse','HEAD']),tree=git(['rev-parse','HEAD^{tree}']);
|
||||
const identity={...process.env,GIT_AUTHOR_NAME:'ragnarok-clanker',GIT_AUTHOR_EMAIL:'ragnarok-clanker',GIT_COMMITTER_NAME:'ragnarok-clanker',GIT_COMMITTER_EMAIL:'ragnarok-clanker'};
|
||||
const root=git(['commit-tree',tree],{input:'Initial rodecomp source snapshot\n',env:identity});
|
||||
fs.writeFileSync(data+'/history-reset.json',JSON.stringify({previous,root,tree},null,2));
|
||||
git(['update-ref','refs/heads/main',root,previous]);
|
||||
for(const ref of git(['for-each-ref','--format=%(refname)']).split('\n'))if(ref&&ref!=='refs/heads/main')git(['update-ref','-d',ref]);
|
||||
git(['reset','--hard',root]);git(['config','user.name','ragnarok-clanker']);git(['config','user.email','ragnarok-clanker']);
|
||||
const db=new DatabaseSync(data+'/td.sqlite');db.exec('BEGIN IMMEDIATE');
|
||||
try{for(const table of db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all())for(const c of db.prepare(`PRAGMA table_info("${table.name}")`).all().filter(c=>c.name.includes('commit')))db.prepare(`UPDATE "${table.name}" SET "${c.name}"=? WHERE "${c.name}" IS NOT NULL`).run(root);db.exec('COMMIT');}catch(e){db.exec('ROLLBACK');throw e;}db.close();
|
||||
const a=JSON.parse(fs.readFileSync(data+'/forgejo-admin.json','utf8'));
|
||||
async function api(method,url,body){const r=await fetch('http://forgejo:3000/api/v1'+url,{method,headers:{Authorization:'Basic '+Buffer.from(a.username+':'+a.password).toString('base64'),'content-type':'application/json'},body:body?JSON.stringify(body):undefined});if(!r.ok)throw Error(method+' '+r.status+' '+await r.text());}
|
||||
const route='/repos/clank/rodecomp/branch_protections';await api('DELETE',route+'/main');
|
||||
try{git(['push','--force-with-lease=refs/heads/main:'+previous,'forgejo','HEAD:refs/heads/main']);}finally{await api('POST',route,{rule_name:'main',enable_push:true,enable_push_whitelist:true,push_whitelist_usernames:['clank-bot'],block_on_rejected_reviews:false});}
|
||||
git(['reflog','expire','--expire=now','--all']);git(['gc','--prune=now']);
|
||||
console.log(JSON.stringify({previous,root,tree,commits:git(['rev-list','--count','--all'])}));
|
||||
10
tools/rodecomp/rewrite-identity.py
Normal file
10
tools/rodecomp/rewrite-identity.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import subprocess
|
||||
repo='/data/repo'
|
||||
subprocess.run(['git','-C',repo,'filter-repo','--force',
|
||||
'--blob-callback', "import re; blob.data = re.sub(rb'(?i)[a-z0-9.!#$%&*+/=?^_`{|}~-]*spl3en[a-z0-9.!#$%&*+/=?^_`{|}~-]*@[a-z0-9.-]+', b'ragnarok-clanker', blob.data); blob.data = re.sub(rb'(?i)spl3en', b'ragnarok-clanker', blob.data)",
|
||||
'--message-callback', "import re; message = re.sub(rb'(?i)[a-z0-9.!#$%&*+/=?^_`{|}~-]*spl3en[a-z0-9.!#$%&*+/=?^_`{|}~-]*@[a-z0-9.-]+', b'ragnarok-clanker', message); return re.sub(rb'(?i)spl3en', b'ragnarok-clanker', message)",
|
||||
'--name-callback', "import re; return re.sub(rb'(?i)spl3en', b'ragnarok-clanker', name)",
|
||||
'--email-callback', "return b'ragnarok-clanker' if b'spl3en' in email.lower() else email",
|
||||
'--filename-callback', "import re; return re.sub(rb'(?i)spl3en', b'ragnarok-clanker', filename)",
|
||||
'--refname-callback', "import re; return re.sub(rb'(?i)spl3en', b'ragnarok-clanker', refname)"
|
||||
],check=True)
|
||||
Loading…
Add table
Add a link
Reference in a new issue