@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 +5 -1
- package/index.d.ts +6 -1
- package/index.js +71 -31
- package/package.json +1 -1
- package/reference/orbitdb-relay-pinner/contract.json +70 -0
- package/reference/orbitdb-relay-pinner/rootfs/Dockerfile.rootfs +19 -0
- package/reference/orbitdb-relay-pinner/rootfs/build-rootfs-image.sh +335 -0
- package/reference/orbitdb-relay-pinner/rootfs/build-rootfs.sh +521 -0
- package/reference/orbitdb-relay-pinner/rootfs/orbitdb-relay-pinner-autotls-refresh.py +224 -0
- package/reference/orbitdb-relay-pinner/rootfs/orbitdb-relay-pinner-autotls-refresh.service +24 -0
- package/reference/orbitdb-relay-pinner/rootfs/orbitdb-relay-pinner-bootstrap.conf +2 -0
- package/reference/orbitdb-relay-pinner/rootfs/orbitdb-relay-pinner-bootstrap.service +21 -0
- package/reference/orbitdb-relay-pinner/rootfs/orbitdb-relay-pinner-bootstrap.sh +121 -0
- package/reference/orbitdb-relay-pinner/rootfs/orbitdb-relay-pinner-configure.sh +240 -0
- package/reference/orbitdb-relay-pinner/rootfs/orbitdb-relay-pinner-describe.py +165 -0
- package/reference/orbitdb-relay-pinner/rootfs/orbitdb-relay-pinner-setup-server.py +237 -0
- package/reference/orbitdb-relay-pinner/rootfs/read-rootfs-contract.py +72 -0
|
@@ -0,0 +1,521 @@
|
|
|
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
|
+
OUT_DIR="${OUT_DIR:-${APP_DIR}/dist-rootfs}"
|
|
8
|
+
ROOTFS_CONTRACT_FILE="${ROOTFS_CONTRACT_FILE:-}"
|
|
9
|
+
ROOTFS_PROFILE="${ROOTFS_PROFILE:-}"
|
|
10
|
+
ROOTFS_INSTALL_MODE="${ROOTFS_INSTALL_MODE:-}"
|
|
11
|
+
ROOTFS_BUILD_DRIVER="${ROOTFS_BUILD_DRIVER:-auto}"
|
|
12
|
+
ROOTFS_SIZE_MIB="${ROOTFS_SIZE_MIB:-20480}"
|
|
13
|
+
CHANNEL="${CHANNEL:-ALEPH-CLOUDSOLUTIONS}"
|
|
14
|
+
SKIP_UPLOAD="${SKIP_UPLOAD:-0}"
|
|
15
|
+
SKIP_BUILD="${SKIP_BUILD:-0}"
|
|
16
|
+
IPFS_ADD_URL="${IPFS_ADD_URL:-https://ipfs.aleph.cloud/api/v0/add}"
|
|
17
|
+
ORBITDB_RELAY_PINNER_DIR="${ORBITDB_RELAY_PINNER_DIR:-}"
|
|
18
|
+
UNIVERSAL_CONNECTIVITY_DIR="${UNIVERSAL_CONNECTIVITY_DIR:-}"
|
|
19
|
+
|
|
20
|
+
require() {
|
|
21
|
+
command -v "$1" >/dev/null 2>&1 || {
|
|
22
|
+
echo "Missing required command: $1" >&2
|
|
23
|
+
exit 1
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
die() {
|
|
28
|
+
echo "$*" >&2
|
|
29
|
+
exit 1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
load_rootfs_contract() {
|
|
33
|
+
[ -n "${ROOTFS_CONTRACT_FILE}" ] || return 0
|
|
34
|
+
require python3
|
|
35
|
+
[ -f "${ROOTFS_CONTRACT_FILE}" ] || die "Rootfs contract does not exist: ${ROOTFS_CONTRACT_FILE}"
|
|
36
|
+
|
|
37
|
+
# The contract is relay-owned metadata. We load it once here so users can
|
|
38
|
+
# select a standardized profile without repeating profile/install-mode flags.
|
|
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
|
+
die "ROOTFS_PROFILE=${ROOTFS_PROFILE} conflicts with contract profile ${ROOTFS_CONTRACT_PROFILE}"
|
|
43
|
+
fi
|
|
44
|
+
if [ -n "${ROOTFS_INSTALL_MODE}" ] && [ "${ROOTFS_INSTALL_MODE}" != "${ROOTFS_CONTRACT_INSTALL_MODE}" ]; then
|
|
45
|
+
die "ROOTFS_INSTALL_MODE=${ROOTFS_INSTALL_MODE} conflicts with contract install mode ${ROOTFS_CONTRACT_INSTALL_MODE}"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
ROOTFS_PROFILE="${ROOTFS_CONTRACT_PROFILE}"
|
|
49
|
+
ROOTFS_INSTALL_MODE="${ROOTFS_INSTALL_MODE:-${ROOTFS_CONTRACT_INSTALL_MODE}}"
|
|
50
|
+
echo "Loaded rootfs contract: ${ROOTFS_CONTRACT_PATH}"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
load_rootfs_contract
|
|
54
|
+
ROOTFS_PROFILE="${ROOTFS_PROFILE:-py-libp2p}"
|
|
55
|
+
|
|
56
|
+
case "${ROOTFS_PROFILE}" in
|
|
57
|
+
py-libp2p)
|
|
58
|
+
IMAGE_BASENAME="aleph-py-libp2p-relay.qcow2"
|
|
59
|
+
DEFAULT_ROOTFS_VERSION="py-libp2p-relay-v0.1.0"
|
|
60
|
+
if [ -z "${ROOTFS_INSTALL_MODE}" ]; then
|
|
61
|
+
ROOTFS_INSTALL_MODE="thin"
|
|
62
|
+
fi
|
|
63
|
+
;;
|
|
64
|
+
orbitdb-relay-pinner)
|
|
65
|
+
IMAGE_BASENAME="aleph-orbitdb-relay-pinner.qcow2"
|
|
66
|
+
DEFAULT_ROOTFS_VERSION="orbitdb-relay-pinner-v0.1.0"
|
|
67
|
+
if [ -z "${ROOTFS_INSTALL_MODE}" ]; then
|
|
68
|
+
ROOTFS_INSTALL_MODE="prebaked"
|
|
69
|
+
fi
|
|
70
|
+
;;
|
|
71
|
+
uc-go-peer)
|
|
72
|
+
IMAGE_BASENAME="aleph-uc-go-peer.qcow2"
|
|
73
|
+
DEFAULT_ROOTFS_VERSION="uc-go-peer-v0.1.0"
|
|
74
|
+
if [ -z "${ROOTFS_INSTALL_MODE}" ]; then
|
|
75
|
+
ROOTFS_INSTALL_MODE="prebaked"
|
|
76
|
+
fi
|
|
77
|
+
;;
|
|
78
|
+
uc-rust-peer)
|
|
79
|
+
IMAGE_BASENAME="aleph-uc-rust-peer.qcow2"
|
|
80
|
+
DEFAULT_ROOTFS_VERSION="uc-rust-peer-v0.1.0"
|
|
81
|
+
if [ -z "${ROOTFS_INSTALL_MODE}" ]; then
|
|
82
|
+
ROOTFS_INSTALL_MODE="prebaked"
|
|
83
|
+
fi
|
|
84
|
+
;;
|
|
85
|
+
*)
|
|
86
|
+
echo "Unsupported ROOTFS_PROFILE: ${ROOTFS_PROFILE}" >&2
|
|
87
|
+
echo "Expected one of: py-libp2p, orbitdb-relay-pinner, uc-go-peer, uc-rust-peer" >&2
|
|
88
|
+
exit 1
|
|
89
|
+
;;
|
|
90
|
+
esac
|
|
91
|
+
|
|
92
|
+
case "${ROOTFS_INSTALL_MODE}" in
|
|
93
|
+
thin|prebaked)
|
|
94
|
+
;;
|
|
95
|
+
*)
|
|
96
|
+
echo "Unsupported ROOTFS_INSTALL_MODE: ${ROOTFS_INSTALL_MODE}" >&2
|
|
97
|
+
echo "Expected one of: thin, prebaked" >&2
|
|
98
|
+
exit 1
|
|
99
|
+
;;
|
|
100
|
+
esac
|
|
101
|
+
|
|
102
|
+
case "${ROOTFS_BUILD_DRIVER}" in
|
|
103
|
+
auto|host|docker)
|
|
104
|
+
;;
|
|
105
|
+
*)
|
|
106
|
+
echo "Unsupported ROOTFS_BUILD_DRIVER: ${ROOTFS_BUILD_DRIVER}" >&2
|
|
107
|
+
echo "Expected one of: auto, host, docker" >&2
|
|
108
|
+
exit 1
|
|
109
|
+
;;
|
|
110
|
+
esac
|
|
111
|
+
|
|
112
|
+
IMAGE="${OUT_DIR}/${IMAGE_BASENAME}"
|
|
113
|
+
|
|
114
|
+
resolve_rootfs_version() {
|
|
115
|
+
if [ -n "${ROOTFS_VERSION:-}" ]; then
|
|
116
|
+
printf '%s\n' "${ROOTFS_VERSION}"
|
|
117
|
+
return
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [ "${ROOTFS_PROFILE}" = "orbitdb-relay-pinner" ] && [ -n "${ORBITDB_RELAY_PINNER_DIR}" ]; then
|
|
121
|
+
local orbitdb_package_json="${ORBITDB_RELAY_PINNER_DIR}/package.json"
|
|
122
|
+
if [ -f "${orbitdb_package_json}" ] && command -v python3 >/dev/null 2>&1; then
|
|
123
|
+
local orbitdb_version
|
|
124
|
+
orbitdb_version="$(python3 - "${orbitdb_package_json}" <<'PY'
|
|
125
|
+
import json
|
|
126
|
+
import sys
|
|
127
|
+
from pathlib import Path
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
payload = json.loads(Path(sys.argv[1]).read_text())
|
|
131
|
+
except Exception:
|
|
132
|
+
raise SystemExit(1)
|
|
133
|
+
|
|
134
|
+
version = payload.get("version")
|
|
135
|
+
if not isinstance(version, str) or not version.strip():
|
|
136
|
+
raise SystemExit(1)
|
|
137
|
+
|
|
138
|
+
print(f"orbitdb-relay-pinner-v{version.strip().lstrip('v')}")
|
|
139
|
+
PY
|
|
140
|
+
)" || orbitdb_version=""
|
|
141
|
+
|
|
142
|
+
if [ -n "${orbitdb_version}" ]; then
|
|
143
|
+
printf '%s\n' "${orbitdb_version}"
|
|
144
|
+
return
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
if [ "${ROOTFS_PROFILE}" = "uc-rust-peer" ] && [ -n "${UNIVERSAL_CONNECTIVITY_DIR}" ]; then
|
|
150
|
+
local rust_peer_cargo_toml="${UNIVERSAL_CONNECTIVITY_DIR}/rust-peer/Cargo.toml"
|
|
151
|
+
if [ -f "${rust_peer_cargo_toml}" ]; then
|
|
152
|
+
local rust_peer_version
|
|
153
|
+
rust_peer_version="$(
|
|
154
|
+
sed -nE 's/^version = "([^"]+)"/uc-rust-peer-v\1/p' "${rust_peer_cargo_toml}" | head -n 1
|
|
155
|
+
)"
|
|
156
|
+
if [ -n "${rust_peer_version}" ]; then
|
|
157
|
+
printf '%s\n' "${rust_peer_version}"
|
|
158
|
+
return
|
|
159
|
+
fi
|
|
160
|
+
fi
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
if [ "${ROOTFS_PROFILE}" = "uc-go-peer" ] && [ -n "${UNIVERSAL_CONNECTIVITY_DIR}" ]; then
|
|
164
|
+
if [ -f "${UNIVERSAL_CONNECTIVITY_DIR}/go-peer/go.mod" ]; then
|
|
165
|
+
printf '%s\n' "${DEFAULT_ROOTFS_VERSION}"
|
|
166
|
+
return
|
|
167
|
+
fi
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
printf '%s\n' "${DEFAULT_ROOTFS_VERSION}"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
ROOTFS_VERSION="$(resolve_rootfs_version)"
|
|
174
|
+
|
|
175
|
+
resolve_aleph_bin() {
|
|
176
|
+
if [ -n "${ALEPH_BIN:-}" ]; then
|
|
177
|
+
printf '%s\n' "${ALEPH_BIN}"
|
|
178
|
+
return
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
local local_aleph="${REPO_DIR}/aleph-client/.venv/bin/aleph"
|
|
182
|
+
if [ -x "${local_aleph}" ]; then
|
|
183
|
+
printf '%s\n' "${local_aleph}"
|
|
184
|
+
return
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
if command -v aleph >/dev/null 2>&1; then
|
|
188
|
+
command -v aleph
|
|
189
|
+
return
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
echo "Missing aleph CLI. Set ALEPH_BIN=/path/to/aleph or install aleph-client." >&2
|
|
193
|
+
exit 1
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
build_with_host_tools() {
|
|
197
|
+
echo "Using host virt-customize/qemu-img toolchain."
|
|
198
|
+
"${SCRIPT_DIR}/build-rootfs-image.sh"
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
build_with_docker() {
|
|
202
|
+
require docker
|
|
203
|
+
|
|
204
|
+
if ! docker info >/dev/null 2>&1; then
|
|
205
|
+
echo "Docker is installed, but the Docker daemon is not running." >&2
|
|
206
|
+
echo "Start Docker Desktop, wait until it is ready, then rerun rootfs/build-rootfs.sh." >&2
|
|
207
|
+
exit 1
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
echo "virt-customize is not available on this host."
|
|
211
|
+
echo "Using Dockerized Debian/libguestfs builder instead."
|
|
212
|
+
echo "This can be slow on macOS because qemu may run without hardware acceleration."
|
|
213
|
+
|
|
214
|
+
docker build --platform linux/amd64 \
|
|
215
|
+
-t aleph-relay-rootfs-builder:local \
|
|
216
|
+
-f "${SCRIPT_DIR}/Dockerfile.rootfs" \
|
|
217
|
+
"${SCRIPT_DIR}"
|
|
218
|
+
|
|
219
|
+
local orbitdb_mount=()
|
|
220
|
+
local orbitdb_env=()
|
|
221
|
+
local uc_mount=()
|
|
222
|
+
local uc_env=()
|
|
223
|
+
if [ "${ROOTFS_PROFILE}" = "orbitdb-relay-pinner" ]; then
|
|
224
|
+
if [ -z "${ORBITDB_RELAY_PINNER_DIR}" ]; then
|
|
225
|
+
echo "ROOTFS_PROFILE=orbitdb-relay-pinner requires ORBITDB_RELAY_PINNER_DIR=/path/to/orbitdb-relay-pinner" >&2
|
|
226
|
+
exit 1
|
|
227
|
+
fi
|
|
228
|
+
if [ ! -d "${ORBITDB_RELAY_PINNER_DIR}" ]; then
|
|
229
|
+
echo "Missing orbitdb-relay-pinner directory: ${ORBITDB_RELAY_PINNER_DIR}" >&2
|
|
230
|
+
exit 1
|
|
231
|
+
fi
|
|
232
|
+
orbitdb_mount=(-v "${ORBITDB_RELAY_PINNER_DIR}:/workspace-orbitdb-relay-pinner:ro")
|
|
233
|
+
orbitdb_env=(-e ORBITDB_RELAY_PINNER_DIR=/workspace-orbitdb-relay-pinner)
|
|
234
|
+
fi
|
|
235
|
+
if [ "${ROOTFS_PROFILE}" = "uc-rust-peer" ]; then
|
|
236
|
+
if [ -z "${UNIVERSAL_CONNECTIVITY_DIR}" ]; then
|
|
237
|
+
echo "ROOTFS_PROFILE=uc-rust-peer requires UNIVERSAL_CONNECTIVITY_DIR=/path/to/universal-connectivity" >&2
|
|
238
|
+
exit 1
|
|
239
|
+
fi
|
|
240
|
+
if [ ! -d "${UNIVERSAL_CONNECTIVITY_DIR}/rust-peer" ]; then
|
|
241
|
+
echo "Missing rust-peer directory: ${UNIVERSAL_CONNECTIVITY_DIR}/rust-peer" >&2
|
|
242
|
+
exit 1
|
|
243
|
+
fi
|
|
244
|
+
uc_mount=(-v "${UNIVERSAL_CONNECTIVITY_DIR}:/workspace-universal-connectivity:ro")
|
|
245
|
+
uc_env=(-e UNIVERSAL_CONNECTIVITY_DIR=/workspace-universal-connectivity)
|
|
246
|
+
fi
|
|
247
|
+
if [ "${ROOTFS_PROFILE}" = "uc-go-peer" ]; then
|
|
248
|
+
if [ -z "${UNIVERSAL_CONNECTIVITY_DIR}" ]; then
|
|
249
|
+
echo "ROOTFS_PROFILE=uc-go-peer requires UNIVERSAL_CONNECTIVITY_DIR=/path/to/universal-connectivity" >&2
|
|
250
|
+
exit 1
|
|
251
|
+
fi
|
|
252
|
+
if [ ! -d "${UNIVERSAL_CONNECTIVITY_DIR}/go-peer" ]; then
|
|
253
|
+
echo "Missing go-peer directory: ${UNIVERSAL_CONNECTIVITY_DIR}/go-peer" >&2
|
|
254
|
+
exit 1
|
|
255
|
+
fi
|
|
256
|
+
uc_mount=(-v "${UNIVERSAL_CONNECTIVITY_DIR}:/workspace-universal-connectivity:ro")
|
|
257
|
+
uc_env=(-e UNIVERSAL_CONNECTIVITY_DIR=/workspace-universal-connectivity)
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
docker run --rm --privileged --platform linux/amd64 \
|
|
261
|
+
-e LIBGUESTFS_BACKEND=direct \
|
|
262
|
+
-e ROOTFS_PROFILE="${ROOTFS_PROFILE}" \
|
|
263
|
+
-e ROOTFS_INSTALL_MODE="${ROOTFS_INSTALL_MODE}" \
|
|
264
|
+
-e PY_LIBP2P_DIR=/workspace/py-libp2p \
|
|
265
|
+
"${orbitdb_env[@]}" \
|
|
266
|
+
"${uc_env[@]}" \
|
|
267
|
+
-e OUT_DIR=/workspace/relay-deployer-pwa/dist-rootfs \
|
|
268
|
+
-e BASE_URL="${BASE_URL:-}" \
|
|
269
|
+
-e IMAGE_SIZE="${IMAGE_SIZE:-20G}" \
|
|
270
|
+
-v "${REPO_DIR}:/workspace" \
|
|
271
|
+
"${orbitdb_mount[@]}" \
|
|
272
|
+
"${uc_mount[@]}" \
|
|
273
|
+
-w /workspace/relay-deployer-pwa \
|
|
274
|
+
aleph-relay-rootfs-builder:local \
|
|
275
|
+
bash rootfs/build-rootfs-image.sh
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
required_port_forwards_json() {
|
|
279
|
+
if [ -n "${ROOTFS_CONTRACT_PORT_FORWARDS_JSON:-}" ]; then
|
|
280
|
+
printf ' "requiredPortForwards": %s,\n' "${ROOTFS_CONTRACT_PORT_FORWARDS_JSON}"
|
|
281
|
+
return
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
case "${ROOTFS_PROFILE}" in
|
|
285
|
+
orbitdb-relay-pinner)
|
|
286
|
+
cat <<'EOF'
|
|
287
|
+
"requiredPortForwards": [
|
|
288
|
+
{ "port": 22, "tcp": true, "udp": false, "purpose": "SSH" },
|
|
289
|
+
{ "port": 80, "tcp": true, "udp": false, "purpose": "Temporary setup endpoint" },
|
|
290
|
+
{ "port": 9090, "tcp": true, "udp": false, "purpose": "Metrics and health API" },
|
|
291
|
+
{ "port": 9091, "tcp": true, "udp": false, "purpose": "libp2p TCP" },
|
|
292
|
+
{ "port": 443, "tcp": true, "udp": false, "purpose": "Caddy HTTPS and WSS proxy" },
|
|
293
|
+
{ "port": 9093, "tcp": false, "udp": true, "purpose": "WebRTC direct" },
|
|
294
|
+
{ "port": 9094, "tcp": false, "udp": true, "purpose": "QUIC" }
|
|
295
|
+
],
|
|
296
|
+
EOF
|
|
297
|
+
;;
|
|
298
|
+
uc-rust-peer)
|
|
299
|
+
cat <<'EOF'
|
|
300
|
+
"requiredPortForwards": [
|
|
301
|
+
{ "port": 22, "tcp": true, "udp": false, "purpose": "SSH" },
|
|
302
|
+
{ "port": 80, "tcp": true, "udp": false, "purpose": "Temporary setup endpoint" },
|
|
303
|
+
{ "port": 9092, "tcp": true, "udp": false, "purpose": "libp2p TCP" },
|
|
304
|
+
{ "port": 9093, "tcp": true, "udp": false, "purpose": "WebSocket bridge" },
|
|
305
|
+
{ "port": 443, "tcp": true, "udp": false, "purpose": "Caddy HTTPS and WSS proxy" },
|
|
306
|
+
{ "port": 9090, "tcp": false, "udp": true, "purpose": "WebRTC direct" },
|
|
307
|
+
{ "port": 9091, "tcp": false, "udp": true, "purpose": "QUIC" }
|
|
308
|
+
],
|
|
309
|
+
EOF
|
|
310
|
+
;;
|
|
311
|
+
uc-go-peer)
|
|
312
|
+
cat <<'EOF'
|
|
313
|
+
"requiredPortForwards": [
|
|
314
|
+
{ "port": 22, "tcp": true, "udp": false, "purpose": "SSH" },
|
|
315
|
+
{ "port": 80, "tcp": true, "udp": false, "purpose": "Temporary setup endpoint" },
|
|
316
|
+
{ "port": 443, "tcp": true, "udp": false, "purpose": "Caddy HTTPS and WSS proxy" },
|
|
317
|
+
{ "port": 9095, "tcp": true, "udp": true, "purpose": "libp2p raw TCP and UDP transports" }
|
|
318
|
+
],
|
|
319
|
+
EOF
|
|
320
|
+
;;
|
|
321
|
+
esac
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
sync_manifest_copy_target() {
|
|
325
|
+
local manifest_path="${OUT_DIR}/rootfs-manifest.json"
|
|
326
|
+
local copy_target="${ROOTFS_CONTRACT_MANIFEST_COPY_TARGET:-}"
|
|
327
|
+
local resolved_target=""
|
|
328
|
+
local target_dir=""
|
|
329
|
+
local target_ext=".json"
|
|
330
|
+
local versioned_target=""
|
|
331
|
+
|
|
332
|
+
[ -n "${copy_target}" ] || return 0
|
|
333
|
+
[ -f "${manifest_path}" ] || die "Manifest does not exist: ${manifest_path}"
|
|
334
|
+
|
|
335
|
+
if [[ "${copy_target}" = /* ]]; then
|
|
336
|
+
resolved_target="${copy_target}"
|
|
337
|
+
else
|
|
338
|
+
resolved_target="${REPO_DIR}/${copy_target}"
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
target_dir="$(dirname "${resolved_target}")"
|
|
342
|
+
mkdir -p "${target_dir}"
|
|
343
|
+
cp "${manifest_path}" "${resolved_target}"
|
|
344
|
+
|
|
345
|
+
case "${resolved_target}" in
|
|
346
|
+
*.json)
|
|
347
|
+
target_ext=".json"
|
|
348
|
+
;;
|
|
349
|
+
esac
|
|
350
|
+
|
|
351
|
+
versioned_target="${target_dir}/${ROOTFS_VERSION}${target_ext}"
|
|
352
|
+
cp "${manifest_path}" "${versioned_target}"
|
|
353
|
+
|
|
354
|
+
echo "Copied rootfs manifest to ${resolved_target}"
|
|
355
|
+
echo "Copied versioned rootfs manifest to ${versioned_target}"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
write_manifest() {
|
|
359
|
+
local rootfs_item_hash="$1"
|
|
360
|
+
local rootfs_source_size_bytes=""
|
|
361
|
+
local requires_bootstrap_network="false"
|
|
362
|
+
local bootstrap_summary="Dependencies are preinstalled in the image."
|
|
363
|
+
local notes="${ROOTFS_CONTRACT_MANIFEST_NOTES:-}"
|
|
364
|
+
|
|
365
|
+
if [ "${ROOTFS_INSTALL_MODE}" = "thin" ]; then
|
|
366
|
+
requires_bootstrap_network="true"
|
|
367
|
+
bootstrap_summary="First boot installs runtime packages and application dependencies. Outbound network access is required before the relay service becomes healthy."
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
if [ -z "${notes}" ] && [ "${ROOTFS_PROFILE}" = "uc-rust-peer" ]; then
|
|
371
|
+
notes="The rust-peer image publishes a WSS bridge on 443 for browser clients, but the upstream peer does not yet self-advertise host-remapped websocket multiaddrs. Use the mapped proxy hostname or explicit multiaddrs from deployment metadata."
|
|
372
|
+
fi
|
|
373
|
+
if [ -z "${notes}" ] && [ "${ROOTFS_PROFILE}" = "uc-go-peer" ]; then
|
|
374
|
+
notes="The go-peer image keeps the relay on internal port 9095, then advertises raw TCP on external port 80, secure WebSocket on 443 via Caddy, and a single externally mapped UDP port for QUIC, WebTransport, and WebRTC-direct."
|
|
375
|
+
fi
|
|
376
|
+
|
|
377
|
+
if [ -f "${OUT_DIR}/ipfs-add-response.jsonl" ]; then
|
|
378
|
+
rootfs_source_size_bytes="$(python3 - "${OUT_DIR}/ipfs-add-response.jsonl" <<'PY'
|
|
379
|
+
import json
|
|
380
|
+
import sys
|
|
381
|
+
from pathlib import Path
|
|
382
|
+
|
|
383
|
+
lines = [line for line in Path(sys.argv[1]).read_text().splitlines() if line.strip()]
|
|
384
|
+
if not lines:
|
|
385
|
+
raise SystemExit(0)
|
|
386
|
+
|
|
387
|
+
payload = json.loads(lines[-1])
|
|
388
|
+
size = payload.get("Size")
|
|
389
|
+
if isinstance(size, str) and size.isdigit():
|
|
390
|
+
print(size)
|
|
391
|
+
elif isinstance(size, int) and size > 0:
|
|
392
|
+
print(size)
|
|
393
|
+
PY
|
|
394
|
+
)"
|
|
395
|
+
fi
|
|
396
|
+
|
|
397
|
+
cat > "${OUT_DIR}/rootfs-manifest.json" <<EOF
|
|
398
|
+
{
|
|
399
|
+
"profile": "${ROOTFS_PROFILE}",
|
|
400
|
+
"version": "${ROOTFS_VERSION}",
|
|
401
|
+
"rootfsInstallStrategy": "${ROOTFS_INSTALL_MODE}",
|
|
402
|
+
"requiresBootstrapNetwork": ${requires_bootstrap_network},
|
|
403
|
+
"bootstrapSummary": "${bootstrap_summary}",
|
|
404
|
+
$(if [[ "${rootfs_source_size_bytes}" =~ ^[0-9]+$ ]]; then printf ' "rootfsSourceSizeBytes": %s,\n' "${rootfs_source_size_bytes}"; fi)$(required_port_forwards_json) "rootfsItemHash": "${rootfs_item_hash}",
|
|
405
|
+
"rootfsSizeMiB": ${ROOTFS_SIZE_MIB},
|
|
406
|
+
"createdAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"$(if [ -n "${notes}" ]; then printf ',\n "notes": "%s"' "${notes}"; fi)
|
|
407
|
+
}
|
|
408
|
+
EOF
|
|
409
|
+
|
|
410
|
+
echo "Rootfs manifest written to ${OUT_DIR}/rootfs-manifest.json"
|
|
411
|
+
sync_manifest_copy_target
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
upload_image() {
|
|
415
|
+
local aleph_bin
|
|
416
|
+
aleph_bin="$(resolve_aleph_bin)"
|
|
417
|
+
|
|
418
|
+
require python3
|
|
419
|
+
require curl
|
|
420
|
+
[ -f "${IMAGE}" ] || die "Rootfs image does not exist: ${IMAGE}"
|
|
421
|
+
|
|
422
|
+
echo "Uploading ${IMAGE} to IPFS via ${IPFS_ADD_URL}..." >&2
|
|
423
|
+
: > "${OUT_DIR}/ipfs-add-response.jsonl"
|
|
424
|
+
if ! curl --fail --silent --show-error \
|
|
425
|
+
-X POST \
|
|
426
|
+
-F "file=@${IMAGE}" \
|
|
427
|
+
"${IPFS_ADD_URL}" \
|
|
428
|
+
> "${OUT_DIR}/ipfs-add-response.jsonl"; then
|
|
429
|
+
die "IPFS upload failed for ${IMAGE}"
|
|
430
|
+
fi
|
|
431
|
+
|
|
432
|
+
local cid
|
|
433
|
+
if ! cid="$(python3 - "${OUT_DIR}/ipfs-add-response.jsonl" <<'PY'
|
|
434
|
+
import json
|
|
435
|
+
import sys
|
|
436
|
+
from pathlib import Path
|
|
437
|
+
|
|
438
|
+
lines = [line for line in Path(sys.argv[1]).read_text().splitlines() if line.strip()]
|
|
439
|
+
if not lines:
|
|
440
|
+
raise SystemExit("No response received from the IPFS add endpoint")
|
|
441
|
+
|
|
442
|
+
payload = json.loads(lines[-1])
|
|
443
|
+
cid = payload.get("Hash")
|
|
444
|
+
if not cid:
|
|
445
|
+
raise SystemExit(f"IPFS add response did not include a Hash: {payload}")
|
|
446
|
+
|
|
447
|
+
print(cid)
|
|
448
|
+
PY
|
|
449
|
+
)"; then
|
|
450
|
+
die "Failed to extract CID from ${OUT_DIR}/ipfs-add-response.jsonl"
|
|
451
|
+
fi
|
|
452
|
+
[ -n "${cid}" ] || die "IPFS upload returned an empty CID"
|
|
453
|
+
|
|
454
|
+
echo "Pinning CID ${cid} on Aleph Cloud with ${aleph_bin}..." >&2
|
|
455
|
+
: > "${OUT_DIR}/store-message.json"
|
|
456
|
+
if ! "${aleph_bin}" file pin "${cid}" \
|
|
457
|
+
--channel "${CHANNEL}" \
|
|
458
|
+
> "${OUT_DIR}/store-message.json"; then
|
|
459
|
+
die "Aleph pin failed for CID ${cid}"
|
|
460
|
+
fi
|
|
461
|
+
|
|
462
|
+
if ! python3 - "${OUT_DIR}/store-message.json" <<'PY'
|
|
463
|
+
import json
|
|
464
|
+
import sys
|
|
465
|
+
from pathlib import Path
|
|
466
|
+
|
|
467
|
+
content = Path(sys.argv[1]).read_text().strip()
|
|
468
|
+
if not content:
|
|
469
|
+
raise SystemExit("Aleph pin returned an empty response")
|
|
470
|
+
|
|
471
|
+
payload = json.loads(content)
|
|
472
|
+
print(payload["item_hash"])
|
|
473
|
+
PY
|
|
474
|
+
then
|
|
475
|
+
die "Failed to extract item_hash from ${OUT_DIR}/store-message.json"
|
|
476
|
+
fi
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
mkdir -p "${OUT_DIR}"
|
|
480
|
+
|
|
481
|
+
echo "Building rootfs profile: ${ROOTFS_PROFILE}"
|
|
482
|
+
echo "Using install mode: ${ROOTFS_INSTALL_MODE}"
|
|
483
|
+
|
|
484
|
+
if [ "${SKIP_BUILD}" != "1" ]; then
|
|
485
|
+
case "${ROOTFS_BUILD_DRIVER}" in
|
|
486
|
+
host)
|
|
487
|
+
if command -v virt-customize >/dev/null 2>&1; then
|
|
488
|
+
build_with_host_tools
|
|
489
|
+
else
|
|
490
|
+
die "ROOTFS_BUILD_DRIVER=host requested, but virt-customize is not available."
|
|
491
|
+
fi
|
|
492
|
+
;;
|
|
493
|
+
docker)
|
|
494
|
+
build_with_docker
|
|
495
|
+
;;
|
|
496
|
+
auto)
|
|
497
|
+
# GitHub-hosted runners often have libguestfs tooling installed but fail
|
|
498
|
+
# later inside supermin. Prefer the Dockerized Debian/libguestfs toolchain
|
|
499
|
+
# in CI, while keeping host builds for local Linux machines.
|
|
500
|
+
if [ "${GITHUB_ACTIONS:-}" = "true" ] && command -v docker >/dev/null 2>&1; then
|
|
501
|
+
build_with_docker
|
|
502
|
+
elif command -v virt-customize >/dev/null 2>&1; then
|
|
503
|
+
build_with_host_tools
|
|
504
|
+
else
|
|
505
|
+
build_with_docker
|
|
506
|
+
fi
|
|
507
|
+
;;
|
|
508
|
+
esac
|
|
509
|
+
else
|
|
510
|
+
echo "SKIP_BUILD=1 set; reusing ${IMAGE}"
|
|
511
|
+
[ -f "${IMAGE}" ] || die "SKIP_BUILD=1 requested, but rootfs image is missing: ${IMAGE}"
|
|
512
|
+
fi
|
|
513
|
+
|
|
514
|
+
if [ "${SKIP_UPLOAD}" = "1" ]; then
|
|
515
|
+
echo "SKIP_UPLOAD=1 set; image ready at ${IMAGE}"
|
|
516
|
+
echo "Upload later with: SKIP_BUILD=1 rootfs/build-rootfs.sh"
|
|
517
|
+
exit 0
|
|
518
|
+
fi
|
|
519
|
+
|
|
520
|
+
ROOTFS_ITEM_HASH="$(upload_image)"
|
|
521
|
+
write_manifest "${ROOTFS_ITEM_HASH}"
|