[HLCC-ICE-000001][ZY-CONTRIB-20260723-001] feat: 以来光者贡献链启用冰朔第五域个人子频道
This commit is contained in:
commit
5615453e4e
660 changed files with 122355 additions and 0 deletions
|
|
@ -0,0 +1,58 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Activate the reviewed full-offline HLCC candidate unit without root shell."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import signal
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
|
||||
SERVICE = "hlcc-jd-candidate.service"
|
||||
HEALTH_URL = "http://127.0.0.1:3341/health"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
result = subprocess.run(
|
||||
["/usr/bin/systemctl", "show", "--property=MainPID", "--value", SERVICE],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
pid = int(result.stdout.strip())
|
||||
if pid <= 1:
|
||||
raise RuntimeError("HLCC candidate bootstrap has no active main process")
|
||||
|
||||
process_root = pathlib.Path("/proc") / str(pid)
|
||||
if process_root.stat().st_uid != os.getuid():
|
||||
raise RuntimeError("refusing to signal a process owned by another user")
|
||||
command = (process_root / "cmdline").read_bytes().replace(b"\0", b" ").decode("utf-8", "replace")
|
||||
if "/hololake-code-channel/jd-candidate/hlcc-bootstrap.py" not in command:
|
||||
raise RuntimeError("refusing to signal an unexpected process")
|
||||
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
for _attempt in range(40):
|
||||
try:
|
||||
with urllib.request.urlopen(HEALTH_URL, timeout=2) as response:
|
||||
payload = json.load(response)
|
||||
if (
|
||||
payload.get("ok") is True
|
||||
and payload.get("mode") == "isolated-candidate"
|
||||
and payload.get("version") == "16.0.1"
|
||||
and payload.get("package_profile") == "full-offline-v16.0.1"
|
||||
and payload.get("ready") is True
|
||||
and payload.get("stage") == "ready"
|
||||
):
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(1)
|
||||
raise RuntimeError("full-offline HLCC personal channel did not become ready")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const root = __dirname;
|
||||
const script = fs.readFileSync(path.join(root, "activate-staged-candidate.py"), "utf8");
|
||||
const unit = fs.readFileSync(path.join(root, "hlcc-jd-candidate-activator.service"), "utf8");
|
||||
const bootstrap = fs.readFileSync(path.join(root, "hlcc-bootstrap.py"), "utf8");
|
||||
|
||||
assert.match(bootstrap, /hlcc-offline\/16\.0\.1/);
|
||||
assert.match(bootstrap, /forgejo-upstream-all\.bundle/);
|
||||
assert.match(bootstrap, /guanghu-code-channel\.bundle/);
|
||||
assert.match(bootstrap, /full-offline-v16\.0\.1/);
|
||||
assert.match(script, /systemctl", "show", "--property=MainPID"/);
|
||||
assert.match(script, /process_root\.stat\(\)\.st_uid != os\.getuid\(\)/);
|
||||
assert.match(script, /"\/hololake-code-channel\/jd-candidate\/hlcc-bootstrap\.py" not in command/);
|
||||
assert.match(script, /os\.kill\(pid, signal\.SIGTERM\)/);
|
||||
assert.match(script, /payload\.get\("mode"\) == "isolated-candidate"/);
|
||||
assert.match(script, /payload\.get\("ready"\) is True/);
|
||||
assert.match(script, /payload\.get\("stage"\) == "ready"/);
|
||||
assert.doesNotMatch(script, /shell=True|systemctl", "(?:restart|stop|start)"/);
|
||||
assert.match(unit, /^User=guanghu$/m);
|
||||
assert.match(unit, /^NoNewPrivileges=true$/m);
|
||||
assert.match(unit, /^ProtectSystem=strict$/m);
|
||||
|
||||
console.log("HLCC full-offline candidate activator: PASS");
|
||||
40
server-tools/hololake-code-channel/jd-candidate/app.ini
Normal file
40
server-tools/hololake-code-channel/jd-candidate/app.ini
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
APP_NAME = 光湖代码频道
|
||||
RUN_USER = guanghu
|
||||
RUN_MODE = prod
|
||||
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
PATH = /var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/hlcc.db
|
||||
|
||||
[repository]
|
||||
ROOT = /var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories
|
||||
|
||||
[server]
|
||||
DOMAIN = guanghulab.com
|
||||
HTTP_ADDR = 127.0.0.1
|
||||
HTTP_PORT = 3340
|
||||
ROOT_URL = https://guanghulab.com/code/
|
||||
DISABLE_SSH = true
|
||||
LFS_START_SERVER = true
|
||||
OFFLINE_MODE = true
|
||||
|
||||
[service]
|
||||
DISABLE_REGISTRATION = true
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = true
|
||||
|
||||
[actions]
|
||||
ENABLED = false
|
||||
|
||||
[mirror]
|
||||
ENABLED = false
|
||||
|
||||
[other]
|
||||
SHOW_FOOTER_BRANDING = false
|
||||
SHOW_FOOTER_VERSION = false
|
||||
SHOW_FOOTER_TEMPLATE_LOAD_TIME = false
|
||||
|
||||
[cron.update_checker]
|
||||
ENABLED = false
|
||||
|
|
@ -0,0 +1,528 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Verify and start the isolated JD HoloLake Code Channel candidate."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import http.server
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
|
||||
VERSION = "16.0.1"
|
||||
UPSTREAM_FINGERPRINT = "EB114F5E6C0DC2BCDD183550A4B61A2DC5923710"
|
||||
STATE_ROOT = pathlib.Path("/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1")
|
||||
LEGACY_DB = STATE_ROOT / "data" / "owner-identity-source.db"
|
||||
OWNER_NAME = "bingshuo"
|
||||
CHANNEL_REPOSITORY = "fifth-domain"
|
||||
LEGACY_REPOSITORY_URL = "https://guanghulab.com/fifth-domain/bingshuo/fifth-domain.git"
|
||||
SEED_COMMIT_NUMBER = "HLCC-ICE-000001"
|
||||
SEED_CONTRIBUTION_NUMBER = "ZY-CONTRIB-20260723-001"
|
||||
RELAY_ROOT = "https://guanghubingshuo.com/hlcc-offline/16.0.1"
|
||||
BINARY_NAME = f"forgejo-{VERSION}-linux-amd64"
|
||||
EXPECTED = {
|
||||
BINARY_NAME: "7a4c568136650c10498a9d3d62c7fd630a0cf09c166293ebd78708248f6398fc",
|
||||
f"{BINARY_NAME}.asc": "1c0ca36df3adb0a7692b6bdc84d7886001ca0c6d0408e67c9d232d2f33cecc71",
|
||||
"forgejo-release-key.asc": "6fae8894c671ce2397cb35fe40c324f73deade6b4cb3cd6cedd1d2b248e0e3ea",
|
||||
"forgejo-upstream-all.bundle": "c33bd074d9b2896259e86ebe03ad31ccdd8ff71897beed4320081fa03b15381f",
|
||||
"guanghu-code-channel.bundle": "fc53740259d108128e69f5a809cec438ecf3158175617574ba55b8612c5eaa6c",
|
||||
"MANIFEST.sha256": "d564c3b600d4b7a199d8a04ce505ceabf81993ca74fa440805601d55e550f185",
|
||||
}
|
||||
|
||||
STATUS = {
|
||||
"ok": True,
|
||||
"mode": "bootstrap",
|
||||
"version": VERSION,
|
||||
"code": "HLCC-JD-CANDIDATE-01",
|
||||
"ready": False,
|
||||
"stage": "starting",
|
||||
"package_profile": "full-offline-v16.0.1",
|
||||
}
|
||||
STATUS_LOCK = threading.Lock()
|
||||
|
||||
|
||||
def set_status(**values: object) -> None:
|
||||
with STATUS_LOCK:
|
||||
STATUS.update(values)
|
||||
|
||||
|
||||
class HealthHandler(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self) -> None: # noqa: N802 - BaseHTTPRequestHandler API
|
||||
if self.path != "/health":
|
||||
self.send_error(404)
|
||||
return
|
||||
with STATUS_LOCK:
|
||||
payload = json.dumps(STATUS, ensure_ascii=False).encode("utf-8")
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.send_header("Content-Length", str(len(payload)))
|
||||
self.end_headers()
|
||||
self.wfile.write(payload)
|
||||
|
||||
def log_message(self, _format: str, *_args: object) -> None:
|
||||
return
|
||||
|
||||
|
||||
def sha256(path: pathlib.Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def download_verified(name: str) -> pathlib.Path:
|
||||
destination = STATE_ROOT / "release" / name
|
||||
if destination.is_file() and sha256(destination) == EXPECTED[name]:
|
||||
return destination
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = destination.with_suffix(destination.suffix + ".partial")
|
||||
temporary.unlink(missing_ok=True)
|
||||
request = urllib.request.Request(
|
||||
f"{RELAY_ROOT}/{name}",
|
||||
headers={"User-Agent": "HoloLake-Code-Channel/16.0.1"},
|
||||
)
|
||||
with urllib.request.urlopen(request, timeout=45) as response, temporary.open("wb") as output:
|
||||
shutil.copyfileobj(response, output, length=1024 * 1024)
|
||||
if sha256(temporary) != EXPECTED[name]:
|
||||
temporary.unlink(missing_ok=True)
|
||||
raise RuntimeError(f"sha256 mismatch: {name}")
|
||||
temporary.replace(destination)
|
||||
return destination
|
||||
|
||||
|
||||
def verify_release(files: dict[str, pathlib.Path]) -> None:
|
||||
manifest = files["MANIFEST.sha256"].read_text(encoding="utf-8")
|
||||
for name in (
|
||||
BINARY_NAME,
|
||||
f"{BINARY_NAME}.asc",
|
||||
"forgejo-release-key.asc",
|
||||
"forgejo-upstream-all.bundle",
|
||||
"guanghu-code-channel.bundle",
|
||||
):
|
||||
expected_line = f"{EXPECTED[name]} {name}"
|
||||
if expected_line not in manifest.splitlines():
|
||||
raise RuntimeError(f"manifest entry mismatch: {name}")
|
||||
|
||||
gpg_home = STATE_ROOT / "gpg"
|
||||
gpg_home.mkdir(parents=True, exist_ok=True)
|
||||
gpg_home.chmod(0o700)
|
||||
environment = {**os.environ, "GNUPGHOME": str(gpg_home)}
|
||||
subprocess.run(
|
||||
["gpg", "--batch", "--import", str(files["forgejo-release-key.asc"])],
|
||||
check=True,
|
||||
env=environment,
|
||||
stdout=subprocess.DEVNULL,
|
||||
)
|
||||
fingerprint = subprocess.run(
|
||||
["gpg", "--batch", "--with-colons", "--fingerprint", UPSTREAM_FINGERPRINT],
|
||||
check=True,
|
||||
env=environment,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
).stdout
|
||||
if f"fpr:::::::::{UPSTREAM_FINGERPRINT}:" not in fingerprint:
|
||||
raise RuntimeError("release key fingerprint mismatch")
|
||||
subprocess.run(
|
||||
[
|
||||
"gpg",
|
||||
"--batch",
|
||||
"--verify",
|
||||
str(files[f"{BINARY_NAME}.asc"]),
|
||||
str(files[BINARY_NAME]),
|
||||
],
|
||||
check=True,
|
||||
env=environment,
|
||||
stdout=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
|
||||
def wait_for_candidate(process: subprocess.Popen[bytes]) -> None:
|
||||
url = "http://127.0.0.1:3340/api/healthz"
|
||||
for _attempt in range(90):
|
||||
if process.poll() is not None:
|
||||
raise RuntimeError(f"candidate exited with code {process.returncode}")
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=2) as response:
|
||||
payload = json.load(response)
|
||||
if payload.get("status") == "pass":
|
||||
return
|
||||
except Exception: # Candidate is still starting.
|
||||
pass
|
||||
time.sleep(1)
|
||||
raise RuntimeError("candidate readiness timeout")
|
||||
|
||||
|
||||
def immutable_source_commit(script_path: pathlib.Path | None = None) -> str:
|
||||
source = (script_path or pathlib.Path(__file__)).resolve()
|
||||
matches = [part for part in source.parts if re.fullmatch(r"[0-9a-f]{40}", part)]
|
||||
if len(matches) != 1:
|
||||
raise RuntimeError("immutable release commit unavailable")
|
||||
return matches[0]
|
||||
|
||||
|
||||
def owner_identity_columns(connection: sqlite3.Connection) -> list[str]:
|
||||
return [row[1] for row in connection.execute("pragma table_info(user)")]
|
||||
|
||||
|
||||
def migrate_owner_identity(
|
||||
legacy_db: pathlib.Path = LEGACY_DB,
|
||||
channel_db: pathlib.Path | None = None,
|
||||
receipt_path: pathlib.Path | None = None,
|
||||
) -> str:
|
||||
target = channel_db or STATE_ROOT / "data" / "hlcc.db"
|
||||
receipt = receipt_path or STATE_ROOT / "data" / "owner-migration-receipt.json"
|
||||
if not legacy_db.is_file() or not target.is_file():
|
||||
raise RuntimeError("owner migration database unavailable")
|
||||
|
||||
legacy = sqlite3.connect(f"file:{legacy_db}?mode=ro", uri=True, timeout=15)
|
||||
channel = sqlite3.connect(target, timeout=15)
|
||||
try:
|
||||
existing = channel.execute(
|
||||
"select is_active, is_admin from user where lower_name = ?",
|
||||
(OWNER_NAME,),
|
||||
).fetchall()
|
||||
if existing:
|
||||
if len(existing) != 1 or existing[0] != (1, 1):
|
||||
raise RuntimeError("channel owner identity mismatch")
|
||||
return "already-present"
|
||||
|
||||
source_rows = legacy.execute(
|
||||
"select * from user where lower_name = ?",
|
||||
(OWNER_NAME,),
|
||||
).fetchall()
|
||||
if len(source_rows) != 1:
|
||||
raise RuntimeError("legacy owner identity mismatch")
|
||||
|
||||
old_columns = owner_identity_columns(legacy)
|
||||
new_info = list(channel.execute("pragma table_info(user)"))
|
||||
new_columns = {row[1] for row in new_info}
|
||||
missing = [
|
||||
row[1]
|
||||
for row in new_info
|
||||
if row[3] and row[4] is None and row[1] != "id" and row[1] not in old_columns
|
||||
]
|
||||
if missing:
|
||||
raise RuntimeError("channel owner schema has unsupported required columns")
|
||||
|
||||
copied_columns = [name for name in old_columns if name in new_columns and name != "id"]
|
||||
values = dict(zip(old_columns, source_rows[0]))
|
||||
for counter in ("num_repos", "num_stars", "num_followers", "num_following"):
|
||||
if counter in values:
|
||||
values[counter] = 0
|
||||
if "use_custom_avatar" in values:
|
||||
values["use_custom_avatar"] = 0
|
||||
if "prohibit_login" in values:
|
||||
values["prohibit_login"] = 0
|
||||
|
||||
backup_dir = target.parent / "backups"
|
||||
backup_dir.mkdir(parents=True, exist_ok=True)
|
||||
backup_path = backup_dir / "hlcc-before-owner-migration.db"
|
||||
backup = sqlite3.connect(backup_path)
|
||||
try:
|
||||
channel.backup(backup)
|
||||
finally:
|
||||
backup.close()
|
||||
backup_path.chmod(0o600)
|
||||
|
||||
placeholders = ",".join("?" for _name in copied_columns)
|
||||
column_sql = ",".join(f'"{name}"' for name in copied_columns)
|
||||
with channel:
|
||||
channel.execute(
|
||||
f"insert into user ({column_sql}) values ({placeholders})",
|
||||
[values[name] for name in copied_columns],
|
||||
)
|
||||
verified = channel.execute(
|
||||
"select id, is_active, is_admin from user where lower_name = ?",
|
||||
(OWNER_NAME,),
|
||||
).fetchall()
|
||||
if len(verified) != 1 or verified[0][1:] != (1, 1):
|
||||
raise RuntimeError("channel owner migration verification failed")
|
||||
|
||||
receipt.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema": "guanghu.hlcc-owner-migration/v1",
|
||||
"owner": OWNER_NAME,
|
||||
"identity_only": True,
|
||||
"password_hash_preserved": True,
|
||||
"access_tokens_migrated": False,
|
||||
"repositories_migrated": False,
|
||||
"result": "VERIFIED",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
receipt.chmod(0o600)
|
||||
return "migrated"
|
||||
finally:
|
||||
channel.close()
|
||||
legacy.close()
|
||||
|
||||
|
||||
def api_json(
|
||||
method: str,
|
||||
path: str,
|
||||
token: str = "",
|
||||
body: dict[str, object] | None = None,
|
||||
) -> tuple[int, dict[str, object]]:
|
||||
headers = {"Accept": "application/json"}
|
||||
data = None
|
||||
if token:
|
||||
headers["Authorization"] = f"token {token}"
|
||||
if body is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
data = json.dumps(body).encode("utf-8")
|
||||
request = urllib.request.Request(
|
||||
f"http://127.0.0.1:3340{path}",
|
||||
data=data,
|
||||
headers=headers,
|
||||
method=method,
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=15) as response:
|
||||
raw = response.read()
|
||||
return response.status, json.loads(raw or b"{}")
|
||||
except urllib.error.HTTPError as error:
|
||||
raw = error.read()
|
||||
try:
|
||||
payload = json.loads(raw or b"{}")
|
||||
except json.JSONDecodeError:
|
||||
payload = {}
|
||||
return error.code, payload
|
||||
|
||||
|
||||
def delete_bootstrap_token(channel_db: pathlib.Path, token_name: str) -> None:
|
||||
connection = sqlite3.connect(channel_db, timeout=15)
|
||||
try:
|
||||
with connection:
|
||||
owner = connection.execute(
|
||||
"select id from user where lower_name = ?",
|
||||
(OWNER_NAME,),
|
||||
).fetchone()
|
||||
if owner:
|
||||
connection.execute(
|
||||
"delete from access_token where uid = ? and name = ?",
|
||||
(owner[0], token_name),
|
||||
)
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
def generate_bootstrap_token(binary: pathlib.Path, token_name: str) -> str:
|
||||
channel_db = STATE_ROOT / "data" / "hlcc.db"
|
||||
delete_bootstrap_token(channel_db, token_name)
|
||||
result = subprocess.run(
|
||||
[
|
||||
str(binary),
|
||||
"admin",
|
||||
"user",
|
||||
"generate-access-token",
|
||||
"--username",
|
||||
OWNER_NAME,
|
||||
"--token-name",
|
||||
token_name,
|
||||
"--scopes",
|
||||
"write:repository",
|
||||
"--raw",
|
||||
"--config",
|
||||
str(STATE_ROOT / "config" / "app.ini"),
|
||||
"--work-path",
|
||||
str(STATE_ROOT / "data"),
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
token = result.stdout.strip().splitlines()[-1]
|
||||
if not re.fullmatch(r"[A-Za-z0-9_-]{32,160}", token):
|
||||
raise RuntimeError("bootstrap access token format invalid")
|
||||
return token
|
||||
|
||||
|
||||
def seed_fifth_domain_channel(binary: pathlib.Path) -> str:
|
||||
status, repository = api_json("GET", f"/api/v1/repos/{OWNER_NAME}/{CHANNEL_REPOSITORY}")
|
||||
if status == 200:
|
||||
if repository.get("private") is not False:
|
||||
raise RuntimeError("existing channel repository is not public")
|
||||
if repository.get("empty") is False and repository.get("default_branch") == "main":
|
||||
return "already-present"
|
||||
if repository.get("empty") is not True:
|
||||
raise RuntimeError("existing channel repository state invalid")
|
||||
elif status != 404:
|
||||
raise RuntimeError("channel repository lookup failed")
|
||||
|
||||
token_name = "hlcc-fifth-domain-bootstrap"
|
||||
token = generate_bootstrap_token(binary, token_name)
|
||||
channel_db = STATE_ROOT / "data" / "hlcc.db"
|
||||
try:
|
||||
if status == 404:
|
||||
created_status, _created = api_json(
|
||||
"POST",
|
||||
"/api/v1/user/repos",
|
||||
token,
|
||||
{
|
||||
"name": CHANNEL_REPOSITORY,
|
||||
"description": "光湖代码频道 · 冰朔第五域个人子频道 · 2026-07-23 新起点",
|
||||
"private": False,
|
||||
"auto_init": False,
|
||||
"default_branch": "main",
|
||||
},
|
||||
)
|
||||
if created_status != 201:
|
||||
raise RuntimeError("channel repository creation failed")
|
||||
|
||||
source_commit = immutable_source_commit()
|
||||
with tempfile.TemporaryDirectory(prefix="hlcc-seed-") as temporary:
|
||||
root = pathlib.Path(temporary)
|
||||
snapshot = root / "snapshot"
|
||||
subprocess.run(
|
||||
["git", "clone", "--depth=1", "--branch", "main", LEGACY_REPOSITORY_URL, str(snapshot)],
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
actual_commit = subprocess.run(
|
||||
["git", "-C", str(snapshot), "rev-parse", "HEAD"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout.strip()
|
||||
if actual_commit != source_commit:
|
||||
raise RuntimeError("legacy snapshot commit mismatch")
|
||||
|
||||
shutil.rmtree(snapshot / ".git")
|
||||
subprocess.run(["git", "-C", str(snapshot), "init", "-b", "main"], check=True, capture_output=True)
|
||||
subprocess.run(
|
||||
["git", "-C", str(snapshot), "config", "user.name", "光湖代码频道 · 铸渊"],
|
||||
check=True,
|
||||
)
|
||||
subprocess.run(
|
||||
["git", "-C", str(snapshot), "config", "user.email", "hlcc@guanghulab.invalid"],
|
||||
check=True,
|
||||
)
|
||||
subprocess.run(["git", "-C", str(snapshot), "add", "-A"], check=True)
|
||||
subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"-C",
|
||||
str(snapshot),
|
||||
"commit",
|
||||
"-m",
|
||||
(
|
||||
f"[{SEED_COMMIT_NUMBER}][{SEED_CONTRIBUTION_NUMBER}] "
|
||||
"feat: 以来光者贡献链启用冰朔第五域个人子频道"
|
||||
),
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
credential = root / "credentials"
|
||||
encoded = urllib.parse.quote(token, safe="")
|
||||
credential.write_text(
|
||||
f"http://{OWNER_NAME}:{encoded}@127.0.0.1:3340\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
credential.chmod(0o600)
|
||||
subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"-C",
|
||||
str(snapshot),
|
||||
"-c",
|
||||
f"credential.helper=store --file {credential}",
|
||||
"push",
|
||||
"http://127.0.0.1:3340/bingshuo/fifth-domain.git",
|
||||
"main:main",
|
||||
],
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
credential.unlink(missing_ok=True)
|
||||
|
||||
verified_status, verified = api_json(
|
||||
"GET",
|
||||
f"/api/v1/repos/{OWNER_NAME}/{CHANNEL_REPOSITORY}",
|
||||
)
|
||||
if (
|
||||
verified_status != 200
|
||||
or verified.get("private") is not False
|
||||
or verified.get("empty") is not False
|
||||
or verified.get("default_branch") != "main"
|
||||
):
|
||||
raise RuntimeError("channel repository verification failed")
|
||||
return "seeded"
|
||||
finally:
|
||||
delete_bootstrap_token(channel_db, token_name)
|
||||
|
||||
|
||||
def bootstrap() -> None:
|
||||
process: subprocess.Popen[bytes] | None = None
|
||||
try:
|
||||
for directory in ("config", "data", "logs", "release", "tmp"):
|
||||
(STATE_ROOT / directory).mkdir(parents=True, exist_ok=True)
|
||||
set_status(stage="downloading")
|
||||
files = {name: download_verified(name) for name in EXPECTED}
|
||||
set_status(stage="verifying")
|
||||
verify_release(files)
|
||||
binary = files[BINARY_NAME]
|
||||
binary.chmod(0o755)
|
||||
config_source = pathlib.Path(__file__).with_name("app.ini")
|
||||
config_target = STATE_ROOT / "config" / "app.ini"
|
||||
shutil.copyfile(config_source, config_target)
|
||||
config_target.chmod(0o600)
|
||||
set_status(stage="launching")
|
||||
log_path = STATE_ROOT / "logs" / "hlcc.log"
|
||||
log_handle = log_path.open("ab", buffering=0)
|
||||
process = subprocess.Popen(
|
||||
[
|
||||
str(binary),
|
||||
"web",
|
||||
"--work-path",
|
||||
str(STATE_ROOT / "data"),
|
||||
"--config",
|
||||
str(config_target),
|
||||
],
|
||||
stdout=log_handle,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
wait_for_candidate(process)
|
||||
set_status(stage="migrating-owner")
|
||||
migrate_owner_identity()
|
||||
set_status(stage="seeding-fifth-domain-channel")
|
||||
seed_fifth_domain_channel(binary)
|
||||
set_status(mode="isolated-candidate", ready=True, stage="ready")
|
||||
return_code = process.wait()
|
||||
raise RuntimeError(f"candidate stopped with code {return_code}")
|
||||
except Exception as error:
|
||||
if process and process.poll() is None:
|
||||
process.terminate()
|
||||
set_status(ok=False, ready=False, stage="failed", error=str(error)[:180])
|
||||
|
||||
|
||||
def main() -> None:
|
||||
thread = threading.Thread(target=bootstrap, name="hlcc-bootstrap", daemon=True)
|
||||
thread.start()
|
||||
server = http.server.ThreadingHTTPServer(("127.0.0.1", 3341), HealthHandler)
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
[Unit]
|
||||
Description=Activate the staged full-offline HoloLake Code Channel JD candidate
|
||||
After=hlcc-jd-candidate.service
|
||||
Requires=hlcc-jd-candidate.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=guanghu
|
||||
Group=guanghu
|
||||
ExecStart=/usr/bin/python3 __RELEASE_ROOT__/server-tools/hololake-code-channel/jd-candidate/activate-staged-candidate.py
|
||||
RemainAfterExit=true
|
||||
TimeoutStartSec=60
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
ReadOnlyPaths=__RELEASE_ROOT__
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
[Unit]
|
||||
Description=HoloLake Code Channel isolated JD candidate
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=guanghu
|
||||
Group=guanghu
|
||||
UMask=0077
|
||||
StateDirectory=guanghu/personas/guanghu/hlcc-v16.0.1
|
||||
ExecStart=/usr/bin/python3 __RELEASE_ROOT__/server-tools/hololake-code-channel/jd-candidate/hlcc-bootstrap.py
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
ReadOnlyPaths=__RELEASE_ROOT__
|
||||
ReadWritePaths=/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
[Unit]
|
||||
Description=Activate the HoloLake Code Channel Fifth Domain personal subchannel
|
||||
After=hlcc-jd-candidate.service
|
||||
Requires=hlcc-jd-candidate.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=guanghu
|
||||
Group=guanghu
|
||||
ExecStart=/usr/bin/python3 __RELEASE_ROOT__/server-tools/hololake-code-channel/jd-candidate/activate-staged-candidate.py
|
||||
RemainAfterExit=true
|
||||
TimeoutStartSec=180
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
ReadOnlyPaths=__RELEASE_ROOT__
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const root = __dirname;
|
||||
const bootstrap = fs.readFileSync(path.join(root, "hlcc-bootstrap.py"), "utf8");
|
||||
const ini = fs.readFileSync(path.join(root, "app.ini"), "utf8");
|
||||
const unit = fs.readFileSync(path.join(root, "hlcc-jd-candidate.service"), "utf8");
|
||||
const manifest = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(
|
||||
root,
|
||||
"../../../deployment/requests/HLCC-JD-CANDIDATE-INITIAL-PROVISION-20260723.json",
|
||||
),
|
||||
"utf8",
|
||||
),
|
||||
);
|
||||
const offlineReceipt = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(
|
||||
root,
|
||||
"../../../deployment/receipts/HLCC-BS-SG-003-OFFLINE-PACK-20260723.json",
|
||||
),
|
||||
"utf8",
|
||||
),
|
||||
);
|
||||
|
||||
assert.match(bootstrap, /VERSION = "16\.0\.1"/);
|
||||
assert.match(bootstrap, /EB114F5E6C0DC2BCDD183550A4B61A2DC5923710/);
|
||||
assert.match(bootstrap, /MANIFEST\.sha256/);
|
||||
assert.match(bootstrap, /gpg"[\s\S]*"--verify"/);
|
||||
assert.match(bootstrap, /127\.0\.0\.1", 3341/);
|
||||
assert.match(bootstrap, /127\.0\.0\.1:3340\/api\/healthz/);
|
||||
assert.match(bootstrap, /payload\.get\("status"\) == "pass"/);
|
||||
assert.doesNotMatch(bootstrap, /127\.0\.0\.1:3340\/api\/v1\/version/);
|
||||
assert.match(bootstrap, /forgejo-upstream-all\.bundle/);
|
||||
assert.match(bootstrap, /guanghu-code-channel\.bundle/);
|
||||
assert.match(bootstrap, /package_profile": "full-offline-v16\.0\.1"/);
|
||||
assert.match(bootstrap, /OWNER_NAME = "bingshuo"/);
|
||||
assert.match(bootstrap, /SEED_COMMIT_NUMBER = "HLCC-ICE-000001"/);
|
||||
assert.match(bootstrap, /SEED_CONTRIBUTION_NUMBER = "ZY-CONTRIB-20260723-001"/);
|
||||
assert.match(bootstrap, /以来光者贡献链启用冰朔第五域个人子频道/);
|
||||
assert.match(bootstrap, /owner-identity-source\.db/);
|
||||
assert.match(bootstrap, /password_hash_preserved/);
|
||||
assert.match(bootstrap, /access_tokens_migrated": False/);
|
||||
assert.match(bootstrap, /repositories_migrated": False/);
|
||||
assert.match(bootstrap, /delete from access_token/);
|
||||
assert.doesNotMatch(
|
||||
bootstrap,
|
||||
/print\s*\([^)]*token|stderr\.write\s*\([^)]*token|stdout\.write\s*\([^)]*token/,
|
||||
);
|
||||
for (const artifact of offlineReceipt.artifacts) {
|
||||
assert.match(artifact.sha256, /^[0-9a-f]{64}$/);
|
||||
assert.match(
|
||||
bootstrap,
|
||||
new RegExp(`: "${artifact.sha256}"`),
|
||||
);
|
||||
}
|
||||
assert.match(offlineReceipt.release.manifest_sha256, /^[0-9a-f]{64}$/);
|
||||
assert.match(
|
||||
bootstrap,
|
||||
new RegExp(`"MANIFEST\\.sha256": "${offlineReceipt.release.manifest_sha256}"`),
|
||||
);
|
||||
assert.match(ini, /APP_NAME = 光湖代码频道/);
|
||||
assert.match(ini, /ROOT_URL = https:\/\/guanghulab\.com\/code\//);
|
||||
assert.match(ini, /REQUIRE_SIGNIN_VIEW = false/);
|
||||
assert.match(ini, /SHOW_FOOTER_BRANDING = false/);
|
||||
assert.match(ini, /SHOW_FOOTER_VERSION = false/);
|
||||
assert.match(ini, /\[cron\.update_checker\][\s\S]*ENABLED = false/);
|
||||
assert.match(unit, /^User=guanghu$/m);
|
||||
assert.match(unit, /^ProtectSystem=strict$/m);
|
||||
assert.match(unit, /^ReadOnlyPaths=__RELEASE_ROOT__$/m);
|
||||
assert.match(
|
||||
unit,
|
||||
/^ReadWritePaths=\/var\/lib\/guanghu\/personas\/guanghu\/hlcc-v16\.0\.1$/m,
|
||||
);
|
||||
assert.equal(manifest.target_node, "JD-FD-PRIMARY");
|
||||
assert.equal(manifest.runtime_check.url, "http://127.0.0.1:3341/health");
|
||||
assert.equal(manifest.module.unit, "hlcc-jd-candidate.service");
|
||||
assert.equal(manifest.module.run_user, "guanghu");
|
||||
|
||||
console.log("HoloLake Code Channel JD candidate package: PASS");
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Create a one-user SQLite handoff without exposing the legacy database to HLCC."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import pwd
|
||||
import sqlite3
|
||||
import tempfile
|
||||
|
||||
|
||||
OWNER_NAME = "bingshuo"
|
||||
|
||||
|
||||
def prepare(source: pathlib.Path, destination: pathlib.Path, owner: str) -> None:
|
||||
if not source.is_file():
|
||||
raise RuntimeError("legacy database unavailable")
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
legacy = sqlite3.connect(f"file:{source}?mode=ro", uri=True, timeout=15)
|
||||
try:
|
||||
schema = legacy.execute(
|
||||
"select sql from sqlite_master where type = 'table' and name = 'user'"
|
||||
).fetchone()
|
||||
row = legacy.execute(
|
||||
"select * from user where lower_name = ?",
|
||||
(OWNER_NAME,),
|
||||
).fetchall()
|
||||
if not schema or not schema[0] or len(row) != 1:
|
||||
raise RuntimeError("legacy owner identity mismatch")
|
||||
|
||||
file_descriptor, temporary_name = tempfile.mkstemp(
|
||||
prefix=".owner-identity-source.",
|
||||
suffix=".db",
|
||||
dir=destination.parent,
|
||||
)
|
||||
os.close(file_descriptor)
|
||||
temporary = pathlib.Path(temporary_name)
|
||||
try:
|
||||
handoff = sqlite3.connect(temporary)
|
||||
try:
|
||||
handoff.execute(schema[0])
|
||||
columns = [item[1] for item in legacy.execute("pragma table_info(user)")]
|
||||
placeholders = ",".join("?" for _column in columns)
|
||||
column_sql = ",".join(f'"{column}"' for column in columns)
|
||||
handoff.execute(
|
||||
f"insert into user ({column_sql}) values ({placeholders})",
|
||||
row[0],
|
||||
)
|
||||
handoff.commit()
|
||||
finally:
|
||||
handoff.close()
|
||||
temporary.chmod(0o600)
|
||||
identity = pwd.getpwnam(owner)
|
||||
os.chown(temporary, identity.pw_uid, identity.pw_gid)
|
||||
temporary.replace(destination)
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
finally:
|
||||
legacy.close()
|
||||
|
||||
receipt = destination.with_suffix(".receipt.json")
|
||||
receipt.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema": "guanghu.hlcc-owner-identity-handoff/v1",
|
||||
"owner": OWNER_NAME,
|
||||
"rows": 1,
|
||||
"contains_repository_data": False,
|
||||
"contains_access_tokens": False,
|
||||
"result": "PREPARED",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
identity = pwd.getpwnam(owner)
|
||||
os.chown(receipt, identity.pw_uid, identity.pw_gid)
|
||||
receipt.chmod(0o600)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--source", required=True, type=pathlib.Path)
|
||||
parser.add_argument("--destination", required=True, type=pathlib.Path)
|
||||
parser.add_argument("--owner", default="guanghu")
|
||||
arguments = parser.parse_args()
|
||||
prepare(arguments.source, arguments.destination, arguments.owner)
|
||||
print("OWNER_IDENTITY_SOURCE_PREPARED rows=1 tokens=0 repositories=0")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
MODULE_PATH = pathlib.Path(__file__).with_name("hlcc-bootstrap.py")
|
||||
SPEC = importlib.util.spec_from_file_location("hlcc_bootstrap", MODULE_PATH)
|
||||
assert SPEC and SPEC.loader
|
||||
MODULE = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(MODULE)
|
||||
|
||||
|
||||
SCHEMA = """
|
||||
create table user (
|
||||
id integer primary key autoincrement,
|
||||
lower_name text not null,
|
||||
name text not null,
|
||||
email text not null,
|
||||
passwd text not null,
|
||||
salt text,
|
||||
passwd_hash_algo text,
|
||||
avatar text not null,
|
||||
avatar_email text not null,
|
||||
type integer default 0,
|
||||
is_active integer default 1,
|
||||
is_admin integer default 0,
|
||||
num_repos integer default 0,
|
||||
num_stars integer default 0,
|
||||
num_followers integer default 0,
|
||||
num_following integer default 0,
|
||||
use_custom_avatar integer default 0,
|
||||
prohibit_login integer default 0
|
||||
);
|
||||
create table repository (
|
||||
id integer primary key autoincrement,
|
||||
owner_id integer not null,
|
||||
name text not null
|
||||
);
|
||||
create table access_token (
|
||||
id integer primary key autoincrement,
|
||||
uid integer not null,
|
||||
name text not null,
|
||||
token_hash text not null
|
||||
);
|
||||
"""
|
||||
|
||||
|
||||
class OwnerMigrationTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
root = pathlib.Path(self.temporary.name)
|
||||
self.old = root / "old.db"
|
||||
self.new = root / "new.db"
|
||||
self.receipt = root / "receipt.json"
|
||||
for database in (self.old, self.new):
|
||||
connection = sqlite3.connect(database)
|
||||
connection.executescript(SCHEMA)
|
||||
connection.commit()
|
||||
connection.close()
|
||||
|
||||
connection = sqlite3.connect(self.old)
|
||||
connection.execute(
|
||||
"""
|
||||
insert into user (
|
||||
lower_name, name, email, passwd, salt, passwd_hash_algo,
|
||||
avatar, avatar_email, is_active, is_admin, num_repos,
|
||||
num_stars, num_followers, num_following, use_custom_avatar
|
||||
) values (?, ?, ?, ?, ?, ?, ?, ?, 1, 1, 12, 4, 3, 2, 1)
|
||||
""",
|
||||
(
|
||||
"bingshuo",
|
||||
"bingshuo",
|
||||
"owner@example.invalid",
|
||||
"preserved-password-hash",
|
||||
"preserved-salt",
|
||||
"pbkdf2$50000$50",
|
||||
"legacy-avatar",
|
||||
"avatar@example.invalid",
|
||||
),
|
||||
)
|
||||
connection.execute(
|
||||
"insert into repository (owner_id, name) values (1, 'legacy-repo')"
|
||||
)
|
||||
connection.execute(
|
||||
"insert into access_token (uid, name, token_hash) values (1, 'legacy-token', 'secret-hash')"
|
||||
)
|
||||
connection.commit()
|
||||
connection.close()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.temporary.cleanup()
|
||||
|
||||
def test_migrates_only_owner_identity_and_preserves_password_hash(self) -> None:
|
||||
result = MODULE.migrate_owner_identity(self.old, self.new, self.receipt)
|
||||
self.assertEqual(result, "migrated")
|
||||
connection = sqlite3.connect(self.new)
|
||||
owner = connection.execute(
|
||||
"""
|
||||
select lower_name, passwd, salt, passwd_hash_algo, is_active,
|
||||
is_admin, num_repos, num_stars, num_followers,
|
||||
num_following, use_custom_avatar
|
||||
from user
|
||||
"""
|
||||
).fetchone()
|
||||
self.assertEqual(
|
||||
owner,
|
||||
(
|
||||
"bingshuo",
|
||||
"preserved-password-hash",
|
||||
"preserved-salt",
|
||||
"pbkdf2$50000$50",
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
)
|
||||
self.assertEqual(connection.execute("select count(*) from repository").fetchone()[0], 0)
|
||||
self.assertEqual(connection.execute("select count(*) from access_token").fetchone()[0], 0)
|
||||
connection.close()
|
||||
receipt = json.loads(self.receipt.read_text(encoding="utf-8"))
|
||||
self.assertTrue(receipt["identity_only"])
|
||||
self.assertFalse(receipt["access_tokens_migrated"])
|
||||
self.assertFalse(receipt["repositories_migrated"])
|
||||
|
||||
def test_is_idempotent(self) -> None:
|
||||
self.assertEqual(MODULE.migrate_owner_identity(self.old, self.new, self.receipt), "migrated")
|
||||
self.assertEqual(MODULE.migrate_owner_identity(self.old, self.new, self.receipt), "already-present")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in a new issue