Repository navigation
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·1164 lines (1060 loc) · 49.6 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·1164 lines (1060 loc) · 49.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env sh
# c0mpute.com installer. Served at https://c0mpute.com/install.sh.
#
# Installs the c0mpute v1 stack idempotently:
# mise — runtime version manager (skipped if present)
# bun — JS runtime for TUI / JS-flavoured plugins (skipped if present)
# c0mpute — this repo (https://github.com/profullstack/c0mpute)
# coinpay — routed via https://c0mpute.com/plugins/coinpay/install.sh
# infernet — routed via https://c0mpute.com/plugins/infernet/install.sh
#
# Re-running upgrades in place. Each step skips when already installed
# unless --force is passed.
#
# Flags:
# --minimal Install only c0mpute (skip coinpay + infernet + transcode deps)
# --no-coinpay Skip CoinPay CLI
# --no-infernet Skip Infernet CLI
# --no-transcode Skip transcode plugin's system deps (ffmpeg)
# --no-tui Skip the c0mpute-tui terminal UI
# --desktop Also install the desktop app (Linux AppImage; chat, agent,
# node manager, hosting) as ~/.local/bin/c0mpute-desktop
# --worker Add Docker / FFmpeg readiness checks
# --developer Verbose diagnostics
# --force Reinstall over existing
# --ref <code> Credit a referrer on c0mpute.com/nodes (same as C0MPUTE_REF)
# --name <handle> Your node's nickname on c0mpute.com/nodes (same as C0MPUTE_NAME)
# --no-house-models Don't pull Infernet house models (same as
# INFERNET_NO_HOUSE_MODELS=1). They are ON by default:
# the node pulls and serves the models listed at
# https://infernetprotocol.com/house-models.json that fit it.
# --uncensored-models Also serve uncensored house models. They are OFF
# unless you opt in (same as INFERNET_UNCENSORED_MODELS=1)
# --no-uncensored-models Keep uncensored house models off
# (same as INFERNET_NO_UNCENSORED_MODELS=1)
# --system-service Run the worker as a root-managed systemd system
# service (needs sudo). Default: a systemd *user*
# service under your account, no root involved.
# --no-exec Don't auto-exec a fresh shell at the end
# (CI-friendly; default is to drop you into a new
# shell with c0mpute already on $PATH)
#
# Env vars:
# NO_STORAGE_RELOCATE=1 Don't symlink ~/.local/share/c0mpute to a
# bigger volume even if one's available
# (default: relocate when a writable mount
# has ≥2x more free space than $HOME)
set -eu
C0MPUTE_VERSION="${C0MPUTE_VERSION:-latest}"
C0MPUTE_HOME="${C0MPUTE_HOME:-$HOME/.c0mpute}"
RELEASE_BASE="${C0MPUTE_RELEASE_BASE:-https://c0mpute.com/releases}"
# Route plugin installs through c0mpute.com so each wrapper can do its
# own error handling; the wrapper at https://c0mpute.com/plugins/<id>/install.sh
# is the in-repo plugins/<id>/install.sh and chains to upstream itself.
COINPAY_INSTALL_URL="${COINPAY_INSTALL_URL:-https://c0mpute.com/plugins/coinpay/install.sh}"
INFERNET_INSTALL_URL="${INFERNET_INSTALL_URL:-https://c0mpute.com/plugins/infernet/install.sh}"
TRANSCODE_INSTALL_URL="${TRANSCODE_INSTALL_URL:-https://c0mpute.com/plugins/transcode/install.sh}"
INSTALL_C0MPUTE=1
INSTALL_COINPAY=1
INSTALL_INFERNET=1
INSTALL_TRANSCODE=1
INSTALL_TUI=1
INSTALL_DESKTOP="${C0MPUTE_DESKTOP:-0}"
WORKER_MODE=0
DEVELOPER_MODE=0
FORCE=0
NO_START=0
SYSTEM_SERVICE="${C0MPUTE_SYSTEM_SERVICE:-0}"
say() { printf '\033[1;36m→\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m!\033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31m✗\033[0m %s\n' "$*" >&2; exit 1; }
ok() { printf '\033[1;32m✓\033[0m %s\n' "$*"; }
# Privilege escalation helper for system-wide steps (the /usr/local/bin
# symlink + /etc/profile.d hook). Empty when already root or when sudo isn't
# available; callers try a direct write first and fall back to $SUDO.
if [ "$(id -u 2>/dev/null || echo 0)" != "0" ] && command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
else
SUDO=""
fi
while [ $# -gt 0 ]; do
case "$1" in
--minimal) INSTALL_COINPAY=0; INSTALL_INFERNET=0; INSTALL_TRANSCODE=0; INSTALL_TUI=0 ;;
--no-coinpay) INSTALL_COINPAY=0 ;;
--no-infernet) INSTALL_INFERNET=0 ;;
--no-transcode) INSTALL_TRANSCODE=0 ;;
--no-tui) INSTALL_TUI=0 ;;
--desktop) INSTALL_DESKTOP=1 ;;
--no-start) NO_START=1 ;;
--worker) WORKER_MODE=1 ;;
--developer) DEVELOPER_MODE=1 ;;
--force) FORCE=1 ;;
--no-exec) NO_EXEC=1 ;;
--ref) shift; C0MPUTE_REF="${1:-}" ;;
--ref=*) C0MPUTE_REF="${1#--ref=}" ;;
--name) shift; C0MPUTE_NAME="${1:-}" ;;
--name=*) C0MPUTE_NAME="${1#--name=}" ;;
--no-house-models) INFERNET_NO_HOUSE_MODELS=1 ;;
--uncensored-models) INFERNET_UNCENSORED_MODELS=1 ;;
--no-uncensored-models) INFERNET_NO_UNCENSORED_MODELS=1 ;;
--system-service) SYSTEM_SERVICE=1 ;;
--help|-h)
sed -n '2,41p' "$0"
exit 0
;;
*)
die "unknown flag: $1 (try --help)"
;;
esac
shift
done
detect_platform() {
os=$(uname -s | tr '[:upper:]' '[:lower:]')
arch=$(uname -m)
case "$arch" in
x86_64|amd64) arch="x86_64" ;;
arm64|aarch64) arch="aarch64" ;;
*) die "unsupported arch: $arch" ;;
esac
case "$os" in
linux|darwin) ;;
*) die "unsupported os: $os (Linux/macOS only; Windows users see docs)" ;;
esac
printf '%s-%s' "$os" "$arch"
}
require() {
command -v "$1" >/dev/null 2>&1 || die "$1 is required but not installed"
}
# Detect the user's interactive shell. Tries (in order):
# 1. $SHELL — the login shell from passwd; what `chsh` sets.
# 2. /proc/$PPID/comm — the parent process name (best effort).
# 3. fall back to "sh".
# Returns one of: bash | zsh | fish | dash | ksh | sh
detect_shell() {
candidate="${SHELL:-}"
if [ -z "$candidate" ] && [ -r "/proc/$PPID/comm" ]; then
candidate=$(cat "/proc/$PPID/comm" 2>/dev/null || true)
fi
case "${candidate:-}" in
*zsh*) echo "zsh" ;;
*bash*) echo "bash" ;;
*fish*) echo "fish" ;;
*dash*) echo "dash" ;;
*ksh*) echo "ksh" ;;
*) echo "sh" ;;
esac
}
# Shell rc file path for a given shell name.
shell_rc_for() {
case "$1" in
bash) echo "$HOME/.bashrc" ;;
zsh) echo "$HOME/.zshrc" ;;
fish) echo "$HOME/.config/fish/config.fish" ;;
*) echo "$HOME/.profile" ;;
esac
}
# ────────────────────────────────────────────────────────────────────────
# Idempotent mise + bun install
# ────────────────────────────────────────────────────────────────────────
#
# Some plugins are JS/TS-runtime-based (the Bun-built TUI, Node-based
# infernet variants). mise gives us a single tool to manage the runtime
# versions; bun is the JS runtime we standardise on. Both install
# user-locally — no sudo, no system package manager.
ensure_mise() {
if command -v mise >/dev/null 2>&1; then
return 0
fi
say "installing mise"
if ! command -v curl >/dev/null 2>&1; then
warn "curl missing; skipping mise install"
return 1
fi
curl -fsSL https://mise.run | sh >/dev/null 2>&1 || {
warn "mise install failed"
return 1
}
if [ -x "$HOME/.local/bin/mise" ]; then
PATH="$HOME/.local/bin:$PATH"
export PATH
ok "mise installed"
fi
}
ensure_bun() {
if command -v bun >/dev/null 2>&1; then
return 0
fi
if command -v mise >/dev/null 2>&1; then
say "installing bun via mise"
mise use --global bun@latest >/dev/null 2>&1 || mise install bun@latest >/dev/null 2>&1 || true
if [ -x "$HOME/.local/share/mise/installs/bun/latest/bin/bun" ] \
|| command -v bun >/dev/null 2>&1; then
ok "bun installed (via mise)"
return 0
fi
fi
say "installing bun"
curl -fsSL https://bun.sh/install | bash >/dev/null 2>&1 || {
warn "bun install failed"
return 1
}
if [ -x "$HOME/.bun/bin/bun" ]; then
PATH="$HOME/.bun/bin:$PATH"
export PATH
ok "bun installed"
fi
}
# ────────────────────────────────────────────────────────────────────────
# c0mpute itself
# ────────────────────────────────────────────────────────────────────────
# Tags of the most recent published releases, newest first (drafts are not
# listed). Empty when GitHub's API is unreachable or rate limited.
recent_release_tags() {
curl -fsSL -H 'Accept: application/vnd.github+json' \
"${C0MPUTE_RELEASES_API:-https://api.github.com/repos/profullstack/c0mpute/releases?per_page=6}" 2>/dev/null \
| sed -n 's/^ *"tag_name": *"\([^"]*\)".*/\1/p' | head -n 6
}
# Release signing. Every artifact on a c0mpute release has a minisign
# signature next to it (<artifact>.minisig). The public key is also committed
# as minisign.pub at the repo root; C0MPUTE_PUBKEY overrides it for testing.
C0MPUTE_PUBKEY="${C0MPUTE_PUBKEY:-RWQqOew3QbWhAWH36TA5roreh18RgRqgozSwhE3yZtdza7eeLKmbDbJM}"
# When minisign isn't installed we fetch this pinned upstream release
# (github.com/jedisct1/minisign, static binaries) into the temp dir and check
# it against these hard-coded sha256s before running it. No root needed.
MINISIGN_PIN_VERSION="0.12"
MINISIGN_PIN_LINUX_SHA256="9a599b48ba6eb7b1e80f12f36b94ceca7c00b7a5173c95c3efc88d9822957e73"
MINISIGN_PIN_MACOS_SHA256="89000b19535765f9cffc65a65d64a820f433ef6db8020667f7570e06bf6aac63"
c0mpute_sha256() {
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1
elif command -v openssl >/dev/null 2>&1; then openssl dgst -sha256 -r "$1" | cut -d' ' -f1
else return 1
fi
}
# Print the path of a minisign binary for platform $1, fetching the pinned
# release into dir $2 when none is installed. Fails when neither works.
c0mpute_minisign() {
if [ -z "${C0MPUTE_FORCE_PINNED_MINISIGN:-}" ] && command -v minisign >/dev/null 2>&1; then
command -v minisign
return 0
fi
case "$1" in
linux-x86_64) ms_asset="minisign-${MINISIGN_PIN_VERSION}-linux.tar.gz"; ms_want="$MINISIGN_PIN_LINUX_SHA256"; ms_member="minisign-linux/x86_64/minisign" ;;
linux-aarch64) ms_asset="minisign-${MINISIGN_PIN_VERSION}-linux.tar.gz"; ms_want="$MINISIGN_PIN_LINUX_SHA256"; ms_member="minisign-linux/aarch64/minisign" ;;
darwin-aarch64) ms_asset="minisign-${MINISIGN_PIN_VERSION}-macos.zip"; ms_want="$MINISIGN_PIN_MACOS_SHA256"; ms_member="minisign" ;;
*) warn "no pinned minisign build for $1"; return 1 ;;
esac
say "minisign not installed; fetching pinned minisign ${MINISIGN_PIN_VERSION} to verify the download" >&2
curl -fsSL -o "$2/$ms_asset" "https://github.com/jedisct1/minisign/releases/download/${MINISIGN_PIN_VERSION}/${ms_asset}" 2>/dev/null \
|| { warn "could not download minisign"; return 1; }
ms_got=$(c0mpute_sha256 "$2/$ms_asset") || { warn "no sha256 tool (sha256sum/shasum/openssl) to check minisign"; return 1; }
if [ "$ms_got" != "$ms_want" ]; then
warn "minisign download has sha256 ${ms_got}, expected ${ms_want}"
return 1
fi
mkdir -p "$2/minisign"
case "$ms_asset" in
*.zip) unzip -q -o "$2/$ms_asset" "$ms_member" -d "$2/minisign" >/dev/null 2>&1 || { warn "could not unzip minisign"; return 1; } ;;
*) tar -xzf "$2/$ms_asset" -C "$2/minisign" "$ms_member" 2>/dev/null || [ -f "$2/minisign/$ms_member" ] || { warn "could not unpack minisign"; return 1; } ;;
esac
chmod +x "$2/minisign/$ms_member"
printf '%s\n' "$2/minisign/$ms_member"
}
install_c0mpute() {
platform="$1"
target="$C0MPUTE_HOME/bin/c0mpute"
if [ -x "$target" ] && [ "$FORCE" -eq 0 ]; then
# Verify the existing binary actually runs. v0.1.0's glibc-linked
# build fails on systems with older glibc; v0.1.1+ uses musl. If
# the binary can't execute `version` cleanly, treat it as broken
# and force reinstall.
if "$target" version >/dev/null 2>&1; then
say "c0mpute already installed at $target (use --force to reinstall)"
return 0
fi
warn "existing $target appears broken (won't run); reinstalling"
fi
artifact="c0mpute-${platform}.tar.gz"
url="${RELEASE_BASE}/${C0MPUTE_VERSION}/${artifact}"
sig_url="${url}.minisig"
tmp=$(mktemp -d)
say "downloading c0mpute ${C0MPUTE_VERSION}"
http_code=$(curl -sSL -o "$tmp/$artifact" -w '%{http_code}' "$url" 2>/dev/null || echo "000")
if [ "$http_code" != "200" ] && [ "$C0MPUTE_VERSION" = "latest" ]; then
# The newest release can be public before its binaries finish uploading
# (or a platform build failed). Walk back through recent releases and
# take the first one that has this platform's asset.
warn "latest c0mpute release has no ${artifact} yet (HTTP ${http_code}); trying the previous releases"
for tag in $(recent_release_tags); do
try_url="${RELEASE_BASE}/${tag}/${artifact}"
code=$(curl -sSL -o "$tmp/$artifact" -w '%{http_code}' "$try_url" 2>/dev/null || echo "000")
if [ "$code" = "200" ]; then
say "using c0mpute ${tag}"
url="$try_url"
sig_url="${url}.minisig"
http_code=200
break
fi
done
fi
if [ "$http_code" != "200" ]; then
rm -rf "$tmp"
cat <<EOF >&2
✗ no prebuilt c0mpute binary at ${url} (HTTP ${http_code}).
We don't have a release pipeline publishing binaries yet. While we
set that up, install from source:
git clone https://github.com/profullstack/c0mpute.git
cd c0mpute
cargo build --release --bin c0mpute
mkdir -p ~/.c0mpute/bin
cp target/release/c0mpute ~/.c0mpute/bin/c0mpute
export PATH="\$HOME/.c0mpute/bin:\$PATH"
Track release availability at:
https://github.com/profullstack/c0mpute/releases
EOF
exit 1
fi
# Fail closed: every release artifact ships a minisign signature
# (<artifact>.minisig, key in minisign.pub at the repo root). No signature,
# no verifier, a bad signature or a signature for a different file all abort.
sig_code=$(curl -sSL -o "$tmp/$artifact.minisig" -w '%{http_code}' "$sig_url" 2>/dev/null || echo "000")
if [ "$sig_code" != "200" ]; then
if [ "${C0MPUTE_ALLOW_UNSIGNED:-0}" = "1" ]; then
warn "no signature at ${sig_url} (HTTP ${sig_code}); installing UNVERIFIED because C0MPUTE_ALLOW_UNSIGNED=1"
else
rm -rf "$tmp"
die "no signature at ${sig_url} (HTTP ${sig_code}); refusing to install an unverified binary (C0MPUTE_ALLOW_UNSIGNED=1 overrides)"
fi
else
say "verifying signature for c0mpute"
if ! ms_bin=$(c0mpute_minisign "$platform" "$tmp"); then
rm -rf "$tmp"
die "cannot verify the download: install minisign (brew install minisign / apt install minisign / https://jedisct1.github.io/minisign/) and re-run"
fi
if ! ms_out=$("$ms_bin" -V -P "$C0MPUTE_PUBKEY" -m "$tmp/$artifact" -x "$tmp/$artifact.minisig" 2>&1); then
rm -rf "$tmp"
die "signature verification FAILED for ${artifact} from ${url}; not installing it"
fi
# The signed (trusted) comment names the file and release, so a validly
# signed artifact for another platform or release can't be swapped in.
ms_tag=""
case "$url" in */latest/*) ;; *) ms_tag="${url%/*}"; ms_tag="${ms_tag##*/}" ;; esac
case "$ms_out" in
*"file:${artifact}"*) ;;
*) rm -rf "$tmp"; die "signature for ${artifact} is valid but signs a different file; not installing it" ;;
esac
if [ -n "$ms_tag" ]; then
case "$ms_out" in
*"version:${ms_tag} "*) ;;
*) rm -rf "$tmp"; die "signature for ${artifact} is valid but not for release ${ms_tag}; not installing it" ;;
esac
fi
ok "signature verified (minisign key $(printf '%s' "$C0MPUTE_PUBKEY" | cut -c1-12)…)"
fi
tar -xzf "$tmp/$artifact" -C "$C0MPUTE_HOME/bin"
chmod +x "$C0MPUTE_HOME/bin/c0mpute"
rm -rf "$tmp"
ok "installed c0mpute → $C0MPUTE_HOME/bin/c0mpute"
# git's remote helper for c0mpute:// URLs is this same binary under another
# name. Relative, so it survives `c0mpute update` replacing the binary.
# Without it: "git: 'remote-c0mpute' is not a git command".
ln -sf c0mpute "$C0MPUTE_HOME/bin/git-remote-c0mpute" \
&& ok "git clone/push c0mpute:// ready (git-remote-c0mpute → c0mpute)"
}
# ────────────────────────────────────────────────────────────────────────
# Chain to upstream installers for coinpay + infernet
# ────────────────────────────────────────────────────────────────────────
chain_install() {
name="$1"
url="$2"
# Always run the upstream installer so `curl | sh` upgrades plugins in place —
# the coinpay/infernet installers are idempotent and update an existing
# install. (Previously we skipped when already on PATH, so plugins went stale.)
if command -v "$name" >/dev/null 2>&1; then
say "upgrading $name (via $url)"
else
say "installing $name (via $url)"
fi
if ! curl -fsSL "$url" | sh; then
warn "$name install/upgrade failed (continuing without it)"
return 1
fi
}
# House models: Infernet nodes pull and serve the models Infernet publishes
# (https://infernetprotocol.com/house-models.json) that fit the box. Persist any
# opt-out into infernet's config and say clearly what will happen.
house_models_notice() {
command -v infernet >/dev/null 2>&1 || return 0
if [ "${INFERNET_NO_HOUSE_MODELS:-}" = "1" ]; then
infernet house-models off >/dev/null 2>&1 || true
ok "house models: off (--no-house-models)"
return 0
fi
if [ "${INFERNET_NO_UNCENSORED_MODELS:-}" = "1" ]; then
infernet house-models uncensored off >/dev/null 2>&1 || true
elif [ "${INFERNET_UNCENSORED_MODELS:-}" = "1" ]; then
infernet house-models uncensored on >/dev/null 2>&1 || true
fi
say "house models: ON — this node pulls and serves the models listed at"
say " https://infernetprotocol.com/house-models.json that fit it (RAM; keeps 10% of disk free)."
if [ "${INFERNET_UNCENSORED_MODELS:-}" = "1" ] && [ "${INFERNET_NO_UNCENSORED_MODELS:-}" != "1" ]; then
say " uncensored house models: ON (you opted in with --uncensored-models)"
else
say " uncensored house models: off (opt in: infernet house-models uncensored on)"
fi
say " opt out any time: infernet house-models off"
}
# transcode is in-process (no separate binary), but it depends on
# ffmpeg. Delegate to the plugin's own install.sh — local copy if
# we're running in-repo, otherwise the published one.
install_transcode_deps() {
local_path="$(dirname "$0")/../plugins/transcode/install.sh"
if [ -f "$local_path" ]; then
say "running transcode plugin installer (local: $local_path)"
sh "$local_path" || warn "transcode deps install reported a problem"
return 0
fi
say "running transcode plugin installer (via $TRANSCODE_INSTALL_URL)"
if ! curl -fsSL "$TRANSCODE_INSTALL_URL" | sh; then
warn "transcode deps install failed (continuing without ffmpeg)"
return 1
fi
}
# ────────────────────────────────────────────────────────────────────────
# desktop app (opt-in: --desktop)
# ────────────────────────────────────────────────────────────────────────
# The release carries a version-less AppImage per arch
# (c0mpute-desktop-linux-<arch>.AppImage, see .github/workflows/release.yml),
# checked here against its .sha256. The app updates itself from GitHub
# releases afterwards. macOS and Windows builds are on the releases page.
install_desktop() {
plat=$(detect_platform)
case "$plat" in
linux-*) ;;
*)
warn "desktop app: get the macOS build from https://github.com/profullstack/c0mpute/releases/latest"
return 0
;;
esac
asset="c0mpute-desktop-${plat}.AppImage"
bin_dir="$HOME/.local/bin"
dest="$bin_dir/c0mpute-desktop"
mkdir -p "$bin_dir" "$HOME/.local/share/applications"
say "installing the desktop app ($asset)"
if ! curl -fsSL -o "$dest.tmp" "${RELEASE_BASE}/${C0MPUTE_VERSION}/${asset}" \
|| ! curl -fsSL -o "$dest.tmp.sha256" "${RELEASE_BASE}/${C0MPUTE_VERSION}/${asset}.sha256"; then
rm -f "$dest.tmp" "$dest.tmp.sha256"
warn "desktop app: $asset is not on the ${C0MPUTE_VERSION} release (yet); skipped"
return 0
fi
want=$(cut -d' ' -f1 < "$dest.tmp.sha256")
if command -v sha256sum >/dev/null 2>&1; then got=$(sha256sum "$dest.tmp" | cut -d' ' -f1)
else got=$(shasum -a 256 "$dest.tmp" | cut -d' ' -f1); fi
rm -f "$dest.tmp.sha256"
if [ "$want" != "$got" ]; then
rm -f "$dest.tmp"
warn "desktop app: checksum mismatch for $asset; not installed"
return 0
fi
chmod 755 "$dest.tmp" && mv "$dest.tmp" "$dest"
cat > "$HOME/.local/share/applications/c0mpute-desktop.desktop" <<DESKTOP
[Desktop Entry]
Name=c0mpute
Comment=c0mpute chat, agent, node manager and hosting
Exec=$dest %U
Terminal=false
Type=Application
Categories=Network;
StartupWMClass=c0mpute-desktop
DESKTOP
say "desktop app: $dest (menu entry: c0mpute)"
}
# ────────────────────────────────────────────────────────────────────────
# c0mpute-tui (Bun terminal UI on @profullstack/hqtui)
#
# `c0mpute tui` subprocess-launches a `c0mpute-tui` binary on PATH. We
# install the TUI as SOURCE run through Bun, wrapped by a small launcher —
# NOT a compiled binary — so a reinstall picks up the repo's copy without a
# release cycle. Source comes from the in-repo copy when running in-tree,
# otherwise the repo tarball on GitHub. Optional: any failure warns and
# continues (the rest of c0mpute works without the TUI).
# ────────────────────────────────────────────────────────────────────────
install_tui() {
bun_bin="$(command -v bun || true)"
[ -x "$HOME/.bun/bin/bun" ] && bun_bin="${bun_bin:-$HOME/.bun/bin/bun}"
if [ -z "$bun_bin" ]; then
warn "bun not available; skipping c0mpute-tui (run: c0mpute tui after installing bun)"
return 1
fi
wrapper="$C0MPUTE_HOME/bin/c0mpute-tui"
# Only skip if the existing file is actually OUR bun launcher. Earlier
# installers shipped a `bun build --compile` binary that crashed at runtime
# (the old react-blessed TUI's dynamic requires couldn't be bundled); those
# must be replaced even without --force. Detect our wrapper by its
# `bun run` marker (a compiled binary won't match under grep).
if [ -x "$wrapper" ] && [ "$FORCE" -eq 0 ] && grep -q 'bun run' "$wrapper" 2>/dev/null; then
say "c0mpute-tui already installed at $wrapper (use --force to reinstall)"
return 0
fi
if [ -e "$wrapper" ] && ! grep -q 'bun run' "$wrapper" 2>/dev/null; then
warn "replacing stale c0mpute-tui (old compiled build crashes) with the source launcher"
fi
dest="$C0MPUTE_HOME/tui"
# Locate the TUI source: in-repo copy first, else fetch the repo tarball.
local_tui="$(dirname "$0")/../apps/tui"
src=""
cleanup_tmp=""
if [ -d "$local_tui/src" ]; then
src="$local_tui"
else
say "fetching c0mpute-tui source"
tmp=$(mktemp -d)
cleanup_tmp="$tmp"
ref="${C0MPUTE_TUI_REF:-master}"
tarball="https://github.com/profullstack/c0mpute/archive/refs/heads/${ref}.tar.gz"
if curl -fsSL "$tarball" | tar -xz -C "$tmp" 2>/dev/null; then
src=$(find "$tmp" -type d -path '*/apps/tui' 2>/dev/null | head -1)
fi
if [ -z "$src" ] || [ ! -d "$src/src" ]; then
warn "couldn't fetch c0mpute-tui source; skipping (run: c0mpute tui later to retry)"
[ -n "$cleanup_tmp" ] && rm -rf "$cleanup_tmp"
return 1
fi
fi
say "installing c0mpute-tui → $dest"
rm -rf "$dest"
mkdir -p "$dest"
cp -R "$src/src" "$src/package.json" "$src/tsconfig.json" "$dest/" 2>/dev/null
# Materialise a self-contained node_modules (the monorepo hoists deps, so a
# copied node_modules would be dangling symlinks).
if ! ( cd "$dest" && "$bun_bin" install --production --no-save >/dev/null 2>&1 ); then
warn "c0mpute-tui dependency install failed; skipping"
[ -n "$cleanup_tmp" ] && rm -rf "$cleanup_tmp"
return 1
fi
cat > "$wrapper" <<EOF
#!/usr/bin/env sh
# c0mpute-tui launcher: runs the hqtui-based TUI from source through Bun.
BUN="\$(command -v bun || echo "\$HOME/.bun/bin/bun")"
exec "\$BUN" run "$dest/src/index.ts" "\$@"
EOF
chmod +x "$wrapper"
[ -n "$cleanup_tmp" ] && rm -rf "$cleanup_tmp"
ok "installed c0mpute-tui → $wrapper"
}
# ────────────────────────────────────────────────────────────────────────
# PATH + diagnostics
# ────────────────────────────────────────────────────────────────────────
# Make `c0mpute` reachable no matter the shell config. Shell rc files
# (ensure_path) only help NEW shells and silently do nothing when they're
# root-owned (cloud GPU images) — that's how you get `c0mpute: command not
# found` right after a "successful" install. Mirror infernet's installer:
# 1. Symlink the binary into a system bin dir (already on every PATH), so it
# works in the CURRENT shell immediately.
# 2. Write /etc/profile.d so future login shells also see ~/.c0mpute/bin —
# which is where the peer binaries (coinpay, infernet) and c0mpute-tui
# live; the single symlink only covers the main binary.
# Both try a direct write first, then $SUDO. All best-effort.
link_system_bin() {
target="$C0MPUTE_HOME/bin/c0mpute"
[ -x "$target" ] || return 0 # nothing installed to link
linked=""
for sysbin in /usr/local/bin /usr/bin /opt/bin; do
[ -d "$sysbin" ] || continue
if ln -sf "$target" "$sysbin/c0mpute" 2>/dev/null; then
ln -sf "$target" "$sysbin/git-remote-c0mpute" 2>/dev/null || true
linked="$sysbin/c0mpute"; ok "symlinked $linked → $target"; break
fi
if [ -n "$SUDO" ] && $SUDO ln -sf "$target" "$sysbin/c0mpute" 2>/dev/null; then
$SUDO ln -sf "$target" "$sysbin/git-remote-c0mpute" 2>/dev/null || true
linked="$sysbin/c0mpute"; ok "symlinked $linked → $target (via sudo)"; break
fi
done
[ -n "$linked" ] || warn "no writable system bin dir — relying on shell rc / profile.d for PATH"
if [ -d /etc/profile.d ]; then
line='export PATH="$HOME/.c0mpute/bin:$HOME/.local/bin:$PATH"'
if printf '# Added by c0mpute installer\n%s\n' "$line" > /etc/profile.d/c0mpute.sh 2>/dev/null; then
ok "wrote /etc/profile.d/c0mpute.sh"
elif [ -n "$SUDO" ] && printf '# Added by c0mpute installer\n%s\n' "$line" \
| $SUDO tee /etc/profile.d/c0mpute.sh >/dev/null 2>&1; then
ok "wrote /etc/profile.d/c0mpute.sh (via sudo)"
fi
fi
unset target linked sysbin line
}
ensure_path() {
# Bash / Zsh / sh-style profile. Each rc file gets the PATH line +
# the right `mise activate <shell>` invocation. Idempotent — won't
# duplicate lines on re-run.
for rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
[ -f "$rc" ] || continue
# Must be writable. An rc file created root-owned by a prior sudo install
# (common on cloud GPU images) makes `>> "$rc"` fail with a raw shell-level
# "cannot create ...: Permission denied" that 2>/dev/null can't suppress.
# Pre-check and skip — link_system_bin() already put c0mpute on PATH via
# the /usr/local/bin symlink + /etc/profile.d hook, so this is best-effort.
if [ ! -w "$rc" ]; then
warn "$rc not writable (try: sudo chown \$USER:\$USER $rc) — skipping PATH append"
continue
fi
if ! grep -q '\.c0mpute/bin' "$rc"; then
{
printf '\n# Added by c0mpute installer\n'
printf 'export PATH="$HOME/.c0mpute/bin:$HOME/.local/bin:$PATH"\n'
} >> "$rc"
fi
# mise activation per shell. .profile is sh-only and mise's
# `activate sh` doesn't exist; we skip it there.
if command -v mise >/dev/null 2>&1 && ! grep -q 'mise activate' "$rc"; then
case "$rc" in
*.bashrc) printf 'eval "$(mise activate bash)"\n' >> "$rc" ;;
*.zshrc) printf 'eval "$(mise activate zsh)"\n' >> "$rc" ;;
esac
fi
done
# Fish — different syntax, different config path.
fish_rc="$HOME/.config/fish/config.fish"
if [ -d "$HOME/.config/fish" ] || [ "$(detect_shell)" = "fish" ]; then
mkdir -p "$(dirname "$fish_rc")"
[ -f "$fish_rc" ] || touch "$fish_rc"
if ! grep -q '\.c0mpute/bin' "$fish_rc"; then
{
printf '\n# Added by c0mpute installer\n'
printf 'fish_add_path -p $HOME/.c0mpute/bin $HOME/.local/bin\n'
} >> "$fish_rc"
fi
if command -v mise >/dev/null 2>&1 && ! grep -q 'mise activate' "$fish_rc"; then
printf 'mise activate fish | source\n' >> "$fish_rc"
fi
fi
}
print_versions() {
echo
if [ -x "$C0MPUTE_HOME/bin/c0mpute" ]; then
printf 'c0mpute installed: %s\n' "$("$C0MPUTE_HOME/bin/c0mpute" version 2>/dev/null | tail -1)"
fi
if [ -x "$C0MPUTE_HOME/bin/c0mpute-tui" ]; then
printf 'c0mpute-tui: installed (run: c0mpute tui)\n'
fi
if command -v coinpay >/dev/null 2>&1; then
printf 'coinpay installed: %s\n' "$(coinpay --version 2>/dev/null || coinpay version 2>/dev/null | tail -1)"
fi
if command -v infernet >/dev/null 2>&1; then
printf 'infernet installed: %s\n' "$(infernet --version 2>/dev/null || infernet version 2>/dev/null | tail -1)"
fi
}
run_doctor() {
if [ -x "$C0MPUTE_HOME/bin/c0mpute" ]; then
PATH="$C0MPUTE_HOME/bin:$PATH" "$C0MPUTE_HOME/bin/c0mpute" doctor || true
fi
}
worker_checks() {
echo
say "worker-readiness checks"
if command -v docker >/dev/null 2>&1; then ok "docker present"; else warn "docker not installed (recommended for sandboxed jobs)"; fi
if command -v ffmpeg >/dev/null 2>&1; then ok "ffmpeg present"; else warn "ffmpeg not installed (required for transcode jobs)"; fi
}
# The worker starts with TTS + STT enabled. Python's venv support is a
# separate OS package on Debian/Ubuntu, so verify it before creating a
# systemd service that would otherwise restart-loop before advertising.
speech_python_ready() {
command -v python3 >/dev/null 2>&1 || return 1
probe=$(mktemp -d 2>/dev/null) || return 1
ready=0
if python3 -m venv "$probe/venv" >/dev/null 2>&1 \
&& "$probe/venv/bin/python" -m pip --version >/dev/null 2>&1; then
ready=1
fi
rm -rf "$probe"
[ "$ready" -eq 1 ]
}
ensure_speech_python() {
[ "$(uname -s)" = "Linux" ] || return 0
speech_python_ready && return 0
if [ -z "$SUDO" ] && [ "$(id -u)" != "0" ]; then
probe=$(mktemp -d 2>/dev/null) || return 1
if python3 -m venv --without-pip "$probe/venv" >/dev/null 2>&1; then
rm -rf "$probe"
warn "system venv pip support is unavailable; c0mpute will bootstrap pip in the user's speech environment"
return 0
fi
rm -rf "$probe"
warn "Python venv support is unavailable and sudo is not configured"
return 1
fi
if command -v apt-get >/dev/null 2>&1; then
say "installing Python 3 venv support for the default TTS/STT roles"
speech_venv_package="python3-venv"
if command -v python3 >/dev/null 2>&1; then
speech_venv_package=$(python3 -c 'import sys; print(f"python{sys.version_info.major}.{sys.version_info.minor}-venv")' 2>/dev/null || echo python3-venv)
fi
if [ -n "$SUDO" ]; then
$SUDO apt-get update || return 1
$SUDO apt-cache show "$speech_venv_package" >/dev/null 2>&1 || speech_venv_package="python3-venv"
$SUDO apt-get install -y python3 "$speech_venv_package" || return 1
else
apt-get update || return 1
apt-cache show "$speech_venv_package" >/dev/null 2>&1 || speech_venv_package="python3-venv"
apt-get install -y python3 "$speech_venv_package" || return 1
fi
speech_python_ready && return 0
fi
warn "Python venv + pip are required for the default TTS/STT roles; install your distribution's Python venv package"
return 1
}
# ────────────────────────────────────────────────────────────────────────
# Storage relocation: symlink the shard data dir onto the largest
# writable volume.
#
# c0mpute stores Reed-Solomon shards under ~/.local/share/c0mpute/shards
# by default. On hosts with a small root fs and a big mounted volume
# (RunPod /workspace, Vast.ai /workspace or /data, Lambda /lambda, bare
# metal /mnt/<whatever>) the shard dir fills the overlay fast.
#
# Same approach as infernet's install.sh: scan `df -P`, pick the
# writable mount with the most free space (excluding $HOME's mount and
# virtual/system fs), require ≥2x more free space than $HOME, then
# symlink ~/.local/share/c0mpute → <volume>/c0mpute. The Rust side
# uses the standard XDG path; the symlink redirects the actual writes.
#
# Opt out: NO_STORAGE_RELOCATE=1
# ────────────────────────────────────────────────────────────────────────
detect_storage_volume() {
[ "${NO_STORAGE_RELOCATE:-0}" = "1" ] && return 0
data_dir="$HOME/.local/share/c0mpute"
# Don't relocate over an existing real directory with content; the
# operator already started using it.
if [ -d "$data_dir" ] && [ ! -L "$data_dir" ] && [ -n "$(ls -A "$data_dir" 2>/dev/null)" ]; then
return 0
fi
home_mp="$(df -P "$HOME" 2>/dev/null | awk 'NR==2 {print $6}')"
home_kb="$(df -P "$HOME" 2>/dev/null | awk 'NR==2 {print $4}')"
[ -n "$home_mp" ] || return 0
best_mp=""
best_kb=0
while read -r _fs _blocks _used _avail _capacity _mp; do
case "$_fs" in
tmpfs|devtmpfs|overlay|proc|sysfs|cgroup*|mqueue|securityfs|pstore|debugfs|tracefs|configfs|fusectl|none|squashfs|nsfs|hugetlbfs|binfmt_misc|autofs)
continue ;;
esac
case "$_mp" in
/|/proc|/proc/*|/sys|/sys/*|/dev|/dev/*|/run|/run/*|/boot|/boot/*|/etc|/etc/*|/usr|/usr/*|/var/lib/docker*|/snap|/snap/*|/tmp)
continue ;;
esac
[ "$_mp" = "$home_mp" ] && continue
[ -w "$_mp" ] || continue
[ "${_avail:-0}" -lt 10485760 ] && continue # ≥10 GB free
if [ "$_avail" -gt "$best_kb" ]; then
best_kb="$_avail"
best_mp="$_mp"
fi
done <<EOF
$(df -P 2>/dev/null | tail -n +2)
EOF
[ -z "$best_mp" ] && return 0
# Only relocate if the volume has ≥2x more free space than $HOME
# (guards against e.g. a 16 GB USB drive on a 200 GB-free desktop).
if [ "$best_kb" -lt $(( ${home_kb:-0} * 2 )) ]; then
return 0
fi
target="$best_mp/c0mpute"
mkdir -p "$target" 2>/dev/null || { warn "cannot mkdir $target; skipping relocation"; return 0; }
mkdir -p "$(dirname "$data_dir")" 2>/dev/null || true
# If the data dir is missing, an empty real dir, or already a symlink,
# replace it with a symlink pointing at the big volume.
if [ -L "$data_dir" ]; then
current="$(readlink "$data_dir" 2>/dev/null)"
if [ "$current" = "$target" ]; then
ok "shard storage already symlinked → $target"
return 0
fi
rm -f "$data_dir" 2>/dev/null || true
elif [ -d "$data_dir" ]; then
rmdir "$data_dir" 2>/dev/null || true
fi
ln -sf "$target" "$data_dir" 2>/dev/null || {
warn "ln -s $target $data_dir failed; using $data_dir on root fs"
return 0
}
free_g=$(( best_kb / 1024 / 1024 ))
home_g=$(( ${home_kb:-0} / 1024 / 1024 ))
ok "shard storage → $target (${free_g}G free, vs \$HOME ${home_g}G)"
}
# ────────────────────────────────────────────────────────────────────────
# Auto-start the worker so a node self-onboards on install/upgrade. Default:
# a systemd *user* service under your own account (no root, nothing written
# outside $HOME), with lingering so it starts at boot. A root-managed system
# service only with --system-service, or when a previous install made one.
# Falls back to a background daemon. Idempotent: restarts onto the new binary.
# ────────────────────────────────────────────────────────────────────────
worker_unit() { # <WantedBy> [User= line]
cat <<UNIT
[Unit]
Description=c0mpute worker
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
${2:-}
Environment=HOME=$HOME
Environment=PATH=$C0MPUTE_HOME/bin:$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=$C0MPUTE_HOME/bin/c0mpute worker start
Restart=on-failure
RestartSec=5
# 90: replaced by a newer worker start of this identity; 91: a duplicate that
# stood down. Restarting either would fight the worker that replaced it.
RestartPreventExitStatus=90 91
[Install]
WantedBy=$1
UNIT
}
# One identity, one worker: a user unit next to the system unit would start a
# second worker with the same identity at every boot.
remove_user_service() {
unit="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/c0mpute-worker.service"
[ -f "$unit" ] || return 0
systemctl --user disable --now c0mpute-worker >/dev/null 2>&1
rm -f "$unit"
systemctl --user daemon-reload >/dev/null 2>&1
ok "removed the c0mpute-worker user service (the system service runs this worker now)"
}
start_system_service() {
{ [ -n "$SUDO" ] || [ "$(id -u)" = "0" ]; } || return 1
remove_user_service
tmp=$(mktemp)
worker_unit multi-user.target "User=$(id -un)" > "$tmp"
if $SUDO cp "$tmp" /etc/systemd/system/c0mpute-worker.service 2>/dev/null; then
rm -f "$tmp"
$SUDO systemctl daemon-reload 2>/dev/null
$SUDO systemctl enable c0mpute-worker >/dev/null 2>&1
if $SUDO systemctl restart c0mpute-worker 2>/dev/null; then
ok "worker running as a systemd system service as $(id -un) (auto-starts on boot, restarts on crash)"
return 0
fi
fi
rm -f "$tmp"
return 1
}
start_user_service() {
[ "$(id -u)" = "0" ] && return 1
systemctl --user show-environment >/dev/null 2>&1 || return 1
unit_dir="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
mkdir -p "$unit_dir" || return 1
worker_unit default.target > "$unit_dir/c0mpute-worker.service" || return 1
"$C0MPUTE_HOME/bin/c0mpute" worker stop >/dev/null 2>&1 # a daemon from an earlier install
systemctl --user daemon-reload 2>/dev/null
systemctl --user enable c0mpute-worker >/dev/null 2>&1
systemctl --user restart c0mpute-worker 2>/dev/null || return 1
ok "worker running as a systemd user service as $(id -un) (no root; restarts on crash)"
# Lingering starts user services at boot, before anyone logs in. logind
# lets a user enable it for themselves without sudo on most distros.
if [ "$(loginctl show-user "$(id -un)" -p Linger --value 2>/dev/null)" = "yes" ] \
|| loginctl enable-linger "$(id -un)" >/dev/null 2>&1; then
ok " starts at boot (systemd lingering on for $(id -un); undo: loginctl disable-linger)"
else
warn " starts when you log in. To start at boot too: sudo loginctl enable-linger $(id -un)"
fi
return 0
}
start_worker() {
[ "$NO_START" -eq 1 ] && return 0
bin="$C0MPUTE_HOME/bin/c0mpute"
[ -x "$bin" ] || return 0
say "starting the c0mpute worker (auto-registers + serves its models)"
if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
# Keep a system service an earlier install (or --system-service) set up,
# so an upgrade never leaves two workers running.
if [ -f /etc/systemd/system/c0mpute-worker.service ]; then
start_system_service && return 0
# Never start a second worker next to the system service.
warn "a c0mpute-worker system service from an earlier install exists and needs sudo to restart:"
warn " sudo systemctl restart c0mpute-worker"
warn " (or switch to a user service: sudo systemctl disable --now c0mpute-worker &&"
warn " sudo rm /etc/systemd/system/c0mpute-worker.service, then re-run this installer)"
return 0
fi
if [ "$SYSTEM_SERVICE" = "1" ]; then
start_system_service && return 0
warn "systemd system service setup failed (needs sudo); using a user service instead"
fi
start_user_service && return 0
if [ "$(id -u)" = "0" ]; then
start_system_service && return 0
fi
fi
# Fallback: detached daemon. Restart onto the new binary on upgrade.
"$bin" worker stop >/dev/null 2>&1
if "$bin" worker start -d >/dev/null 2>&1; then
ok "worker started (background daemon)"
else
warn "couldn't auto-start the worker — run: c0mpute worker start -d"
fi
}
# Public handles the worker puts in its capability ad for c0mpute.com/nodes.
# Neither is a secret, verified, or a payout; an existing value is kept unless
# a new one is passed.
# ref : who invited you. `curl -fsSL https://c0mpute.com/install.sh |
# C0MPUTE_REF=<code> sh` (links from c0mpute.com/r/<code>) -> $C0MPUTE_HOME/ref
# name: who runs this node (your nickname). `... | sh -s -- --name <handle>`
# or C0MPUTE_NAME=<handle> -> $C0MPUTE_HOME/name; later `c0mpute name <handle>`.
save_handle() { # <value> <file> <label>
code=$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')
[ -n "$code" ] || return 0
case "$code" in
*[!a-z0-9_-]*) warn "ignoring $3 '$code' (use a-z, 0-9, - or _)"; return 0 ;;
esac
if [ "${#code}" -gt 32 ]; then
warn "ignoring $3 longer than 32 characters"
return 0
fi
mkdir -p "$C0MPUTE_HOME" && printf '%s\n' "$code" > "$C0MPUTE_HOME/$2" \
&& ok "$3 '$code' saved (shown on https://c0mpute.com/nodes)"
}
save_ref() {
save_handle "${C0MPUTE_REF:-}" ref "referral code"
save_handle "${C0MPUTE_NAME:-}" name "node name"
}
# ────────────────────────────────────────────────────────────────────────
# main
# ────────────────────────────────────────────────────────────────────────
main() {