Skip to content

Commit a810dc8

Browse files
committed
feat: add sync-init.sh and tests/installers.sh
sync-init.sh verifies (and optionally replaces) the local lib/zsh/init.zsh against the canonical GitHub raw main copy. Supports --write, --local, --remote, --checksum-url and --no-checksum flags. tests/installers.sh provides a POSIX sh test suite covering: - script syntax validation (sh -n / zsh -n) - checksum integrity checks - loader install with XDG path and branch-override assertions - standalone zpmod delegation test - sync-init fixture round-trip test
1 parent b003a25 commit a810dc8

2 files changed

Lines changed: 503 additions & 0 deletions

File tree

lib/sh/sync-init.sh

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
#!/usr/bin/env sh
2+
# -*- mode: sh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
3+
# vim: ft=sh sw=2 ts=2 et
4+
#
5+
# sync-init.sh — verify and optionally sync local lib/zsh/init.zsh with remote.
6+
#
7+
# Usage:
8+
# sh lib/sh/sync-init.sh [OPTIONS]
9+
#
10+
# Options:
11+
# --write Replace local file with remote copy (requires valid checksum)
12+
# --local PATH Local file to compare (default: lib/zsh/init.zsh)
13+
# --remote URL|PATH Remote URL or local path (default: GitHub raw main)
14+
# --checksum-url URL|PATH Checksum.txt URL or path (default: GitHub raw main)
15+
# --no-checksum Skip checksum validation of remote content
16+
# --help Print this help and exit
17+
#
18+
# Exit codes:
19+
# 0 Files match (or --write sync succeeded)
20+
# 1 Files differ, fetch error, or checksum mismatch
21+
# 2 Usage error
22+
23+
set -eu
24+
25+
WORKDIR="$(mktemp -d)" || exit 1
26+
trap 'rm -rf "${WORKDIR:?}"' EXIT INT TERM
27+
28+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
29+
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
30+
31+
DEFAULT_LOCAL="${REPO_ROOT}/lib/zsh/init.zsh"
32+
DEFAULT_REMOTE="https://raw.githubusercontent.com/z-shell/zi-src/main/lib/zsh/init.zsh"
33+
DEFAULT_CHECKSUM_URL="https://raw.githubusercontent.com/z-shell/zi-src/main/lib/checksum.txt"
34+
CHECKSUM_KEY="lib/zsh/init.zsh"
35+
36+
OPT_LOCAL="${DEFAULT_LOCAL}"
37+
OPT_REMOTE="${DEFAULT_REMOTE}"
38+
OPT_CHECKSUM_URL="${DEFAULT_CHECKSUM_URL}"
39+
OPT_WRITE=0
40+
OPT_NO_CHECKSUM=0
41+
42+
# ── Helpers ───────────────────────────────────────────────────────────────────
43+
44+
print_help() {
45+
cat <<EOF
46+
Usage: sh lib/sh/sync-init.sh [OPTIONS]
47+
48+
Verify local lib/zsh/init.zsh against the canonical GitHub raw main copy.
49+
By default, reports mismatches only. Use --write to update the local file.
50+
51+
Options:
52+
--write Replace local file with remote (requires valid checksum)
53+
--local PATH Local file to compare [default: lib/zsh/init.zsh]
54+
--remote URL|PATH Remote URL or local path [default: GitHub raw main]
55+
--checksum-url URL|PATH Checksum URL or path [default: GitHub raw main checksum.txt]
56+
--no-checksum Skip checksum validation of remote content
57+
--help Print this help and exit
58+
59+
Exit codes:
60+
0 Files match (or --write sync succeeded)
61+
1 Files differ, fetch error, or checksum mismatch
62+
2 Usage error
63+
EOF
64+
}
65+
66+
# Fetch a URL or copy a local readable path to stdout.
67+
_fetch() {
68+
_src="$1"
69+
case "${_src}" in
70+
http://* | https://*)
71+
if command -v curl >/dev/null 2>&1; then
72+
command curl -fsSL "${_src}"
73+
elif command -v wget >/dev/null 2>&1; then
74+
command wget -qO- "${_src}"
75+
else
76+
printf '%s\n' "[1;31m▓▒░[0m No curl or wget available." >&2
77+
return 1
78+
fi
79+
;;
80+
*)
81+
if [ -r "${_src}" ]; then
82+
command cat "${_src}"
83+
else
84+
printf '%s\n' "[1;31m▓▒░[0m Cannot read local path: ${_src}" >&2
85+
return 1
86+
fi
87+
;;
88+
esac
89+
}
90+
91+
# Compute SHA-256 hex digest of a file.
92+
_sha256() {
93+
_file="$1"
94+
if command -v sha256sum >/dev/null 2>&1; then
95+
sha256sum "${_file}" | command awk '{print $1}'
96+
elif command -v shasum >/dev/null 2>&1; then
97+
shasum -a 256 "${_file}" | command awk '{print $1}'
98+
else
99+
printf '%s\n' "[1;31m▓▒░[0m No sha256sum or shasum available." >&2
100+
return 1
101+
fi
102+
}
103+
104+
# ── Argument Parsing ──────────────────────────────────────────────────────────
105+
106+
while [ "$#" -gt 0 ]; do
107+
case "$1" in
108+
--write)
109+
OPT_WRITE=1
110+
shift
111+
;;
112+
--local)
113+
[ "$#" -ge 2 ] || {
114+
printf '%s\n' "Error: --local requires an argument." >&2
115+
exit 2
116+
}
117+
OPT_LOCAL="$2"
118+
shift 2
119+
;;
120+
--remote)
121+
[ "$#" -ge 2 ] || {
122+
printf '%s\n' "Error: --remote requires an argument." >&2
123+
exit 2
124+
}
125+
OPT_REMOTE="$2"
126+
shift 2
127+
;;
128+
--checksum-url)
129+
[ "$#" -ge 2 ] || {
130+
printf '%s\n' "Error: --checksum-url requires an argument." >&2
131+
exit 2
132+
}
133+
OPT_CHECKSUM_URL="$2"
134+
shift 2
135+
;;
136+
--no-checksum)
137+
OPT_NO_CHECKSUM=1
138+
shift
139+
;;
140+
--help | -h)
141+
print_help
142+
exit 0
143+
;;
144+
*)
145+
printf '%s\n' "Error: unknown option: $1" >&2
146+
exit 2
147+
;;
148+
esac
149+
done
150+
151+
# ── Validate Inputs ───────────────────────────────────────────────────────────
152+
153+
if [ ! -f "${OPT_LOCAL}" ] && [ "${OPT_WRITE}" -eq 0 ]; then
154+
printf '%s\n' "[1;31m▓▒░[0m Local file not found: ${OPT_LOCAL}" >&2
155+
printf '%s\n' "[1;33m▓▒░[0m Use --write to create it from remote." >&2
156+
exit 1
157+
fi
158+
159+
# ── Fetch Remote ──────────────────────────────────────────────────────────────
160+
161+
REMOTE_FILE="${WORKDIR}/remote-init.zsh"
162+
printf '%s\n' "▓▒░ Fetching remote: ${OPT_REMOTE}"
163+
# shellcheck disable=SC2310
164+
if ! _fetch "${OPT_REMOTE}" >"${REMOTE_FILE}"; then
165+
printf '%s\n' "[1;31m▓▒░[0m Failed to fetch remote file." >&2
166+
exit 1
167+
fi
168+
169+
# ── Verify Remote Against Checksum ───────────────────────────────────────────
170+
171+
if [ "${OPT_NO_CHECKSUM}" -eq 0 ]; then
172+
CHECKSUM_FILE="${WORKDIR}/checksum.txt"
173+
printf '%s\n' "▓▒░ Fetching checksum: ${OPT_CHECKSUM_URL}"
174+
# shellcheck disable=SC2310
175+
if ! _fetch "${OPT_CHECKSUM_URL}" >"${CHECKSUM_FILE}"; then
176+
printf '%s\n' "[1;31m▓▒░[0m Failed to fetch checksum file." >&2
177+
exit 1
178+
fi
179+
180+
EXPECTED_HASH="$(grep "${CHECKSUM_KEY}" "${CHECKSUM_FILE}" | command awk '{print $1}')"
181+
if [ -z "${EXPECTED_HASH}" ]; then
182+
printf '%s\n' "[1;31m▓▒░[0m No checksum entry for '${CHECKSUM_KEY}' in checksum.txt." >&2
183+
exit 1
184+
fi
185+
186+
REMOTE_HASH="$(_sha256 "${REMOTE_FILE}")"
187+
if [ "${REMOTE_HASH}" != "${EXPECTED_HASH}" ]; then
188+
printf '%s\n' "[1;31m▓▒░[0m Remote checksum mismatch!" >&2
189+
printf '%s\n' " expected : ${EXPECTED_HASH}" >&2
190+
printf '%s\n' " got : ${REMOTE_HASH}" >&2
191+
exit 1
192+
fi
193+
printf '%s\n' "▓▒░ Remote checksum verified: ${REMOTE_HASH}"
194+
else
195+
REMOTE_HASH="$(_sha256 "${REMOTE_FILE}")"
196+
printf '%s\n' "▓▒░ Remote hash (checksum validation skipped): ${REMOTE_HASH}"
197+
fi
198+
199+
# ── Compare ───────────────────────────────────────────────────────────────────
200+
201+
if [ -f "${OPT_LOCAL}" ]; then
202+
LOCAL_HASH="$(_sha256 "${OPT_LOCAL}")"
203+
else
204+
LOCAL_HASH="(file not present)"
205+
fi
206+
207+
if [ "${LOCAL_HASH}" = "${REMOTE_HASH}" ]; then
208+
printf '%s\n' "[1;32m▓▒░[0m Local file matches remote. No sync needed."
209+
printf '%s\n' " hash : ${LOCAL_HASH}"
210+
printf '%s\n' " path : ${OPT_LOCAL}"
211+
exit 0
212+
fi
213+
214+
printf '%s\n' "[1;33m▓▒░[0m Local and remote differ."
215+
printf '%s\n' " local : ${LOCAL_HASH}"
216+
printf '%s\n' " remote : ${REMOTE_HASH}"
217+
printf '%s\n' " source : ${OPT_REMOTE}"
218+
219+
if command -v diff >/dev/null 2>&1 && [ -f "${OPT_LOCAL}" ]; then
220+
printf '\n%s\n' "--- diff (local vs remote) ---"
221+
diff -u "${OPT_LOCAL}" "${REMOTE_FILE}" || true
222+
printf '%s\n' "--- end diff ---"
223+
fi
224+
225+
# ── Sync ──────────────────────────────────────────────────────────────────────
226+
227+
if [ "${OPT_WRITE}" -eq 0 ]; then
228+
printf '\n%s\n' "▓▒░ Run with --write to replace the local file."
229+
exit 1
230+
fi
231+
232+
LOCAL_DIR="$(dirname "${OPT_LOCAL}")"
233+
TMP_TARGET="${LOCAL_DIR}/.sync-init.zsh.tmp.$$"
234+
235+
command cp "${REMOTE_FILE}" "${TMP_TARGET}"
236+
237+
if [ -f "${OPT_LOCAL}" ]; then
238+
ORIG_MODE="$(command stat -c '%a' "${OPT_LOCAL}" 2>/dev/null ||
239+
command stat -f '%A' "${OPT_LOCAL}" 2>/dev/null ||
240+
printf '755')"
241+
command chmod "${ORIG_MODE}" "${TMP_TARGET}"
242+
else
243+
command chmod 755 "${TMP_TARGET}"
244+
fi
245+
246+
command mv "${TMP_TARGET}" "${OPT_LOCAL}"
247+
248+
NEW_HASH="$(_sha256 "${OPT_LOCAL}")"
249+
printf '%s\n' "[1;32m▓▒░[0m Sync complete."
250+
printf '%s\n' " before : ${LOCAL_HASH}"
251+
printf '%s\n' " after : ${NEW_HASH}"
252+
printf '%s\n' " path : ${OPT_LOCAL}"

0 commit comments

Comments
 (0)