oh-my-devpod 0.14.4 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/runtime/VERSION +1 -1
- package/runtime/bin/omd +0 -0
- package/runtime/build/omd-brew-gateway.sh +326 -0
- package/runtime/build/omd-brew-provisioner.sh +733 -0
- package/runtime/components.toml +10 -0
- package/runtime/install/bootstrap.sh +42 -2
- package/runtime/install/update.sh +22 -1
- package/runtime/modules/core/linuxbrew.sh +36 -1
- package/runtime/modules/lib/common.sh +50 -8
- package/runtime/modules/lib/shared-linuxbrew.sh +242 -0
- package/runtime/modules/lib/source-config.sh +8 -3
- package/runtime/modules/tools/witr.sh +26 -0
- package/runtime/modules/tools/zsh-config.sh +8 -0
package/package.json
CHANGED
package/runtime/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.15.0
|
package/runtime/bin/omd
CHANGED
|
Binary file
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
production_libexec="/usr/local/libexec/oh-my-devpod"
|
|
5
|
+
production_gateway="${production_libexec}/brew-gateway"
|
|
6
|
+
resolved_self="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/$(basename "${BASH_SOURCE[0]}")"
|
|
7
|
+
if command -v readlink >/dev/null 2>&1; then
|
|
8
|
+
resolved_self="$(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null || printf '%s' "${resolved_self}")"
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
if [[ "${resolved_self}" == "${production_gateway}" ||
|
|
12
|
+
"${resolved_self}" == "${production_libexec}/bin/brew" ||
|
|
13
|
+
"${resolved_self}" == "/usr/local/bin/brew" ]]; then
|
|
14
|
+
test_mode=0
|
|
15
|
+
prefix="/home/linuxbrew/.linuxbrew"
|
|
16
|
+
state_dir="/var/lib/oh-my-devpod/linuxbrew"
|
|
17
|
+
libexec_dir="${production_libexec}"
|
|
18
|
+
else
|
|
19
|
+
test_mode="${OHMYDEVPOD_SHARED_BREW_TEST_MODE:-0}"
|
|
20
|
+
prefix="${OHMYDEVPOD_SHARED_BREW_PREFIX:-/home/linuxbrew/.linuxbrew}"
|
|
21
|
+
state_dir="${OHMYDEVPOD_SHARED_BREW_STATE_DIR:-/var/lib/oh-my-devpod/linuxbrew}"
|
|
22
|
+
libexec_dir="${OHMYDEVPOD_SHARED_BREW_LIBEXEC_DIR:-${production_libexec}}"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
manifest="${state_dir}/manifest"
|
|
26
|
+
inventory_dir="${state_dir}/inventory"
|
|
27
|
+
real_brew="${prefix}/bin/brew"
|
|
28
|
+
stable_gateway="${libexec_dir}/brew-gateway"
|
|
29
|
+
lock_file="${state_dir}/locks/mutation.lock"
|
|
30
|
+
brew_noninteractive=0
|
|
31
|
+
declare -a forwarded_env=()
|
|
32
|
+
|
|
33
|
+
fail() {
|
|
34
|
+
printf 'brew: shared Linuxbrew gateway: %s\n' "$*" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
manifest_value() {
|
|
39
|
+
local key="$1"
|
|
40
|
+
sed -n "s/^${key}=//p" "${manifest}" | head -n 1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
group_gid() {
|
|
44
|
+
local group="$1"
|
|
45
|
+
if command -v getent >/dev/null 2>&1; then
|
|
46
|
+
getent group "${group}" | awk -F: '{print $3}'
|
|
47
|
+
else
|
|
48
|
+
id -g "${group}"
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
stat_uid() {
|
|
53
|
+
if stat -c '%u' "$1" >/dev/null 2>&1; then stat -c '%u' "$1"; else stat -f '%u' "$1"; fi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
stat_gid() {
|
|
57
|
+
if stat -c '%g' "$1" >/dev/null 2>&1; then stat -c '%g' "$1"; else stat -f '%g' "$1"; fi
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
stat_mode() {
|
|
61
|
+
if stat -c '%a' "$1" >/dev/null 2>&1; then stat -c '%a' "$1"; else stat -f '%Lp' "$1"; fi
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
validate_manifest() {
|
|
65
|
+
local service_user manager_group expected_uid expected_gid actual_uid actual_gid installation_id
|
|
66
|
+
[[ -f "${manifest}" && ! -L "${manifest}" ]] || fail "missing or unsafe shared manifest"
|
|
67
|
+
[[ "$(manifest_value schema_version || true)" == "1" ]] || fail "unsupported shared manifest schema"
|
|
68
|
+
[[ "$(manifest_value managed_by || true)" == "oh-my-devpod" ]] || fail "shared manifest ownership mismatch"
|
|
69
|
+
[[ "$(manifest_value mode || true)" == "shared-service-account" ]] || fail "shared manifest mode mismatch"
|
|
70
|
+
[[ "$(manifest_value prefix || true)" == "${prefix}" ]] || fail "shared prefix mismatch"
|
|
71
|
+
[[ -d "${prefix}" && ! -L "${prefix}" && -x "${real_brew}" ]] || fail "real Brew backend is unavailable"
|
|
72
|
+
[[ "$(cd "${prefix}" 2>/dev/null && pwd -P)" == "${prefix}" ]] || fail "shared prefix resolves outside its fixed path"
|
|
73
|
+
[[ "$(cd "${state_dir}" 2>/dev/null && pwd -P)" == "${state_dir}" ]] || fail "shared state resolves outside its fixed path"
|
|
74
|
+
[[ -d "${inventory_dir}" && ! -L "${inventory_dir}" ]] || fail "shared inventory is unavailable"
|
|
75
|
+
[[ -f "${lock_file}" && ! -L "${lock_file}" ]] || fail "shared mutation lock is unavailable"
|
|
76
|
+
|
|
77
|
+
service_user="$(manifest_value service_user || true)"
|
|
78
|
+
manager_group="$(manifest_value manager_group || true)"
|
|
79
|
+
expected_uid="$(manifest_value service_uid || true)"
|
|
80
|
+
expected_gid="$(manifest_value service_gid || true)"
|
|
81
|
+
installation_id="$(manifest_value installation_id || true)"
|
|
82
|
+
[[ "${service_user}" =~ ^[a-z_][a-z0-9_-]*[$]?$ ]] || fail "shared service user is invalid"
|
|
83
|
+
[[ "${manager_group}" =~ ^[a-z_][a-z0-9_-]*[$]?$ ]] || fail "shared manager group is invalid"
|
|
84
|
+
[[ "${expected_uid}" =~ ^[0-9]+$ && "${expected_gid}" =~ ^[0-9]+$ ]] || fail "shared service identity is invalid"
|
|
85
|
+
[[ "${installation_id}" =~ ^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$ ]] || fail "shared installation ID is invalid"
|
|
86
|
+
actual_uid="$(id -u "${service_user}" 2>/dev/null || true)"
|
|
87
|
+
if [[ "${test_mode}" == "1" && -n "${OHMYDEVPOD_SHARED_BREW_TEST_SERVICE_GID:-}" ]]; then
|
|
88
|
+
actual_gid="${OHMYDEVPOD_SHARED_BREW_TEST_SERVICE_GID}"
|
|
89
|
+
else
|
|
90
|
+
actual_gid="$(group_gid "${manager_group}" 2>/dev/null || true)"
|
|
91
|
+
fi
|
|
92
|
+
[[ "${actual_uid}" == "${expected_uid}" && "${actual_gid}" == "${expected_gid}" ]] || fail "shared service identity changed"
|
|
93
|
+
[[ "$(stat_uid "${prefix}")" == "${expected_uid}" && "$(stat_gid "${prefix}")" == "${expected_gid}" ]] || fail "shared prefix ownership changed"
|
|
94
|
+
if [[ "${test_mode}" != "1" ]]; then
|
|
95
|
+
[[ "$(stat_uid "${manifest}")" == "0" && "$(stat_gid "${manifest}")" == "0" ]] || fail "shared manifest is not root-owned"
|
|
96
|
+
(( (8#$(stat_mode "${manifest}") & 8#022) == 0 )) || fail "shared manifest is writable by non-root users"
|
|
97
|
+
[[ "$(stat_uid "${state_dir}")" == "0" && "$(stat_uid "$(dirname "${lock_file}")")" == "0" ]] || fail "shared state parents are not root-owned"
|
|
98
|
+
[[ "$(stat_uid "${stable_gateway}")" == "0" && "$(stat_gid "${stable_gateway}")" == "0" ]] || fail "Brew gateway is not root-owned"
|
|
99
|
+
fi
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
service_user() {
|
|
103
|
+
manifest_value service_user
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
service_home() {
|
|
107
|
+
local user="$1" home=""
|
|
108
|
+
if command -v getent >/dev/null 2>&1; then
|
|
109
|
+
home="$(getent passwd "${user}" 2>/dev/null | awk -F: '{print $6}')"
|
|
110
|
+
fi
|
|
111
|
+
printf '%s\n' "${home:-${state_dir}/service-home}"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
valid_formula_name() {
|
|
115
|
+
[[ "$1" =~ ^[a-z0-9][a-z0-9@+_.-]*$ ]]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
record_value() {
|
|
119
|
+
local record="$1" key="$2"
|
|
120
|
+
sed -n "s/^${key}=//p" "${record}" | head -n 1
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
validate_inventory_records() {
|
|
124
|
+
local record formula installation_id state
|
|
125
|
+
installation_id="$(manifest_value installation_id)"
|
|
126
|
+
for record in "${inventory_dir}"/*; do
|
|
127
|
+
[[ -e "${record}" || -L "${record}" ]] || continue
|
|
128
|
+
[[ -f "${record}" && ! -L "${record}" ]] || fail "unsafe shared inventory record"
|
|
129
|
+
formula="$(basename "${record}")"
|
|
130
|
+
valid_formula_name "${formula}" || fail "invalid formula inventory name"
|
|
131
|
+
[[ "$(record_value "${record}" managed_by || true)" == "oh-my-devpod" ]] || fail "invalid formula inventory owner"
|
|
132
|
+
[[ "$(record_value "${record}" component || true)" == "${formula}" ]] || fail "invalid formula inventory component"
|
|
133
|
+
[[ "$(record_value "${record}" kind || true)" == "brew-formula" ]] || fail "invalid formula inventory kind"
|
|
134
|
+
[[ "$(record_value "${record}" artifact || true)" == "${formula}" ]] || fail "invalid formula inventory artifact"
|
|
135
|
+
[[ "$(record_value "${record}" brew_prefix || true)" == "${prefix}" ]] || fail "invalid formula inventory prefix"
|
|
136
|
+
[[ "$(record_value "${record}" installation_id || true)" == "${installation_id}" ]] || fail "invalid formula inventory installation ID"
|
|
137
|
+
state="$(record_value "${record}" state || true)"
|
|
138
|
+
[[ "${state}" == "managed" || "${state}" == "external" ]] || fail "invalid formula inventory state"
|
|
139
|
+
done
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
is_inventory_mutation() {
|
|
143
|
+
case "${1:-}" in
|
|
144
|
+
""|--cellar|--config|--env|--prefix|--repository|--version|-v|analytics|autoremove|command|commands|config|deps|desc|doctor|formulae|help|home|info|leaves|list|log|outdated|readall|search|shellenv|tap-info|tap-pin|tap-unpin|uses)
|
|
145
|
+
return 1
|
|
146
|
+
;;
|
|
147
|
+
*)
|
|
148
|
+
return 0
|
|
149
|
+
;;
|
|
150
|
+
esac
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
snapshot_formulae() {
|
|
154
|
+
local destination="$1"
|
|
155
|
+
: > "${destination}"
|
|
156
|
+
[[ -d "${prefix}/Cellar" ]] || return 0
|
|
157
|
+
find "${prefix}/Cellar" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | LC_ALL=C sort -u > "${destination}"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
write_formula_record() {
|
|
161
|
+
local formula="$1" installation_id="$2" state="$3" provenance="$4" record temporary
|
|
162
|
+
valid_formula_name "${formula}" || return 0
|
|
163
|
+
record="${inventory_dir}/${formula}"
|
|
164
|
+
[[ ! -L "${record}" ]] || fail "unsafe formula inventory record"
|
|
165
|
+
temporary="${inventory_dir}/.${formula}.tmp.$$"
|
|
166
|
+
{
|
|
167
|
+
printf 'managed_by=oh-my-devpod\n'
|
|
168
|
+
printf 'component=%s\n' "${formula}"
|
|
169
|
+
printf 'kind=brew-formula\n'
|
|
170
|
+
printf 'artifact=%s\n' "${formula}"
|
|
171
|
+
printf 'brew_prefix=%s\n' "${prefix}"
|
|
172
|
+
printf 'installation_id=%s\n' "${installation_id}"
|
|
173
|
+
printf 'state=%s\n' "${state}"
|
|
174
|
+
printf 'provenance=%s\n' "${provenance}"
|
|
175
|
+
printf 'observed_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
176
|
+
} > "${temporary}"
|
|
177
|
+
chmod 0644 "${temporary}"
|
|
178
|
+
mv -f "${temporary}" "${record}"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
reconcile_inventory() {
|
|
182
|
+
local before="$1" installation_id formula record
|
|
183
|
+
installation_id="$(manifest_value installation_id)"
|
|
184
|
+
if [[ -d "${prefix}/Cellar" ]]; then
|
|
185
|
+
while IFS= read -r formula; do
|
|
186
|
+
[[ -n "${formula}" ]] || continue
|
|
187
|
+
record="${inventory_dir}/${formula}"
|
|
188
|
+
if [[ -f "${record}" && ! -L "${record}" ]]; then
|
|
189
|
+
continue
|
|
190
|
+
fi
|
|
191
|
+
if grep -Fqx "${formula}" "${before}" 2>/dev/null; then
|
|
192
|
+
write_formula_record "${formula}" "${installation_id}" external preexisting
|
|
193
|
+
else
|
|
194
|
+
write_formula_record "${formula}" "${installation_id}" managed brew-gateway
|
|
195
|
+
fi
|
|
196
|
+
done < <(find "${prefix}/Cellar" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | LC_ALL=C sort -u)
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
for record in "${inventory_dir}"/*; do
|
|
200
|
+
[[ -f "${record}" && ! -L "${record}" ]] || continue
|
|
201
|
+
formula="$(basename "${record}")"
|
|
202
|
+
valid_formula_name "${formula}" || continue
|
|
203
|
+
[[ -d "${prefix}/Cellar/${formula}" ]] || rm -f "${record}"
|
|
204
|
+
done
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
append_gateway_shellenv() {
|
|
208
|
+
local shell_name="${2:-}"
|
|
209
|
+
case "${shell_name}" in
|
|
210
|
+
fish)
|
|
211
|
+
printf 'set -gx PATH %q $PATH;\n' "${libexec_dir}/bin"
|
|
212
|
+
;;
|
|
213
|
+
csh|tcsh)
|
|
214
|
+
printf 'setenv PATH %q:$PATH;\n' "${libexec_dir}/bin"
|
|
215
|
+
;;
|
|
216
|
+
*)
|
|
217
|
+
printf 'export PATH=%q:"$PATH"\n' "${libexec_dir}/bin"
|
|
218
|
+
;;
|
|
219
|
+
esac
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
run_real_brew() {
|
|
223
|
+
local home="$1"
|
|
224
|
+
shift
|
|
225
|
+
local -a clean_env
|
|
226
|
+
clean_env=(HOME="${home}" USER="$(service_user)" LOGNAME="$(service_user)" PATH="/usr/local/bin:/usr/bin:/bin:${prefix}/bin")
|
|
227
|
+
if [[ "${test_mode}" == "1" ]]; then
|
|
228
|
+
clean_env+=(OHMYDEVPOD_SHARED_BREW_PREFIX="${prefix}" OMD_TEST_BREW_LOG="${OMD_TEST_BREW_LOG:-/dev/null}")
|
|
229
|
+
fi
|
|
230
|
+
if [[ "${brew_noninteractive}" == "1" ]]; then
|
|
231
|
+
clean_env+=(NONINTERACTIVE=1 HOMEBREW_NO_ASK=1 HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_INSTALL_CLEANUP=1)
|
|
232
|
+
fi
|
|
233
|
+
if [[ "${#forwarded_env[@]}" -gt 0 ]]; then
|
|
234
|
+
clean_env+=("${forwarded_env[@]}")
|
|
235
|
+
fi
|
|
236
|
+
[[ -z "${HOMEBREW_NO_AUTO_UPDATE:-}" ]] || clean_env+=(HOMEBREW_NO_AUTO_UPDATE="${HOMEBREW_NO_AUTO_UPDATE}")
|
|
237
|
+
[[ -z "${HOMEBREW_NO_ANALYTICS:-}" ]] || clean_env+=(HOMEBREW_NO_ANALYTICS="${HOMEBREW_NO_ANALYTICS}")
|
|
238
|
+
[[ -z "${HOMEBREW_NO_ENV_HINTS:-}" ]] || clean_env+=(HOMEBREW_NO_ENV_HINTS="${HOMEBREW_NO_ENV_HINTS}")
|
|
239
|
+
env -i "${clean_env[@]}" "${real_brew}" "$@"
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
run_backend() {
|
|
243
|
+
local home status mutates=0 before name value
|
|
244
|
+
if [[ "${1:-}" == "--omd-forwarded" ]]; then
|
|
245
|
+
shift
|
|
246
|
+
while [[ "${1:-}" != "--" ]]; do
|
|
247
|
+
case "${1:-}" in
|
|
248
|
+
--omd-noninteractive)
|
|
249
|
+
brew_noninteractive=1
|
|
250
|
+
shift
|
|
251
|
+
;;
|
|
252
|
+
--omd-env)
|
|
253
|
+
[[ "$#" -ge 3 ]] || fail "invalid forwarded Brew environment"
|
|
254
|
+
name="$2"
|
|
255
|
+
value="$3"
|
|
256
|
+
case "${name}" in
|
|
257
|
+
HOMEBREW_NO_AUTO_UPDATE|HOMEBREW_NO_ANALYTICS|HOMEBREW_NO_ENV_HINTS|HTTP_PROXY|HTTPS_PROXY|NO_PROXY|http_proxy|https_proxy|no_proxy) ;;
|
|
258
|
+
*) fail "unsupported forwarded Brew environment" ;;
|
|
259
|
+
esac
|
|
260
|
+
[[ "${#value}" -le 4096 ]] || fail "forwarded Brew environment is too large"
|
|
261
|
+
forwarded_env+=("${name}=${value}")
|
|
262
|
+
shift 3
|
|
263
|
+
;;
|
|
264
|
+
*)
|
|
265
|
+
fail "invalid internal gateway arguments"
|
|
266
|
+
;;
|
|
267
|
+
esac
|
|
268
|
+
done
|
|
269
|
+
[[ "${1:-}" == "--" ]] || fail "invalid internal gateway arguments"
|
|
270
|
+
shift
|
|
271
|
+
fi
|
|
272
|
+
validate_manifest
|
|
273
|
+
[[ "$(id -u)" == "$(manifest_value service_uid)" ]] || fail "backend must run as $(service_user)"
|
|
274
|
+
validate_inventory_records
|
|
275
|
+
home="$(service_home "$(service_user)")"
|
|
276
|
+
before="${state_dir}/.inventory-before.$$"
|
|
277
|
+
if is_inventory_mutation "${1:-}"; then
|
|
278
|
+
mutates=1
|
|
279
|
+
snapshot_formulae "${before}"
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
set +e
|
|
283
|
+
run_real_brew "${home}" "$@"
|
|
284
|
+
status=$?
|
|
285
|
+
set -e
|
|
286
|
+
if [[ "${status}" -eq 0 && "${1:-}" == "shellenv" ]]; then
|
|
287
|
+
append_gateway_shellenv "$@"
|
|
288
|
+
fi
|
|
289
|
+
if [[ "${mutates}" == "1" ]]; then
|
|
290
|
+
reconcile_inventory "${before}"
|
|
291
|
+
rm -f "${before}"
|
|
292
|
+
fi
|
|
293
|
+
return "${status}"
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
run_locked_backend() {
|
|
297
|
+
command -v flock >/dev/null 2>&1 || fail "flock is required"
|
|
298
|
+
exec 9<> "${lock_file}"
|
|
299
|
+
flock -x 9
|
|
300
|
+
run_backend "$@"
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
validate_manifest
|
|
304
|
+
if [[ "${1:-}" == "--service" ]]; then
|
|
305
|
+
shift
|
|
306
|
+
run_locked_backend "$@"
|
|
307
|
+
exit $?
|
|
308
|
+
fi
|
|
309
|
+
|
|
310
|
+
expected_user="$(service_user)"
|
|
311
|
+
if [[ "$(id -u)" == "$(manifest_value service_uid)" ]]; then
|
|
312
|
+
run_locked_backend "$@"
|
|
313
|
+
exit $?
|
|
314
|
+
fi
|
|
315
|
+
declare -a forwarded_args
|
|
316
|
+
forwarded_args=(--omd-forwarded)
|
|
317
|
+
if [[ "${OHMYDEVPOD_BREW_NONINTERACTIVE:-0}" == "1" ]]; then
|
|
318
|
+
forwarded_args+=(--omd-noninteractive)
|
|
319
|
+
fi
|
|
320
|
+
for name in HOMEBREW_NO_AUTO_UPDATE HOMEBREW_NO_ANALYTICS HOMEBREW_NO_ENV_HINTS HTTP_PROXY HTTPS_PROXY NO_PROXY http_proxy https_proxy no_proxy; do
|
|
321
|
+
if [[ -n "${!name:-}" ]]; then
|
|
322
|
+
forwarded_args+=(--omd-env "${name}" "${!name}")
|
|
323
|
+
fi
|
|
324
|
+
done
|
|
325
|
+
forwarded_args+=(--)
|
|
326
|
+
exec sudo -n -H -u "${expected_user}" -- "${stable_gateway}" --service "${forwarded_args[@]}" "$@"
|