-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathonescript
More file actions
executable file
·321 lines (293 loc) · 10.8 KB
/
Copy pathonescript
File metadata and controls
executable file
·321 lines (293 loc) · 10.8 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
#!/usr/bin/env bash
set -euo pipefail
export LC_ALL=C
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CATALOG="$ROOT/catalog.tsv"
VERSION="2.1.0"
usage() {
printf '%s\n' \
'Usage: onescript COMMAND [ARGUMENTS]' \
'' \
'Commands:' \
' list List installed ONEScripts modules' \
' info MODULE_ID Show module metadata' \
' run MODULE_ID -- [ARGS...]' \
' Execute one module' \
' batch MODULE_ID...' \
' Execute safe-default modules in order' \
' menu Open the interactive module menu' \
' doctor Verify catalog, module files, and checksums'
}
catalog_error() {
local line_number="$1" reason="$2"
printf 'ERROR: invalid catalog (line %s: %s)\n' "$line_number" "$reason" >&2
return 1
}
validate_catalog() {
local line='' line_number=0 field_count=0
local id category display_name description requirements path menu_safe remainder tab=$'\t'
local seen_ids='|' seen_paths='|' module_count=0
if [[ ! -f "$CATALOG" || -L "$CATALOG" || ! -r "$CATALOG" ]]; then
catalog_error 0 'catalog is missing, linked, or unreadable'
return 1
fi
while IFS= read -r line || [[ -n "$line" ]]; do
line_number=$((line_number + 1))
[[ -z "$line" || "$line" == \#* ]] && continue
field_count=1
remainder="$line"
while [[ "$remainder" == *"$tab"* ]]; do
remainder="${remainder#*"$tab"}"
field_count=$((field_count + 1))
done
((field_count == 7)) || { catalog_error "$line_number" 'expected seven tab-separated fields'; return 1; }
IFS=$'\t' read -r id category display_name description requirements path menu_safe <<<"$line"
[[ "$id" =~ ^[a-z0-9][a-z0-9-]*$ ]] || { catalog_error "$line_number" 'invalid module ID'; return 1; }
[[ "$category" =~ ^[a-z0-9][a-z0-9-]*$ ]] || { catalog_error "$line_number" 'invalid category'; return 1; }
[[ -n "$display_name" && "$display_name" =~ ^[[:print:]]+$ ]] || { catalog_error "$line_number" 'invalid display name'; return 1; }
[[ -n "$description" && "$description" =~ ^[[:print:]]+$ ]] || { catalog_error "$line_number" 'invalid description'; return 1; }
[[ -n "$requirements" && "$requirements" =~ ^[[:print:]]+$ ]] || { catalog_error "$line_number" 'invalid requirements'; return 1; }
[[ "$path" =~ ^scripts/[a-z0-9][a-z0-9._-]*\.sh$ ]] || { catalog_error "$line_number" 'invalid module path'; return 1; }
[[ "$menu_safe" == 'yes' || "$menu_safe" == 'no' ]] || { catalog_error "$line_number" 'invalid safe-default flag'; return 1; }
[[ "$seen_ids" != *"|$id|"* ]] || { catalog_error "$line_number" 'duplicate module ID'; return 1; }
[[ "$seen_paths" != *"|$path|"* ]] || { catalog_error "$line_number" 'duplicate module path'; return 1; }
seen_ids+="$id|"
seen_paths+="$path|"
module_count=$((module_count + 1))
done <"$CATALOG"
((module_count > 0)) || { catalog_error 0 'catalog has no modules'; return 1; }
}
load_module() {
local wanted="$1" id category display_name description requirements path menu_safe
while IFS=$'\t' read -r id category display_name description requirements path menu_safe || [[ -n "$id" ]]; do
[[ -z "$id" || "$id" == \#* ]] && continue
if [[ "$id" == "$wanted" ]]; then
MODULE_ID="$id"
MODULE_CATEGORY="$category"
MODULE_DISPLAY_NAME="$display_name"
MODULE_DESCRIPTION="$description"
MODULE_REQUIREMENTS="$requirements"
MODULE_PATH="$path"
MODULE_MENU_SAFE="$menu_safe"
return 0
fi
done <"$CATALOG"
return 1
}
info_module() {
local wanted="$1"
if ! load_module "$wanted"; then
printf 'ERROR: unknown module: %s\n' "$wanted" >&2
return 1
fi
printf '%s\n' \
"module_id=$MODULE_ID" \
"category=$MODULE_CATEGORY" \
"display_name=$MODULE_DISPLAY_NAME" \
"menu_safe=$MODULE_MENU_SAFE" \
"path=$MODULE_PATH" \
"requirements=$MODULE_REQUIREMENTS" \
"description=$MODULE_DESCRIPTION"
}
module_executable() {
if [[ ! "$MODULE_PATH" =~ ^scripts/[[:alnum:]][[:alnum:]_.-]*\.sh$ ]]; then
printf 'ERROR: unsafe module path for %s\n' "$MODULE_ID" >&2
return 1
fi
MODULE_EXECUTABLE="$ROOT/$MODULE_PATH"
if [[ ! -d "$ROOT/scripts" || -L "$ROOT/scripts" || ! -f "$MODULE_EXECUTABLE" || -L "$MODULE_EXECUTABLE" || ! -x "$MODULE_EXECUTABLE" ]]; then
printf 'ERROR: module is missing, linked, or not executable: %s\n' "$MODULE_ID" >&2
return 1
fi
}
run_module() {
local wanted="$1"
shift
if ! load_module "$wanted"; then
printf 'ERROR: unknown module: %s\n' "$wanted" >&2
return 1
fi
module_executable || return 1
if (($# > 0)); then
[[ "$1" == "--" ]] || { printf 'ERROR: module arguments must follow --\n' >&2; return 2; }
shift
fi
"$MODULE_EXECUTABLE" "$@"
}
batch_modules() {
local wanted status first_status=0
for wanted in "$@"; do
if ! load_module "$wanted"; then
printf 'ERROR: unknown module: %s\n' "$wanted" >&2
return 1
fi
if [[ "$MODULE_MENU_SAFE" != 'yes' ]]; then
printf 'ERROR: module has no safe default invocation: %s\n' "$wanted" >&2
return 2
fi
module_executable || return 1
done
for wanted in "$@"; do
load_module "$wanted"
module_executable
printf '=== %s ===\n' "$wanted"
if "$MODULE_EXECUTABLE"; then
status=0
else
status=$?
fi
if ((first_status == 0 && status != 0)); then
first_status="$status"
fi
done
return "$first_status"
}
verify_checksum_manifest() {
local line path expected_path allowed
local seen='|' entry_count=0
local -a expected=('onescript' 'catalog.tsv')
if [[ ! -f "$ROOT/SHA256SUMS" || -L "$ROOT/SHA256SUMS" || ! -r "$ROOT/SHA256SUMS" ]]; then
return 1
fi
while IFS=$'\t' read -r MODULE_ID MODULE_CATEGORY MODULE_DISPLAY_NAME MODULE_DESCRIPTION MODULE_REQUIREMENTS MODULE_PATH MODULE_MENU_SAFE || [[ -n "$MODULE_ID" ]]; do
[[ -z "$MODULE_ID" || "$MODULE_ID" == \#* ]] && continue
expected+=("$MODULE_PATH")
done <"$CATALOG"
while IFS= read -r line || [[ -n "$line" ]]; do
[[ "$line" =~ ^[0-9a-f]{64}[[:space:]][[:space:]](.+)$ ]] || return 1
path="${BASH_REMATCH[1]}"
allowed='no'
for expected_path in "${expected[@]}"; do
[[ "$path" == "$expected_path" ]] && { allowed='yes'; break; }
done
[[ "$allowed" == 'yes' && "$seen" != *"|$path|"* ]] || return 1
seen+="$path|"
entry_count=$((entry_count + 1))
done <"$ROOT/SHA256SUMS"
((entry_count == ${#expected[@]})) || return 1
for expected_path in "${expected[@]}"; do
[[ "$seen" == *"|$expected_path|"* ]] || return 1
done
command -v sha256sum >/dev/null 2>&1 || return 1
(cd "$ROOT" && sha256sum -c --strict --quiet SHA256SUMS) >/dev/null 2>&1
}
doctor() {
local id category display_name description requirements path menu_safe
local module_count=0 module_files='ok' checksums='ok' result=0
if ! validate_catalog >/dev/null 2>&1; then
printf '%s\n' 'catalog=failed' 'modules=0' 'module_files=unknown' 'checksums=unknown'
return 1
fi
printf 'catalog=ok\n'
while IFS=$'\t' read -r id category display_name description requirements path menu_safe || [[ -n "$id" ]]; do
[[ -z "$id" || "$id" == \#* ]] && continue
module_count=$((module_count + 1))
load_module "$id"
if ! module_executable >/dev/null 2>&1; then
module_files='failed'
result=1
fi
done <"$CATALOG"
if ! verify_checksum_manifest; then
checksums='failed'
result=1
fi
printf 'modules=%d\nmodule_files=%s\nchecksums=%s\n' "$module_count" "$module_files" "$checksums"
return "$result"
}
menu() {
local id category display_name description requirements path menu_safe selection selection_number selected status index
local -a ids=() display_names=() descriptions=()
if [[ ! -t 0 || ! -t 1 ]]; then
printf 'ERROR: menu requires an interactive terminal\n' >&2
return 2
fi
while IFS=$'\t' read -r id category display_name description requirements path menu_safe || [[ -n "$id" ]]; do
[[ -z "$id" || "$id" == \#* || "$menu_safe" != 'yes' ]] && continue
ids+=("$id")
display_names+=("$display_name")
descriptions+=("$description")
done <"$CATALOG"
((${#ids[@]} > 0)) || { printf 'ERROR: no modules have safe default invocations\n' >&2; return 1; }
while true; do
printf '\nONEScripts modules\n'
for ((index = 0; index < ${#ids[@]}; index++)); do
printf '%d) %s [%s] - %s\n' "$((index + 1))" "${display_names[index]}" "${ids[index]}" "${descriptions[index]}"
done
printf 'q) quit\n'
read -r -p 'Select a module: ' selection || return 1
[[ "$selection" == 'q' || "$selection" == 'Q' ]] && return 0
if [[ ! "$selection" =~ ^[0-9]{1,6}$ ]]; then
printf 'Invalid selection.\n' >&2
continue
fi
selection_number=$((10#$selection))
if ((selection_number < 1 || selection_number > ${#ids[@]})); then
printf 'Invalid selection.\n' >&2
continue
fi
selected="${ids[selection_number - 1]}"
printf '=== %s ===\n' "$selected"
if run_module "$selected"; then status=0; else status=$?; fi
printf 'module_exit=%d\n' "$status"
done
}
list_modules() {
local id category display_name description requirements path menu_safe
printf 'MODULE_ID\tCATEGORY\tMENU_SAFE\tDISPLAY_NAME\tDESCRIPTION\n'
while IFS=$'\t' read -r id category display_name description requirements path menu_safe || [[ -n "$id" ]]; do
[[ -z "$id" || "$id" == \#* ]] && continue
printf '%s\t%s\t%s\t%s\t%s\n' "$id" "$category" "$menu_safe" "$display_name" "$description"
done <"$CATALOG"
}
case "${1:-}" in
list)
(($# == 1)) || { printf 'ERROR: list accepts no arguments\n' >&2; exit 2; }
validate_catalog || exit 1
list_modules
;;
info)
(($# == 2)) || { printf 'ERROR: info requires one module ID\n' >&2; exit 2; }
validate_catalog || exit 1
info_module "$2"
;;
run)
(($# >= 2)) || { printf 'ERROR: run requires a module ID\n' >&2; exit 2; }
validate_catalog || exit 1
shift
run_module "$@"
;;
batch)
(($# >= 2)) || { printf 'ERROR: batch requires at least one module ID\n' >&2; exit 2; }
validate_catalog || exit 1
shift
batch_modules "$@"
;;
menu)
(($# == 1)) || { printf 'ERROR: menu accepts no arguments\n' >&2; exit 2; }
validate_catalog || exit 1
menu
;;
doctor)
(($# == 1)) || { printf 'ERROR: doctor accepts no arguments\n' >&2; exit 2; }
doctor
;;
-h|--help)
(($# == 1)) || { printf 'ERROR: help accepts no arguments\n' >&2; exit 2; }
usage
;;
--version)
(($# == 1)) || { printf 'ERROR: version accepts no arguments\n' >&2; exit 2; }
printf '%s\n' "$VERSION"
;;
'')
if [[ -t 0 && -t 1 ]]; then
validate_catalog || exit 1
menu
else
printf 'ERROR: no command provided; use --help\n' >&2
exit 2
fi
;;
*) printf 'ERROR: unknown command: %s\n' "$1" >&2; usage >&2; exit 2 ;;
esac