-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathinstall.sh
More file actions
318 lines (294 loc) · 14.5 KB
/
Copy pathinstall.sh
File metadata and controls
318 lines (294 loc) · 14.5 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
#!/bin/sh
# moshcode — one-line installer 🤘
#
# Usage:
# curl -fsSL https://moshcoding.com/install.sh | sh
# curl -fsSL https://moshcoding.com/install.sh | sh -s -- update (alias: upgrade)
# curl -fsSL https://moshcoding.com/install.sh | sh -s -- remove (alias: uninstall)
#
# (also works straight from GitHub:)
# curl -fsSL https://raw.githubusercontent.com/moshcoder/moshcode/main/install.sh | sh
#
# What it does — dead simple, no build step:
# 1. Checks for Node.js 18+ and npm (for runtime dependencies).
# 2. Downloads the latest release tarball of moshcoder/moshcode from GitHub
# (falls back to the main branch if no release is published yet).
# 3. Installs runtime dependencies and checks startup before replacing the CLI.
# 4. Drops a `moshcode` wrapper at $MOSHCODE_BIN (default: $HOME/.local/bin)
# that just exec's `node $MOSHCODE_HOME/bin/moshcode.mjs "$@"`.
# 5. Ensures that bin dir is on your PATH.
#
# Env overrides:
# MOSHCODE_HOME=/path install dir (default: $HOME/.moshcode)
# MOSHCODE_BIN=/path/dir wrapper bin dir (default: $HOME/.local/bin)
# MOSHCODE_REF=vX.Y.Z pin a tag/branch (default: latest release, else main)
# MOSHCODE_ALLOW_ROOT=1 install as root anyway (see below)
# MOSHCODE_NO_PROXY=1 skip the pinned-TLS proxy (https:// on a Moshpit
# name will not verify without it)
#
# Do not install this with sudo. moshcode is a user-level CLI, and every path
# here is derived from $HOME — under sudo that is /root, so the payload and the
# wrapper land in root's home where your own user cannot read them. Nothing
# fails at install time; it surfaces later as "permission denied" on a binary
# that looks installed. The installer refuses that case and tells you how.
#
# Re-running updates an existing install in place.
set -eu
REPO="moshcoder/moshcode"
INSTALL_URL="https://moshcoding.com/install.sh"
MOSHCODE_HOME="${MOSHCODE_HOME:-$HOME/.moshcode}"
MOSHCODE_BIN="${MOSHCODE_BIN:-$HOME/.local/bin}"
WRAPPER="$MOSHCODE_BIN/moshcode"
SCRIPT_WRAPPER="$MOSHCODE_BIN/moshscript"
PROXY_INSTALLER="${MOSHCODE_PROXY_INSTALLER:-https://raw.githubusercontent.com/profullstack/moshpit-proxy/main/install.sh}"
# ---- pretty output (acid-lime, matching the CLI) --------------------------
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
ACID=$(printf '\033[38;2;158;240;26m'); ASH=$(printf '\033[38;2;139;147;138m')
RED=$(printf '\033[31m'); BOLD=$(printf '\033[1m'); RESET=$(printf '\033[0m')
else
ACID=''; ASH=''; RED=''; BOLD=''; RESET=''
fi
info() { printf '%s·%s %s\n' "$ASH" "$RESET" "$*"; }
ok() { printf '%s✓%s %s\n' "$ACID" "$RESET" "$*"; }
fail() { printf '%s✗%s %s\n' "$RED" "$RESET" "$*" >&2; exit 1; }
# Unlike fail(), does not exit: an optional component that did not install is
# not a reason to leave the CLI half-written.
warn() { printf '%s!%s %s\n' "$RED" "$RESET" "$*" >&2; }
# ---- prerequisites --------------------------------------------------------
need() { command -v "$1" >/dev/null 2>&1 || fail "$1 is required but not found."; }
# Installing under sudo silently installs for the wrong user. Every path here
# comes from $HOME, which sudo sets to /root, so the payload and wrappers land
# in a directory mode 0700 root — invisible to the user who ran the command.
# The install reports success and only breaks later, at first use.
#
# A bare root shell (containers, CI images, root-only boxes) has no SUDO_USER
# and is a legitimate way to install, so only the sudo-from-a-real-user case is
# refused, and MOSHCODE_ALLOW_ROOT overrides even that.
check_not_sudo() {
[ "$(id -u)" = "0" ] || return 0
[ -n "${SUDO_USER:-}" ] || return 0
if [ -n "${MOSHCODE_ALLOW_ROOT:-}" ]; then
info "MOSHCODE_ALLOW_ROOT set — installing as root into $MOSHCODE_HOME"
return 0
fi
fail "don't install moshcode with sudo.
Every path is based on \$HOME, which sudo has set to $HOME, so this would
install for root and leave $SUDO_USER unable to run it.
Run it as yourself instead:
curl -fsSL $INSTALL_URL | sh
If you really do want it system-wide, choose the paths explicitly:
sudo MOSHCODE_ALLOW_ROOT=1 MOSHCODE_HOME=/opt/moshcode \\
MOSHCODE_BIN=/usr/local/bin sh -c 'curl -fsSL $INSTALL_URL | sh'"
}
check_node() {
command -v node >/dev/null 2>&1 || fail \
"Node.js 18+ is required. Install it (https://nodejs.org) and re-run."
_major="$(node -v 2>/dev/null | sed 's/^v//' | cut -d. -f1)"
[ "${_major:-0}" -ge 18 ] || fail "Node.js 18+ required, found $(node -v)."
ok "Node.js $(node -v)"
unset _major
}
# Resolve the download URL for the latest release tag, or fall back to main.
resolve_ref() {
if [ -n "${MOSHCODE_REF:-}" ]; then
echo "$MOSHCODE_REF"; return 0
fi
# Ask GitHub for the latest release tag (no auth needed for public repos).
_tag="$(curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" 2>/dev/null \
| grep '"tag_name"' | head -1 | sed 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/')"
if [ -n "$_tag" ]; then echo "$_tag"; else echo "main"; fi
unset _tag
}
# The other installer (moshcoding.com/install.sh) keeps the package under
# $MOSHCODE_HOME/pkg and uses $MOSHCODE_HOME itself for the operator's own
# files: aliases.json, credentials.json, herd/, news.opml, sync.json. This one
# replaces $MOSHCODE_HOME wholesale. Run by hand on a box the other installer
# set up, that replaced the config directory — every alias, the account login
# and the herd's ledger, gone in one `rm -rf`. So look before removing: a
# directory that holds an operator's files, or a pkg/ package, is the config
# directory, and the package belongs in pkg/ beneath it.
config_dir_layout() {
[ -f "$MOSHCODE_HOME/pkg/bin/moshcode.mjs" ] && return 0
for _f in aliases.json credentials.json sync.json herd news.opml; do
[ -e "$MOSHCODE_HOME/$_f" ] && { unset _f; return 0; }
done
unset _f
return 1
}
fetch_and_unpack() {
_ref="$1"
if config_dir_layout; then
info "$MOSHCODE_HOME holds your settings — installing the package under $MOSHCODE_HOME/pkg"
MOSHCODE_HOME="$MOSHCODE_HOME/pkg"
fi
_url="https://codeload.github.com/$REPO/tar.gz/$_ref"
info "downloading moshcode@$_ref"
_tmp="$(mktemp -d 2>/dev/null || mktemp -d -t moshcode)"
if ! curl -fsSL "$_url" | tar -xz -C "$_tmp" 2>/dev/null; then
rm -rf "$_tmp"; fail "download failed ($_url) — check the ref/network."
fi
# Tarball extracts to a single top-level dir (e.g. moshcode-main/).
_src="$(find "$_tmp" -maxdepth 1 -type d -name 'moshcode-*' | head -1)"
[ -n "$_src" ] && [ -f "$_src/bin/moshcode.mjs" ] || { rm -rf "$_tmp"; fail "unexpected tarball layout."; }
# GitHub archives contain source, not node_modules. Prepare and check the
# new release before removing a working installation.
if ! install_deps "$_src"; then
rm -rf "$_tmp"; fail "runtime dependency installation failed — existing installation unchanged."
fi
if ! node "$_src/bin/moshcode.mjs" --version >/dev/null; then
rm -rf "$_tmp"; fail "CLI startup check failed — existing installation unchanged."
fi
rm -rf "$MOSHCODE_HOME"
mkdir -p "$(dirname "$MOSHCODE_HOME")"
mv "$_src" "$MOSHCODE_HOME"
rm -rf "$_tmp"
chmod +x "$MOSHCODE_HOME/bin/moshcode.mjs" 2>/dev/null || true
ok "installed to $MOSHCODE_HOME"
unset _ref _url _tmp _src
}
# ---- runtime dependencies -------------------------------------------------
# moshcode was dependency-free ESM until 0.96.0, and this installer was built
# on that: the source tarball carries package.json and nothing under
# node_modules. Anything package.json lists has to be fetched here — or the
# CLI dies on its first import, which is exactly what 0.96.0 through 0.98.0
# did for everyone who installed or upgraded through this script.
#
# Read with node rather than grep: "devDependencies" contains the word too,
# and a dev-only package.json must not drag npm in.
install_deps() (
# Run in a subshell so temporary paths cannot change the caller's install
# location. Only the checked staging directory is passed here.
_deps_dir="$1"
_pkg="$_deps_dir/package.json"
[ -f "$_pkg" ] || return 0
_needs_deps="$(node -e 'const p=require(process.argv[1]);console.log(Object.keys(p.dependencies||{}).length ? "yes" : "no")' "$_pkg")" \
|| fail "cannot read the staged package.json."
[ "$_needs_deps" = yes ] || return 0
command -v npm >/dev/null 2>&1 || fail "npm is required to install moshcode's dependencies (node was found, npm was not)."
info "installing runtime dependencies"
if (cd "$_deps_dir" && npm install --omit=dev --ignore-scripts --no-audit --no-fund --package-lock=false); then
ok "dependencies installed"
else
fail "npm install failed in staging — the new CLI would not start without its dependencies."
fi
)
write_wrapper() {
mkdir -p "$MOSHCODE_BIN"
cat > "$WRAPPER" <<WRAP_EOF
#!/bin/sh
# moshcode wrapper — installed by $INSTALL_URL. Re-run the installer to update.
exec node "$MOSHCODE_HOME/bin/moshcode.mjs" "\$@"
WRAP_EOF
chmod +x "$WRAPPER"
ok "wrapper at $WRAPPER"
# moshscript — thin alias for `moshcode run`, so .mosh files can use
# #!/usr/bin/env moshscript as a shebang and run like shell scripts.
cat > "$SCRIPT_WRAPPER" <<SCRIPT_EOF
#!/bin/sh
# moshscript wrapper — installed by $INSTALL_URL. Re-run the installer to update.
exec node "$MOSHCODE_HOME/bin/moshcode.mjs" run "\$@"
SCRIPT_EOF
chmod +x "$SCRIPT_WRAPPER"
ok "wrapper at $SCRIPT_WRAPPER"
}
ensure_path() {
case ":$PATH:" in *":$MOSHCODE_BIN:"*) return 0 ;; esac
for rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
[ -f "$rc" ] || continue
grep -q "$MOSHCODE_BIN" "$rc" 2>/dev/null && continue
printf '\n# Added by moshcode installer\nexport PATH="%s:$PATH"\n' "$MOSHCODE_BIN" >> "$rc"
done
info "add $MOSHCODE_BIN to PATH in this shell: export PATH=\"$MOSHCODE_BIN:\$PATH\""
}
# ---- the pinned-TLS proxy -------------------------------------------------
# Without this, HTTPS on a Moshpit name cannot work. No public CA will ever sign
# for `.eggs`, so a name answers its origin's own self-signed leaf and a stock
# client refuses it — correctly. moshpit-proxy generates one local root and
# terminates TLS in the one language a browser accepts, which turns "trust this
# certificate" from a thing you do per name into a thing you do once.
#
# Installed here rather than left as a follow-up step because the alternative is
# `moshcode dns enable` finishing successfully, every name resolving, and every
# https:// URL still failing — which reads as broken, not as unfinished.
#
# This is the one part of the install that touches the system's trust store, so
# it is the one part with an opt-out: MOSHCODE_NO_PROXY=1 skips it entirely, and
# `moshpit-proxy install.sh --uninstall` undoes it later. It installs no DNS
# routing and starts no resolver -- `moshcode dns enable` stays a thing a person
# types deliberately.
install_proxy() {
if [ -n "${MOSHCODE_NO_PROXY:-}" ]; then
info "skipping the pinned-TLS proxy (MOSHCODE_NO_PROXY is set) — https:// on a Moshpit name will not verify"
return 0
fi
info "installing the pinned-TLS proxy (so https:// on a Moshpit name verifies)"
# Downloaded first and run second, rather than `curl … | sh`.
#
# A pipeline reports the exit status of its LAST command, and a `sh` handed
# an empty stdin by a 404 exits 0 — so piping reports a successful install
# for a script that never arrived. Verified: pointed at a missing URL, the
# piped form printed "✓ pinned-TLS proxy installed, local root trusted".
_proxy_sh="$(mktemp 2>/dev/null)" || {
warn "could not create a temp file for the pinned-TLS proxy installer — skipping"
return 0
}
if ! curl -fsSL "$PROXY_INSTALLER" -o "$_proxy_sh" 2>/dev/null; then
rm -f "$_proxy_sh"
warn "could not download the pinned-TLS proxy — moshcode is fine, but https:// on a Moshpit name will not verify"
warn " retry: curl -fsSL $PROXY_INSTALLER | sh"
return 0
fi
# `--yes` because this installer is normally reached through a pipe, where
# there is no terminal to answer its prompt on. Announced above rather than
# asked, and MOSHCODE_NO_PROXY is the opt-out.
if sh "$_proxy_sh" --yes >/dev/null 2>&1; then
ok "pinned-TLS proxy installed, local root trusted"
info " it covers .moshpit by default — for other endings:"
info " MOSHPIT_PROXY_TLDS=moshpit,eggs,hacker,2600"
info " undo just this: curl -fsSL $PROXY_INSTALLER | sh -s -- --uninstall"
else
# Never fatal. moshcode is a coding CLI first, and a machine that cannot
# finish an optional component still wants the CLI it asked for. Said out
# loud so it is not discovered later as a TLS error with no explanation.
warn "the pinned-TLS proxy did not install — moshcode is fine, but https:// on a Moshpit name will not verify"
warn " retry: curl -fsSL $PROXY_INSTALLER | sh"
fi
rm -f "$_proxy_sh"
unset _proxy_sh
}
# ---- commands -------------------------------------------------------------
run_install() {
printf '\n%smoshcode installer%s %s— code hard, mosh harder 🤘%s\n\n' "$BOLD" "$RESET" "$ASH" "$RESET"
check_not_sudo
need curl; need tar
check_node
_ref="$(resolve_ref)"
fetch_and_unpack "$_ref"
write_wrapper
ensure_path
install_proxy
printf '\n%sdone.%s run:\n' "$ACID" "$RESET"
printf ' moshcode # open the TUI shell\n'
printf ' moshcode start claude # raw engine session (use agents for autonomous)\n'
printf ' moshcode prd "your idea" # plan with a numbered OpenPRD\n'
printf ' moshcode tools # install/run UGig and CoinPay CLIs\n'
printf ' moshcode help\n\n'
unset _ref
}
run_remove() {
info "removing moshcode"
rm -f "$WRAPPER" 2>/dev/null || true
rm -f "$SCRIPT_WRAPPER" 2>/dev/null || true
rm -rf "$MOSHCODE_HOME" 2>/dev/null || true
ok "removed $WRAPPER, $SCRIPT_WRAPPER, and $MOSHCODE_HOME. 🤘"
}
CMD="${1:-install}"
if [ $# -gt 0 ]; then shift; fi
case "$CMD" in
install) run_install ;;
update|upgrade) run_install ;; # re-fetch latest, same path
remove|uninstall) run_remove ;;
-h|--help|help)
sed -n '2,33p' "$0" 2>/dev/null || printf 'moshcode installer — install | update | remove\n' ;;
*) fail "unknown command: $CMD (try: install | update | remove)" ;;
esac