-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup.sh
More file actions
executable file
·318 lines (288 loc) · 11.3 KB
/
setup.sh
File metadata and controls
executable file
·318 lines (288 loc) · 11.3 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
#!/usr/bin/env bash
# m-dev-tools — interactive bootstrap installer.
#
# Recommended invocation (review first, then run):
# curl -O https://raw.githubusercontent.com/m-dev-tools/.github/main/setup.sh
# less ./setup.sh
# bash ./setup.sh
#
# Or, for the convinced:
# bash <(curl -fsSL https://raw.githubusercontent.com/m-dev-tools/.github/main/setup.sh)
#
# Flags:
# -y, --yes non-interactive; accept defaults
# -d, --dir PATH install root (default: ~/m-dev-tools)
# -h, --help this message
#
# What it does:
# 1. Detect OS (Linux distro / macOS) and check for required tools
# (git, docker, python3.12+, uv, make). Prints install commands
# for anything missing and exits — never sudo's.
# 2. Verifies the Docker daemon is reachable.
# 3. Clones m-cli into the chosen install root.
# 4. Delegates the rest (sibling clones, venv install, engine
# install + start, m doctor verification) to `make bootstrap`
# inside m-cli.
# 5. Post-install: regenerates the m-stdlib inventory
# (`make manifest`) and installs the AI knowledge skill
# (`make skill-install`) when ~/claude/skills/ is present.
# 6. Prints PATH-setup advice and a "next steps" pointer to the
# TDD lifecycle walkthrough.
#
# Idempotent — re-running on an already-installed host skips the
# clones and re-verifies via `m doctor`.
set -euo pipefail
# ── flag parsing ─────────────────────────────────────────────────────
NONINTERACTIVE=0
M_DEV_HOME_ARG=""
while [[ $# -gt 0 ]]; do
case "$1" in
-y|--yes) NONINTERACTIVE=1; shift ;;
-d|--dir) M_DEV_HOME_ARG="$2"; shift 2 ;;
-h|--help)
sed -n '2,/^set -/p' "$0" | sed 's/^# \{0,1\}//' | head -n -1
exit 0 ;;
*) printf 'unknown flag: %s\n' "$1" >&2; exit 2 ;;
esac
done
# ── pretty-print helpers (degrade gracefully on no-tty) ──────────────
if [[ -t 1 ]]; then
RED=$'\033[1;31m'; YEL=$'\033[1;33m'; GRN=$'\033[1;32m'
CYA=$'\033[1;36m'; RST=$'\033[0m'
else
RED=""; YEL=""; GRN=""; CYA=""; RST=""
fi
info() { printf '%s==>%s %s\n' "$CYA" "$RST" "$*"; }
warn() { printf '%sWARN%s %s\n' "$YEL" "$RST" "$*" >&2; }
fail() { printf '%sFAIL%s %s\n' "$RED" "$RST" "$*" >&2; exit 1; }
ok() { printf ' %s✓%s %s\n' "$GRN" "$RST" "$*"; }
ask() {
# ask "prompt" "default" — read interactively, return default in -y mode.
local prompt="$1" default="$2" reply
if (( NONINTERACTIVE )); then
printf '%s\n' "$default"
return 0
fi
read -r -p "$prompt [$default]: " reply
printf '%s\n' "${reply:-$default}"
}
# ── OS detection ─────────────────────────────────────────────────────
detect_os() {
if [[ "$(uname -s)" == "Darwin" ]]; then
printf 'macos\n'
return 0
fi
if [[ -r /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
case "${ID:-unknown}" in
ubuntu|debian|linuxmint|pop) printf 'debian\n' ;;
fedora|rhel|centos|rocky|almalinux) printf 'fedora\n' ;;
arch|manjaro|endeavouros) printf 'arch\n' ;;
*) printf 'linux-other\n' ;;
esac
return 0
fi
printf 'unsupported\n'
}
OS=$(detect_os)
case "$OS" in
macos|debian|fedora|arch|linux-other)
info "Detected OS: $OS" ;;
unsupported)
fail "Unsupported OS. m-dev-tools targets Linux (apt/dnf/pacman) and macOS." ;;
esac
# ── arch detection ───────────────────────────────────────────────────
ARCH_RAW=$(uname -m)
case "$ARCH_RAW" in
arm64|aarch64) ARCH=arm64 ;;
x86_64|amd64) ARCH=amd64 ;;
*) ARCH="$ARCH_RAW"
warn "Unknown arch: $ARCH_RAW — proceeding without arch compatibility check" ;;
esac
info "Detected arch: $ARCH"
install_hint() {
# Print a one-line install hint for the given package on the detected OS.
local pkg="$1"
case "$OS" in
macos) printf ' brew install %s\n' "$pkg" ;;
debian) printf ' sudo apt install %s\n' "$pkg" ;;
fedora) printf ' sudo dnf install %s\n' "$pkg" ;;
arch) printf ' sudo pacman -S %s\n' "$pkg" ;;
*) printf ' install %s via your package manager\n' "$pkg" ;;
esac
}
# ── pre-flight ───────────────────────────────────────────────────────
info "Pre-flight checks..."
missing=0
# git
if command -v git >/dev/null; then
ok "git present ($(git --version | awk '{print $3}'))"
else
warn "git not found."
install_hint git
missing=1
fi
# docker
if command -v docker >/dev/null; then
ok "docker present ($(docker --version | awk '{print $3}' | tr -d ,))"
else
warn "docker not found."
case "$OS" in
debian) install_hint docker.io ;;
macos) printf ' brew install --cask docker-desktop # then launch Docker Desktop\n' ;;
*) install_hint docker ;;
esac
missing=1
fi
# make
if command -v make >/dev/null; then
ok "make present"
else
warn "make not found."
install_hint make
missing=1
fi
# python 3.12+
PYTHON=""
for py in python3.12 python3.13 python3 python; do
if command -v "$py" >/dev/null && \
"$py" -c 'import sys; sys.exit(0 if sys.version_info >= (3,12) else 1)' 2>/dev/null; then
PYTHON="$py"
break
fi
done
if [[ -n "$PYTHON" ]]; then
ok "python ≥ 3.12 present ($("$PYTHON" --version 2>&1))"
else
warn "python 3.12+ not found."
case "$OS" in
macos) printf ' brew install python@3.12\n' ;;
debian) printf ' sudo apt install python3.12 python3.12-venv\n' ;;
fedora) printf ' sudo dnf install python3.12\n' ;;
arch) printf ' sudo pacman -S python\n' ;;
*) install_hint python3.12 ;;
esac
missing=1
fi
# uv
if command -v uv >/dev/null; then
ok "uv present ($(uv --version | awk '{print $2}'))"
else
warn "uv not found."
printf ' curl -LsSf https://astral.sh/uv/install.sh | sh\n'
missing=1
fi
# docker daemon reachable
if docker info >/dev/null 2>&1; then
ok "docker daemon reachable"
else
warn "docker daemon not running."
case "$OS" in
macos)
printf ' Open Docker Desktop and wait for the daemon to start.\n' ;;
debian|fedora|arch)
printf ' sudo systemctl start docker\n'
printf ' sudo usermod -aG docker $USER # then log out / back in for group to take effect\n' ;;
*)
printf ' Start the Docker daemon for your platform.\n' ;;
esac
missing=1
fi
if (( missing )); then
fail "fix the prerequisites above, then re-run setup.sh."
fi
# ── engine-image arch compatibility ──────────────────────────────────
# The bootstrap will pull ghcr.io/m-dev-tools/m-test-engine. If that image
# has no manifest entry for the host arch, surface it now and pin the
# platform so Docker runs the amd64 image under emulation rather than
# failing mid-bootstrap with a cryptic "no matching manifest" error.
ENGINE_IMAGE="ghcr.io/m-dev-tools/m-test-engine:0.1.0"
if [[ "$ARCH" == "arm64" || "$ARCH" == "amd64" ]]; then
info "Checking engine image manifest for $ARCH..."
if manifest=$(docker manifest inspect "$ENGINE_IMAGE" 2>/dev/null); then
if printf '%s' "$manifest" | grep -q "\"architecture\": \"$ARCH\""; then
ok "engine image $ENGINE_IMAGE has $ARCH manifest"
else
warn "Engine image $ENGINE_IMAGE has no $ARCH manifest."
warn "Setting DOCKER_DEFAULT_PLATFORM=linux/amd64 — engine will run under emulation."
export DOCKER_DEFAULT_PLATFORM=linux/amd64
if (( ! NONINTERACTIVE )); then
read -r -p "Continue with linux/amd64 emulation? [Y/n]: " reply
case "${reply:-Y}" in
[Nn]*) fail "aborted by user. Re-run once a native $ARCH image is published." ;;
esac
fi
fi
else
warn "Could not inspect $ENGINE_IMAGE manifest — skipping arch compatibility check."
warn "If the bootstrap fails with 'no matching manifest', re-run with: DOCKER_DEFAULT_PLATFORM=linux/amd64 bash setup.sh"
fi
fi
# ── confirm install location ─────────────────────────────────────────
M_DEV_HOME=$(ask "Install m-dev-tools under" "${M_DEV_HOME_ARG:-$HOME/m-dev-tools}")
info "Installing to: $M_DEV_HOME"
mkdir -p "$M_DEV_HOME"
cd "$M_DEV_HOME"
# ── clone m-cli ──────────────────────────────────────────────────────
if [[ -d m-cli/.git ]]; then
info "m-cli already cloned — skipping"
else
info "Cloning m-cli..."
git clone https://github.com/m-dev-tools/m-cli
fi
# ── delegate to make bootstrap ───────────────────────────────────────
info "Delegating to 'make bootstrap' inside m-cli..."
cd m-cli
make bootstrap
# ── post-install tasks ───────────────────────────────────────────────
# After bootstrap clones m-stdlib and the other siblings, regenerate
# the m-stdlib inventory (drift-check on the committed dist/ artefacts)
# and — on a Claude Code host — install the AI knowledge skill into
# ~/claude/skills/m-stdlib/.
#
# Both targets shell out to python3 in m-stdlib's Makefile, so they
# don't require m-cli's venv on PATH. Failures here are warned, not
# fatal — the workspace is already usable after `make bootstrap`.
info "Running post-install tasks..."
M_STDLIB="$M_DEV_HOME/m-stdlib"
if [[ -d "$M_STDLIB" ]]; then
info " m-stdlib: regenerating inventory (make manifest)..."
if ( cd "$M_STDLIB" && make manifest >/dev/null ); then
ok "m-stdlib inventory up to date"
else
warn "m-stdlib 'make manifest' failed — re-run inside $M_STDLIB to investigate."
fi
if [[ -d "$HOME/claude/skills" ]]; then
info " m-stdlib: installing AI knowledge skill (make skill-install)..."
if ( cd "$M_STDLIB" && make skill-install >/dev/null ); then
ok "m-stdlib skill installed at $HOME/claude/skills/m-stdlib/"
else
warn "m-stdlib 'make skill-install' failed — re-run inside $M_STDLIB to investigate."
fi
else
info " m-stdlib: skipping skill-install (no ~/claude/skills/ — not a Claude Code host)"
fi
else
warn "m-stdlib not found at $M_STDLIB — skipping post-install tasks."
fi
# ── next steps ───────────────────────────────────────────────────────
printf '\n'
ok "Setup complete."
cat <<NEXT
Next steps:
1. Add m-cli to your PATH (paste into ~/.bashrc or ~/.zshrc):
export PATH="$M_DEV_HOME/m-cli/.venv/bin:\$PATH"
2. Verify on a new shell:
m --version
m doctor
m stdlib list # confirms the m-stdlib manifest is discoverable
3. Read the TDD lifecycle walkthrough — exercises every m subcommand
end-to-end against a small data-analysis app:
$M_DEV_HOME/m-cli/docs/m-tdd-lifecycle-walkthrough.md
4. Start a project of your own:
mkdir -p ~/m-work && cd ~/m-work
m new myapp && cd myapp
m ci init --write
m test tests
NEXT