@le-space/rootfs 0.1.52 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,10 +7,14 @@ Shared rootfs contract parsing, reference profile assets, and build helpers.
7
7
  - typed parsing and validation of rootfs contract JSON
8
8
  - shell-env mapping compatible with the existing UC rootfs builder
9
9
  - copied `uc-go-peer` reference contract and guest asset set
10
+ - copied `orbitdb-relay-pinner` reference contract and guest asset set,
11
+ including the delayed first-start, setup endpoint, Caddy, and AutoTLS flow
10
12
 
11
13
  ## Reference assets
12
14
 
13
- The first shared reference profile lives under:
15
+ Current shared reference profiles live under:
14
16
 
15
17
  - `reference/uc-go-peer/contract.json`
16
18
  - `reference/uc-go-peer/rootfs/*`
19
+ - `reference/orbitdb-relay-pinner/contract.json`
20
+ - `reference/orbitdb-relay-pinner/rootfs/*`
package/index.d.ts CHANGED
@@ -51,6 +51,7 @@ declare function referenceProfileRootfsDir(profile: string): string;
51
51
  type RootfsBuildDriver = 'auto' | 'host' | 'docker';
52
52
  interface RootfsBuildOptions {
53
53
  projectDir: string;
54
+ orbitdbRelayPinnerDir?: string;
54
55
  alephDir?: string;
55
56
  outDir?: string;
56
57
  contractPath?: string;
@@ -77,6 +78,7 @@ interface RootfsBuildPlan {
77
78
  contract: RootfsContract;
78
79
  contractPath: string;
79
80
  projectDir: string;
81
+ orbitdbRelayPinnerDir?: string;
80
82
  alephDir: string;
81
83
  outDir: string;
82
84
  driver: RootfsBuildDriver;
@@ -102,7 +104,9 @@ interface RootfsBuildPlan {
102
104
  baseImagePath: string;
103
105
  binaryPath: string;
104
106
  }
105
- declare function deriveRootfsVersion(options?: Pick<RootfsBuildOptions, 'rootfsVersion' | 'gitShortSha' | 'now'>): string;
107
+ declare function deriveRootfsVersion(options?: Pick<RootfsBuildOptions, 'rootfsVersion' | 'gitShortSha' | 'now'> & {
108
+ defaultId?: string;
109
+ }): string;
106
110
  declare function createRootfsBuildPlan(contract: RootfsContract, options: RootfsBuildOptions): RootfsBuildPlan;
107
111
  declare function rootfsBuildShellEnv(plan: RootfsBuildPlan): Record<string, string>;
108
112
 
@@ -166,6 +170,7 @@ interface RootfsExecutionPlanOptions {
166
170
  referenceRootfsDir?: string;
167
171
  projectMountPath?: string;
168
172
  rootfsMountPath?: string;
173
+ orbitdbRelayPinnerMountPath?: string;
169
174
  dockerImageTag?: string;
170
175
  }
171
176
  declare function createHostRootfsExecutionPlan(plan: RootfsBuildPlan, options?: RootfsExecutionPlanOptions): RootfsExecutionPlan;
package/index.js CHANGED
@@ -165,21 +165,44 @@ function deriveRootfsVersion(options = {}) {
165
165
  if (options.rootfsVersion && options.rootfsVersion.trim()) {
166
166
  return options.rootfsVersion.trim();
167
167
  }
168
+ const defaultId = options.defaultId?.trim() || "uc-go-peer";
168
169
  if (options.gitShortSha && options.gitShortSha.trim()) {
169
170
  const now = options.now ?? /* @__PURE__ */ new Date();
170
171
  const yyyy = String(now.getUTCFullYear());
171
172
  const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
172
173
  const dd = String(now.getUTCDate()).padStart(2, "0");
173
- return `uc-go-peer-git-${yyyy}${mm}${dd}-${options.gitShortSha.trim()}`;
174
+ return `${defaultId}-git-${yyyy}${mm}${dd}-${options.gitShortSha.trim()}`;
174
175
  }
175
- return "uc-go-peer-v0.1.0";
176
+ return `${defaultId}-v0.1.0`;
177
+ }
178
+ function defaultRootfsVersionId(contract) {
179
+ return contract.id.trim() || contract.rootfs.profile.trim() || "rootfs";
180
+ }
181
+ function defaultRootfsImageBasename(contract) {
182
+ return `aleph-${contract.rootfs.profile}.qcow2`;
183
+ }
184
+ function sourceSubdirectory(contract) {
185
+ const value = contract.source?.subdirectory?.trim();
186
+ return value && value !== "." ? value : null;
176
187
  }
177
188
  function createRootfsBuildPlan(contract, options) {
178
189
  const projectDir = path.resolve(options.projectDir);
179
- const alephDir = path.resolve(options.alephDir ?? path.join(projectDir, "go-peer/aleph"));
190
+ const orbitdbRelayPinnerDir = options.orbitdbRelayPinnerDir?.trim() ? path.resolve(options.orbitdbRelayPinnerDir) : void 0;
191
+ const subdirectory = sourceSubdirectory(contract);
192
+ const defaultAlephDir = subdirectory ? path.join(projectDir, subdirectory, "aleph") : projectDir;
193
+ const alephDir = path.resolve(options.alephDir ?? defaultAlephDir);
180
194
  const outDir = path.resolve(options.outDir ?? path.join(alephDir, "dist-rootfs"));
181
- const contractPath = path.resolve(options.contractPath ?? path.join(alephDir, "root-profiles", `${contract.id}.json`));
182
- const rootfsVersion = deriveRootfsVersion(options);
195
+ const defaultContractDir = subdirectory ? path.join(alephDir, "root-profiles") : path.join(projectDir, "root-profiles");
196
+ const contractPath = path.resolve(options.contractPath ?? path.join(defaultContractDir, `${contract.id}.json`));
197
+ const versionId = defaultRootfsVersionId(contract);
198
+ const rootfsVersion = deriveRootfsVersion({
199
+ ...options,
200
+ defaultId: versionId,
201
+ rootfsVersion: options.rootfsVersion?.trim() || void 0,
202
+ gitShortSha: options.gitShortSha,
203
+ now: options.now
204
+ });
205
+ const imageBasename = defaultRootfsImageBasename(contract);
183
206
  const copyTarget = contract.manifest.copyTarget?.trim() ?? "";
184
207
  const latestManifestPath = copyTarget ? path.resolve(copyTarget.startsWith("/") ? copyTarget : path.join(projectDir, copyTarget)) : null;
185
208
  const versionedManifestPath = latestManifestPath ? path.join(path.dirname(latestManifestPath), `${rootfsVersion}.json`) : null;
@@ -187,6 +210,7 @@ function createRootfsBuildPlan(contract, options) {
187
210
  contract,
188
211
  contractPath,
189
212
  projectDir,
213
+ orbitdbRelayPinnerDir,
190
214
  alephDir,
191
215
  outDir,
192
216
  driver: options.driver ?? "auto",
@@ -208,13 +232,13 @@ function createRootfsBuildPlan(contract, options) {
208
232
  manifestPath: path.join(outDir, "rootfs-manifest.json"),
209
233
  latestManifestPath,
210
234
  versionedManifestPath,
211
- imagePath: path.join(outDir, "aleph-uc-go-peer.qcow2"),
235
+ imagePath: path.join(outDir, imageBasename),
212
236
  baseImagePath: path.join(outDir, "debian-12-genericcloud-amd64.qcow2"),
213
- binaryPath: path.join(outDir, "universal-chat-go")
237
+ binaryPath: path.join(outDir, path.basename(contract.rootfs.binaryPath || "artifact"))
214
238
  };
215
239
  }
216
240
  function rootfsBuildShellEnv(plan) {
217
- return {
241
+ const env = {
218
242
  PROJECT_DIR: plan.projectDir,
219
243
  OUT_DIR: plan.outDir,
220
244
  ROOTFS_CONTRACT_FILE: plan.contractPath,
@@ -235,6 +259,10 @@ function rootfsBuildShellEnv(plan) {
235
259
  IPFS_GATEWAY_WAIT_ATTEMPTS: String(plan.ipfsGatewayWaitAttempts),
236
260
  IPFS_GATEWAY_WAIT_DELAY_SECONDS: String(plan.ipfsGatewayWaitDelaySeconds)
237
261
  };
262
+ if (plan.orbitdbRelayPinnerDir) {
263
+ env.ORBITDB_RELAY_PINNER_DIR = plan.orbitdbRelayPinnerDir;
264
+ }
265
+ return env;
238
266
  }
239
267
 
240
268
  // src/manifest.ts
@@ -360,6 +388,14 @@ function containerPathForProjectFile(hostPath, projectDir, mountPath, label) {
360
388
  const relative = ensurePathWithin(projectDir, hostPath, label);
361
389
  return path2.posix.join(mountPath, relative.split(path2.sep).join("/"));
362
390
  }
391
+ function containerContractPathForFile(hostPath, projectDir, projectMountPath) {
392
+ const relative = path2.relative(projectDir, hostPath);
393
+ if (!relative.startsWith("..") && !path2.isAbsolute(relative)) {
394
+ return path2.posix.join(projectMountPath, relative.split(path2.sep).join("/"));
395
+ }
396
+ const basename = path2.posix.basename(hostPath);
397
+ return path2.posix.join("/workspace/shared-contracts", basename);
398
+ }
363
399
  function resolveReferenceRootfsDir(contract, override) {
364
400
  if (override) {
365
401
  return path2.resolve(override);
@@ -376,12 +412,7 @@ function createHostRootfsExecutionPlan(plan, options = {}) {
376
412
  command: "/bin/bash",
377
413
  args: [path2.join(referenceRootfsDir, "build-rootfs-image.sh")],
378
414
  workdir: referenceRootfsDir,
379
- env: {
380
- PROJECT_DIR: plan.projectDir,
381
- OUT_DIR: plan.outDir,
382
- ROOTFS_CONTRACT_FILE: plan.contractPath,
383
- ROOTFS_IMAGE_SIZE: plan.rootfsImageSize
384
- }
415
+ env: rootfsBuildShellEnv(plan)
385
416
  }
386
417
  };
387
418
  }
@@ -389,12 +420,35 @@ function createDockerRootfsExecutionPlan(plan, options = {}) {
389
420
  const referenceRootfsDir = resolveReferenceRootfsDir(plan.contract, options.referenceRootfsDir);
390
421
  const projectMountPath = options.projectMountPath ?? "/workspace/project";
391
422
  const rootfsMountPath = options.rootfsMountPath ?? "/workspace/shared-rootfs";
423
+ const orbitdbRelayPinnerMountPath = options.orbitdbRelayPinnerMountPath ?? "/workspace-orbitdb-relay-pinner";
392
424
  const dockerImageTag = options.dockerImageTag ?? `${plan.contract.id}-rootfs-builder:local`;
393
425
  const containerProjectDir = projectMountPath;
394
- const containerContractPath = containerPathForProjectFile(plan.contractPath, plan.projectDir, projectMountPath, "contractPath");
426
+ const containerContractPath = containerContractPathForFile(plan.contractPath, plan.projectDir, projectMountPath);
395
427
  const containerOutDir = containerPathForProjectFile(plan.outDir, plan.projectDir, projectMountPath, "outDir");
396
428
  const hostUid = typeof process.getuid === "function" ? String(process.getuid()) : "1000";
397
429
  const hostGid = typeof process.getgid === "function" ? String(process.getgid()) : "1000";
430
+ const runEnv = {
431
+ ...rootfsBuildShellEnv(plan),
432
+ PROJECT_DIR: containerProjectDir,
433
+ OUT_DIR: containerOutDir,
434
+ ROOTFS_CONTRACT_FILE: containerContractPath,
435
+ HOST_UID: hostUid,
436
+ HOST_GID: hostGid
437
+ };
438
+ const volumeMounts = [
439
+ "-v",
440
+ `${plan.projectDir}:${projectMountPath}`,
441
+ "-v",
442
+ `${referenceRootfsDir}:${rootfsMountPath}`
443
+ ];
444
+ const contractRelative = path2.relative(plan.projectDir, plan.contractPath);
445
+ if (contractRelative.startsWith("..") || path2.isAbsolute(contractRelative)) {
446
+ volumeMounts.push("-v", `${plan.contractPath}:${containerContractPath}:ro`);
447
+ }
448
+ if (plan.orbitdbRelayPinnerDir) {
449
+ runEnv.ORBITDB_RELAY_PINNER_DIR = orbitdbRelayPinnerMountPath;
450
+ volumeMounts.push("-v", `${plan.orbitdbRelayPinnerDir}:${orbitdbRelayPinnerMountPath}:ro`);
451
+ }
398
452
  return {
399
453
  mode: "docker",
400
454
  reason: "Using Dockerized Debian/libguestfs builder.",
@@ -422,22 +476,8 @@ function createDockerRootfsExecutionPlan(plan, options = {}) {
422
476
  "linux/amd64",
423
477
  "-e",
424
478
  "LIBGUESTFS_BACKEND=direct",
425
- "-e",
426
- `ROOTFS_CONTRACT_FILE=${containerContractPath}`,
427
- "-e",
428
- `OUT_DIR=${containerOutDir}`,
429
- "-e",
430
- `ROOTFS_IMAGE_SIZE=${plan.rootfsImageSize}`,
431
- "-e",
432
- `PROJECT_DIR=${containerProjectDir}`,
433
- "-e",
434
- `HOST_UID=${hostUid}`,
435
- "-e",
436
- `HOST_GID=${hostGid}`,
437
- "-v",
438
- `${plan.projectDir}:${projectMountPath}`,
439
- "-v",
440
- `${referenceRootfsDir}:${rootfsMountPath}`,
479
+ ...Object.entries(runEnv).flatMap(([key, value]) => ["-e", `${key}=${value}`]),
480
+ ...volumeMounts,
441
481
  "-w",
442
482
  rootfsMountPath,
443
483
  dockerImageTag,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/rootfs",
3
- "version": "0.1.52",
3
+ "version": "0.2.0",
4
4
  "description": "Shared rootfs contract parsing, reference profile assets, and build helpers.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,70 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "orbitdb-relay-pinner",
4
+ "displayName": "OrbitDB Relay Pinner",
5
+ "source": {
6
+ "repository": "external",
7
+ "subdirectory": "."
8
+ },
9
+ "rootfs": {
10
+ "profile": "orbitdb-relay-pinner",
11
+ "installMode": "prebaked",
12
+ "installDir": "/opt/orbitdb-relay-pinner",
13
+ "binaryPath": "/usr/local/bin/orbitdb-relay-pinner",
14
+ "dataDir": "/var/lib/orbitdb-relay-pinner",
15
+ "envFile": "/etc/default/orbitdb-relay-pinner"
16
+ },
17
+ "services": {
18
+ "bootstrap": "orbitdb-relay-pinner-bootstrap.service",
19
+ "main": "orbitdb-relay-pinner.service",
20
+ "autotlsRefresh": "orbitdb-relay-pinner-autotls-refresh.service"
21
+ },
22
+ "ports": [
23
+ {
24
+ "port": 22,
25
+ "tcp": true,
26
+ "udp": false,
27
+ "purpose": "SSH"
28
+ },
29
+ {
30
+ "port": 80,
31
+ "tcp": true,
32
+ "udp": false,
33
+ "purpose": "Temporary setup endpoint"
34
+ },
35
+ {
36
+ "port": 9090,
37
+ "tcp": true,
38
+ "udp": false,
39
+ "purpose": "Metrics and health API"
40
+ },
41
+ {
42
+ "port": 9091,
43
+ "tcp": true,
44
+ "udp": false,
45
+ "purpose": "libp2p TCP"
46
+ },
47
+ {
48
+ "port": 443,
49
+ "tcp": true,
50
+ "udp": false,
51
+ "purpose": "Caddy HTTPS and WSS proxy"
52
+ },
53
+ {
54
+ "port": 9093,
55
+ "tcp": false,
56
+ "udp": true,
57
+ "purpose": "WebRTC direct"
58
+ },
59
+ {
60
+ "port": 9094,
61
+ "tcp": false,
62
+ "udp": true,
63
+ "purpose": "QUIC"
64
+ }
65
+ ],
66
+ "manifest": {
67
+ "copyTarget": "public/rootfs-manifest.json",
68
+ "notes": "The OrbitDB relay image prebakes Node.js, production dependencies, and Caddy into the qcow2, delays first relay start until Aleph host port mappings are known, exposes a temporary setup endpoint on port 80, and uses Caddy on port 443 to front both HTTPS helper routes and secure libp2p WSS transport for the configured proxy hostname."
69
+ }
70
+ }
@@ -0,0 +1,19 @@
1
+ FROM --platform=linux/amd64 debian:12
2
+
3
+ ENV DEBIAN_FRONTEND=noninteractive
4
+ ENV LIBGUESTFS_BACKEND=direct
5
+
6
+ RUN apt-get update \
7
+ && apt-get install -y --no-install-recommends \
8
+ ca-certificates \
9
+ curl \
10
+ jq \
11
+ libguestfs-tools \
12
+ linux-image-amd64 \
13
+ python3 \
14
+ qemu-system-x86 \
15
+ qemu-utils \
16
+ tar \
17
+ && rm -rf /var/lib/apt/lists/*
18
+
19
+ WORKDIR /workspace/relay-deployer-pwa
@@ -0,0 +1,335 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ APP_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
6
+ REPO_DIR="$(cd "${APP_DIR}/.." && pwd)"
7
+ ROOTFS_CONTRACT_FILE="${ROOTFS_CONTRACT_FILE:-}"
8
+ ROOTFS_PROFILE="${ROOTFS_PROFILE:-}"
9
+ ROOTFS_INSTALL_MODE="${ROOTFS_INSTALL_MODE:-}"
10
+ PY_LIBP2P_DIR="${PY_LIBP2P_DIR:-${REPO_DIR}/py-libp2p}"
11
+ ORBITDB_RELAY_PINNER_DIR="${ORBITDB_RELAY_PINNER_DIR:-}"
12
+ UNIVERSAL_CONNECTIVITY_DIR="${UNIVERSAL_CONNECTIVITY_DIR:-}"
13
+ OUT_DIR="${OUT_DIR:-${APP_DIR}/dist-rootfs}"
14
+ BASE_URL="${BASE_URL:-https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2}"
15
+ BASE_IMAGE="${OUT_DIR}/debian-12-genericcloud-amd64.qcow2"
16
+ IMAGE_SIZE="${IMAGE_SIZE:-${ROOTFS_IMAGE_SIZE:-20G}}"
17
+ ROOTFS_SPARSIFY="${ROOTFS_SPARSIFY:-1}"
18
+
19
+ require() {
20
+ command -v "$1" >/dev/null 2>&1 || {
21
+ echo "Missing required command: $1" >&2
22
+ exit 1
23
+ }
24
+ }
25
+
26
+ require curl
27
+ require qemu-img
28
+ require virt-customize
29
+ require tar
30
+
31
+ load_rootfs_contract() {
32
+ [ -n "${ROOTFS_CONTRACT_FILE}" ] || return 0
33
+ require python3
34
+ [ -f "${ROOTFS_CONTRACT_FILE}" ] || {
35
+ echo "Rootfs contract does not exist: ${ROOTFS_CONTRACT_FILE}" >&2
36
+ exit 1
37
+ }
38
+
39
+ eval "$(python3 "${SCRIPT_DIR}/read-rootfs-contract.py" "${ROOTFS_CONTRACT_FILE}")"
40
+
41
+ if [ -n "${ROOTFS_PROFILE}" ] && [ "${ROOTFS_PROFILE}" != "${ROOTFS_CONTRACT_PROFILE}" ]; then
42
+ echo "ROOTFS_PROFILE=${ROOTFS_PROFILE} conflicts with contract profile ${ROOTFS_CONTRACT_PROFILE}" >&2
43
+ exit 1
44
+ fi
45
+ if [ -n "${ROOTFS_INSTALL_MODE}" ] && [ "${ROOTFS_INSTALL_MODE}" != "${ROOTFS_CONTRACT_INSTALL_MODE}" ]; then
46
+ echo "ROOTFS_INSTALL_MODE=${ROOTFS_INSTALL_MODE} conflicts with contract install mode ${ROOTFS_CONTRACT_INSTALL_MODE}" >&2
47
+ exit 1
48
+ fi
49
+
50
+ ROOTFS_PROFILE="${ROOTFS_CONTRACT_PROFILE}"
51
+ ROOTFS_INSTALL_MODE="${ROOTFS_INSTALL_MODE:-${ROOTFS_CONTRACT_INSTALL_MODE}}"
52
+ echo "Loaded rootfs contract: ${ROOTFS_CONTRACT_PATH}"
53
+ }
54
+
55
+ load_rootfs_contract
56
+ ROOTFS_PROFILE="${ROOTFS_PROFILE:-py-libp2p}"
57
+ ROOTFS_CONTRACT_INSTALL_DIR="${ROOTFS_CONTRACT_INSTALL_DIR:-/opt/go-peer}"
58
+ ROOTFS_CONTRACT_DATA_DIR="${ROOTFS_CONTRACT_DATA_DIR:-/var/lib/uc-go-peer}"
59
+ ROOTFS_CONTRACT_ENV_FILE="${ROOTFS_CONTRACT_ENV_FILE:-/etc/default/uc-go-peer}"
60
+ ROOTFS_CONTRACT_BOOTSTRAP_SERVICE="${ROOTFS_CONTRACT_BOOTSTRAP_SERVICE:-uc-go-peer-bootstrap.service}"
61
+ ROOTFS_CONTRACT_MAIN_SERVICE="${ROOTFS_CONTRACT_MAIN_SERVICE:-uc-go-peer.service}"
62
+ ROOTFS_CONTRACT_AUTOTLS_SERVICE="${ROOTFS_CONTRACT_AUTOTLS_SERVICE:-uc-go-peer-autotls-refresh.service}"
63
+ ROOTFS_CONTRACT_BINARY_PATH="${ROOTFS_CONTRACT_BINARY_PATH:-/usr/local/bin/universal-chat-go}"
64
+ ROOTFS_CONTRACT_BINARY_DIR="$(dirname "${ROOTFS_CONTRACT_BINARY_PATH}")"
65
+
66
+ case "${ROOTFS_PROFILE}" in
67
+ py-libp2p)
68
+ IMAGE="${OUT_DIR}/aleph-py-libp2p-relay.qcow2"
69
+ APP_TAR="${OUT_DIR}/py-libp2p.tar"
70
+ if [ -z "${ROOTFS_INSTALL_MODE}" ]; then
71
+ ROOTFS_INSTALL_MODE="thin"
72
+ fi
73
+ if [ ! -d "${PY_LIBP2P_DIR}" ]; then
74
+ echo "Missing py-libp2p directory: ${PY_LIBP2P_DIR}" >&2
75
+ exit 1
76
+ fi
77
+ ;;
78
+ orbitdb-relay-pinner)
79
+ IMAGE="${OUT_DIR}/aleph-orbitdb-relay-pinner.qcow2"
80
+ APP_TAR="${OUT_DIR}/orbitdb-relay-pinner.tar"
81
+ if [ -z "${ROOTFS_INSTALL_MODE}" ]; then
82
+ ROOTFS_INSTALL_MODE="prebaked"
83
+ fi
84
+ if [ -z "${ORBITDB_RELAY_PINNER_DIR}" ]; then
85
+ echo "ROOTFS_PROFILE=orbitdb-relay-pinner requires ORBITDB_RELAY_PINNER_DIR=/path/to/orbitdb-relay-pinner" >&2
86
+ exit 1
87
+ fi
88
+ if [ ! -d "${ORBITDB_RELAY_PINNER_DIR}" ]; then
89
+ echo "Missing orbitdb-relay-pinner directory: ${ORBITDB_RELAY_PINNER_DIR}" >&2
90
+ exit 1
91
+ fi
92
+ if [ ! -d "${ORBITDB_RELAY_PINNER_DIR}/dist" ]; then
93
+ echo "Missing orbitdb-relay-pinner dist directory: ${ORBITDB_RELAY_PINNER_DIR}/dist" >&2
94
+ echo "Build orbitdb-relay-pinner before creating this rootfs image." >&2
95
+ exit 1
96
+ fi
97
+ if [ "${ROOTFS_INSTALL_MODE}" = "thin" ]; then
98
+ echo "ROOTFS_PROFILE=orbitdb-relay-pinner now requires ROOTFS_INSTALL_MODE=prebaked." >&2
99
+ echo "The runtime is installed into the image, then configured with mapped ports before first start." >&2
100
+ exit 1
101
+ fi
102
+ ;;
103
+ uc-go-peer)
104
+ IMAGE="${OUT_DIR}/aleph-uc-go-peer.qcow2"
105
+ APP_TAR="${OUT_DIR}/uc-go-peer.tar"
106
+ if [ -z "${ROOTFS_INSTALL_MODE}" ]; then
107
+ ROOTFS_INSTALL_MODE="prebaked"
108
+ fi
109
+ if [ -z "${UNIVERSAL_CONNECTIVITY_DIR}" ]; then
110
+ echo "ROOTFS_PROFILE=uc-go-peer requires UNIVERSAL_CONNECTIVITY_DIR=/path/to/universal-connectivity" >&2
111
+ exit 1
112
+ fi
113
+ if [ ! -d "${UNIVERSAL_CONNECTIVITY_DIR}/go-peer" ]; then
114
+ echo "Missing go-peer directory: ${UNIVERSAL_CONNECTIVITY_DIR}/go-peer" >&2
115
+ exit 1
116
+ fi
117
+ if [ "${ROOTFS_INSTALL_MODE}" = "thin" ]; then
118
+ echo "ROOTFS_PROFILE=uc-go-peer requires ROOTFS_INSTALL_MODE=prebaked." >&2
119
+ echo "The Go binary is built into the image, then configured with mapped ports before first start." >&2
120
+ exit 1
121
+ fi
122
+ ;;
123
+ uc-rust-peer)
124
+ IMAGE="${OUT_DIR}/aleph-uc-rust-peer.qcow2"
125
+ APP_TAR="${OUT_DIR}/uc-rust-peer.tar"
126
+ if [ -z "${ROOTFS_INSTALL_MODE}" ]; then
127
+ ROOTFS_INSTALL_MODE="prebaked"
128
+ fi
129
+ if [ -z "${UNIVERSAL_CONNECTIVITY_DIR}" ]; then
130
+ echo "ROOTFS_PROFILE=uc-rust-peer requires UNIVERSAL_CONNECTIVITY_DIR=/path/to/universal-connectivity" >&2
131
+ exit 1
132
+ fi
133
+ if [ ! -d "${UNIVERSAL_CONNECTIVITY_DIR}/rust-peer" ]; then
134
+ echo "Missing rust-peer directory: ${UNIVERSAL_CONNECTIVITY_DIR}/rust-peer" >&2
135
+ exit 1
136
+ fi
137
+ if [ "${ROOTFS_INSTALL_MODE}" = "thin" ]; then
138
+ echo "ROOTFS_PROFILE=uc-rust-peer requires ROOTFS_INSTALL_MODE=prebaked." >&2
139
+ echo "The Rust binary and websocket bridge are built into the image, then configured with mapped ports before first start." >&2
140
+ exit 1
141
+ fi
142
+ ;;
143
+ *)
144
+ echo "Unsupported ROOTFS_PROFILE: ${ROOTFS_PROFILE}" >&2
145
+ echo "Expected one of: py-libp2p, orbitdb-relay-pinner, uc-go-peer, uc-rust-peer" >&2
146
+ exit 1
147
+ ;;
148
+ esac
149
+
150
+ case "${ROOTFS_INSTALL_MODE}" in
151
+ thin|prebaked)
152
+ ;;
153
+ *)
154
+ echo "Unsupported ROOTFS_INSTALL_MODE: ${ROOTFS_INSTALL_MODE}" >&2
155
+ echo "Expected one of: thin, prebaked" >&2
156
+ exit 1
157
+ ;;
158
+ esac
159
+
160
+ mkdir -p "${OUT_DIR}"
161
+
162
+ echo "Building ${ROOTFS_PROFILE} image in ${ROOTFS_INSTALL_MODE} mode"
163
+
164
+ if [ ! -f "${BASE_IMAGE}" ]; then
165
+ curl -L "${BASE_URL}" -o "${BASE_IMAGE}"
166
+ fi
167
+
168
+ cp "${BASE_IMAGE}" "${IMAGE}"
169
+ qemu-img resize "${IMAGE}" "${IMAGE_SIZE}"
170
+
171
+ case "${ROOTFS_PROFILE}" in
172
+ py-libp2p)
173
+ tar \
174
+ --exclude ".git" \
175
+ --exclude ".venv" \
176
+ --exclude "__pycache__" \
177
+ -C "$(dirname "${PY_LIBP2P_DIR}")" \
178
+ -cf "${APP_TAR}" \
179
+ "$(basename "${PY_LIBP2P_DIR}")"
180
+
181
+ py_customize_args=(
182
+ -a "${IMAGE}"
183
+ --mkdir /opt/py-libp2p
184
+ --mkdir /var/lib/py-libp2p-relay
185
+ --copy-in "${APP_TAR}:/opt"
186
+ --copy-in "${SCRIPT_DIR}/relay-bootstrap.sh:/usr/local/sbin"
187
+ --copy-in "${SCRIPT_DIR}/py-libp2p-relay.service:/etc/systemd/system"
188
+ --copy-in "${SCRIPT_DIR}/py-libp2p-relay-bootstrap.service:/etc/systemd/system"
189
+ --run-command "tar -xf /opt/$(basename "${APP_TAR}") -C /opt/py-libp2p --strip-components=1"
190
+ --run-command "chmod 0755 /usr/local/sbin/relay-bootstrap.sh"
191
+ )
192
+
193
+ if [ "${ROOTFS_INSTALL_MODE}" = "prebaked" ]; then
194
+ py_customize_args+=(
195
+ --run-command "BOOTSTRAP_STAMP=/var/lib/py-libp2p-relay/bootstrap-complete RELAY_PORT=4001 /usr/local/sbin/relay-bootstrap.sh"
196
+ )
197
+ fi
198
+
199
+ py_customize_args+=(
200
+ --run-command "systemctl enable py-libp2p-relay.service"
201
+ --run-command "rm -f /opt/$(basename "${APP_TAR}")"
202
+ )
203
+
204
+ virt-customize "${py_customize_args[@]}"
205
+ ;;
206
+ orbitdb-relay-pinner)
207
+ tar \
208
+ -C "${ORBITDB_RELAY_PINNER_DIR}" \
209
+ -cf "${APP_TAR}" \
210
+ dist \
211
+ deploy \
212
+ package.json \
213
+ pnpm-lock.yaml \
214
+ pnpm-workspace.yaml \
215
+ README.md \
216
+ LICENSE \
217
+ .env.example
218
+
219
+ orbitdb_customize_args=(
220
+ -a "${IMAGE}"
221
+ --mkdir /opt/orbitdb-relay-pinner
222
+ --mkdir /var/lib/orbitdb-relay-pinner
223
+ --mkdir /etc/systemd/system/orbitdb-relay-pinner.service.d
224
+ --copy-in "${APP_TAR}:/opt"
225
+ --copy-in "${SCRIPT_DIR}/orbitdb-relay-pinner-bootstrap.sh:/usr/local/sbin"
226
+ --copy-in "${SCRIPT_DIR}/orbitdb-relay-pinner-configure.sh:/usr/local/sbin"
227
+ --copy-in "${SCRIPT_DIR}/orbitdb-relay-pinner-autotls-refresh.py:/usr/local/sbin"
228
+ --copy-in "${SCRIPT_DIR}/orbitdb-relay-pinner-setup-server.py:/usr/local/sbin"
229
+ --copy-in "${SCRIPT_DIR}/orbitdb-relay-pinner-describe.py:/usr/local/sbin"
230
+ --copy-in "${SCRIPT_DIR}/orbitdb-relay-pinner-bootstrap.service:/etc/systemd/system"
231
+ --copy-in "${SCRIPT_DIR}/orbitdb-relay-pinner-autotls-refresh.service:/etc/systemd/system"
232
+ --copy-in "${SCRIPT_DIR}/orbitdb-relay-pinner-bootstrap.conf:/etc/systemd/system/orbitdb-relay-pinner.service.d"
233
+ --run-command "tar -xf /opt/$(basename "${APP_TAR}") -C /opt/orbitdb-relay-pinner"
234
+ --run-command "chmod 0755 /usr/local/sbin/orbitdb-relay-pinner-bootstrap.sh"
235
+ --run-command "chmod 0755 /usr/local/sbin/orbitdb-relay-pinner-configure.sh"
236
+ --run-command "chmod 0755 /usr/local/sbin/orbitdb-relay-pinner-autotls-refresh.py"
237
+ --run-command "chmod 0755 /usr/local/sbin/orbitdb-relay-pinner-setup-server.py"
238
+ --run-command "chmod 0755 /usr/local/sbin/orbitdb-relay-pinner-describe.py"
239
+ --run-command "cp /opt/orbitdb-relay-pinner/deploy/orbitdb-relay-pinner.service /etc/systemd/system/orbitdb-relay-pinner.service"
240
+ --run-command "INSTALL_DIR=/opt/orbitdb-relay-pinner DATA_DIR=/var/lib/orbitdb-relay-pinner ENV_FILE=/etc/default/orbitdb-relay-pinner SERVICE_USER=orbitdb-relay /usr/local/sbin/orbitdb-relay-pinner-bootstrap.sh"
241
+ )
242
+
243
+ orbitdb_customize_args+=(
244
+ --run-command "systemctl enable orbitdb-relay-pinner-bootstrap.service"
245
+ --run-command "systemctl enable orbitdb-relay-pinner.service"
246
+ --run-command "rm -f /opt/$(basename "${APP_TAR}")"
247
+ )
248
+
249
+ virt-customize "${orbitdb_customize_args[@]}"
250
+ ;;
251
+ uc-go-peer)
252
+ tar \
253
+ --exclude ".git" \
254
+ -C "${UNIVERSAL_CONNECTIVITY_DIR}" \
255
+ -cf "${APP_TAR}" \
256
+ go-peer
257
+
258
+ uc_go_customize_args=(
259
+ -a "${IMAGE}"
260
+ --mkdir "${ROOTFS_CONTRACT_INSTALL_DIR}"
261
+ --mkdir "${ROOTFS_CONTRACT_DATA_DIR}"
262
+ --mkdir "${ROOTFS_CONTRACT_BINARY_DIR}"
263
+ --copy-in "${APP_TAR}:/opt"
264
+ --copy-in "${SCRIPT_DIR}/uc-go-peer-bootstrap.sh:/usr/local/sbin"
265
+ --copy-in "${SCRIPT_DIR}/uc-go-peer-configure.sh:/usr/local/sbin"
266
+ --copy-in "${SCRIPT_DIR}/uc-go-peer-autotls-refresh.py:/usr/local/sbin"
267
+ --copy-in "${SCRIPT_DIR}/uc-go-peer-setup-server.py:/usr/local/sbin"
268
+ --copy-in "${SCRIPT_DIR}/uc-go-peer-describe.py:/usr/local/sbin"
269
+ --copy-in "${SCRIPT_DIR}/uc-go-peer-bootstrap.service:/etc/systemd/system"
270
+ --copy-in "${SCRIPT_DIR}/uc-go-peer-autotls-refresh.service:/etc/systemd/system"
271
+ --copy-in "${SCRIPT_DIR}/uc-go-peer.service:/etc/systemd/system"
272
+ --run-command "tar -xf /opt/$(basename "${APP_TAR}") -C /opt"
273
+ --run-command "chmod 0755 /usr/local/sbin/uc-go-peer-bootstrap.sh"
274
+ --run-command "chmod 0755 /usr/local/sbin/uc-go-peer-configure.sh"
275
+ --run-command "chmod 0755 /usr/local/sbin/uc-go-peer-autotls-refresh.py"
276
+ --run-command "chmod 0755 /usr/local/sbin/uc-go-peer-setup-server.py"
277
+ --run-command "chmod 0755 /usr/local/sbin/uc-go-peer-describe.py"
278
+ --run-command "INSTALL_DIR=${ROOTFS_CONTRACT_INSTALL_DIR} APP_BINARY=${ROOTFS_CONTRACT_BINARY_PATH} DATA_DIR=${ROOTFS_CONTRACT_DATA_DIR} ENV_FILE=${ROOTFS_CONTRACT_ENV_FILE} SERVICE_USER=uc-go-peer /usr/local/sbin/uc-go-peer-bootstrap.sh base"
279
+ --run-command "INSTALL_DIR=${ROOTFS_CONTRACT_INSTALL_DIR} APP_BINARY=${ROOTFS_CONTRACT_BINARY_PATH} DATA_DIR=${ROOTFS_CONTRACT_DATA_DIR} ENV_FILE=${ROOTFS_CONTRACT_ENV_FILE} SERVICE_USER=uc-go-peer /usr/local/sbin/uc-go-peer-bootstrap.sh build"
280
+ --run-command "INSTALL_DIR=${ROOTFS_CONTRACT_INSTALL_DIR} APP_BINARY=${ROOTFS_CONTRACT_BINARY_PATH} DATA_DIR=${ROOTFS_CONTRACT_DATA_DIR} ENV_FILE=${ROOTFS_CONTRACT_ENV_FILE} SERVICE_USER=uc-go-peer /usr/local/sbin/uc-go-peer-bootstrap.sh finalize"
281
+ --run-command "systemctl enable ${ROOTFS_CONTRACT_BOOTSTRAP_SERVICE}"
282
+ --run-command "systemctl enable ${ROOTFS_CONTRACT_AUTOTLS_SERVICE}"
283
+ --run-command "systemctl enable ${ROOTFS_CONTRACT_MAIN_SERVICE}"
284
+ --run-command "rm -f /opt/$(basename "${APP_TAR}")"
285
+ )
286
+
287
+ virt-customize "${uc_go_customize_args[@]}"
288
+ ;;
289
+ uc-rust-peer)
290
+ tar \
291
+ --exclude "target" \
292
+ --exclude ".git" \
293
+ -C "${UNIVERSAL_CONNECTIVITY_DIR}" \
294
+ -cf "${APP_TAR}" \
295
+ rust-peer
296
+
297
+ uc_rust_customize_args=(
298
+ -a "${IMAGE}"
299
+ --mkdir /opt/rust-peer
300
+ --mkdir /var/lib/uc-rust-peer
301
+ --copy-in "${APP_TAR}:/opt"
302
+ --copy-in "${SCRIPT_DIR}/uc-rust-peer-bootstrap.sh:/usr/local/sbin"
303
+ --copy-in "${SCRIPT_DIR}/uc-rust-peer-configure.sh:/usr/local/sbin"
304
+ --copy-in "${SCRIPT_DIR}/uc-rust-peer-setup-server.py:/usr/local/sbin"
305
+ --copy-in "${SCRIPT_DIR}/uc-rust-peer-ws-bridge.py:/usr/local/sbin"
306
+ --copy-in "${SCRIPT_DIR}/uc-rust-peer-run.sh:/usr/local/sbin"
307
+ --copy-in "${SCRIPT_DIR}/uc-rust-peer-bootstrap.service:/etc/systemd/system"
308
+ --copy-in "${SCRIPT_DIR}/uc-rust-peer.service:/etc/systemd/system"
309
+ --copy-in "${SCRIPT_DIR}/uc-rust-peer-ws-bridge.service:/etc/systemd/system"
310
+ --run-command "tar -xf /opt/$(basename "${APP_TAR}") -C /opt"
311
+ --run-command "chmod 0755 /usr/local/sbin/uc-rust-peer-bootstrap.sh"
312
+ --run-command "chmod 0755 /usr/local/sbin/uc-rust-peer-configure.sh"
313
+ --run-command "chmod 0755 /usr/local/sbin/uc-rust-peer-setup-server.py"
314
+ --run-command "chmod 0755 /usr/local/sbin/uc-rust-peer-ws-bridge.py"
315
+ --run-command "chmod 0755 /usr/local/sbin/uc-rust-peer-run.sh"
316
+ --run-command "INSTALL_DIR=/opt/rust-peer DATA_DIR=/var/lib/uc-rust-peer ENV_FILE=/etc/default/uc-rust-peer SERVICE_USER=uc-rust-peer /usr/local/sbin/uc-rust-peer-bootstrap.sh"
317
+ --run-command "systemctl enable uc-rust-peer-bootstrap.service"
318
+ --run-command "systemctl enable uc-rust-peer.service"
319
+ --run-command "systemctl enable uc-rust-peer-ws-bridge.service"
320
+ --run-command "rm -f /opt/$(basename "${APP_TAR}")"
321
+ )
322
+
323
+ virt-customize "${uc_rust_customize_args[@]}"
324
+ ;;
325
+ esac
326
+
327
+ if [ "${ROOTFS_SPARSIFY}" = "1" ] && command -v virt-sparsify >/dev/null 2>&1; then
328
+ SPARSE_IMAGE="${IMAGE%.qcow2}.sparse.qcow2"
329
+ rm -f "${SPARSE_IMAGE}"
330
+ echo "Sparsifying and compressing ${IMAGE}..."
331
+ virt-sparsify --compress "${IMAGE}" "${SPARSE_IMAGE}"
332
+ mv "${SPARSE_IMAGE}" "${IMAGE}"
333
+ fi
334
+
335
+ echo "Rootfs image ready at ${IMAGE}"