dsh-scnet 0.4.2 → 0.5.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/README.md +20 -1
- package/README_CN.md +20 -1
- package/docs/installation.md +10 -0
- package/docs/installation.zh-CN.md +13 -1
- package/docs/testing.md +11 -0
- package/docs/testing.zh-CN.md +10 -0
- package/index.mjs +184 -0
- package/package.json +4 -2
- package/skills/scnet-hpc/SKILL.md +63 -105
- package/skills/scnet-hpc/VERSION +1 -0
- package/skills/scnet-hpc/clusters/_template.conf +10 -4
- package/skills/scnet-hpc/references/adding-cluster.md +6 -1
- package/skills/scnet-hpc/references/backends.md +177 -0
- package/skills/scnet-hpc/references/notebook.md +60 -0
- package/skills/scnet-hpc/references/openapi.md +140 -0
- package/skills/scnet-hpc/references/quickstart-en.md +22 -0
- package/skills/scnet-hpc/references/setup.md +4 -0
- package/skills/scnet-hpc/scripts/__pycache__/compute-probe.cpython-312.pyc +0 -0
- package/skills/scnet-hpc/scripts/__pycache__/scnet.cpython-312.pyc +0 -0
- package/skills/scnet-hpc/scripts/__pycache__/scnet_config.cpython-312.pyc +0 -0
- package/skills/scnet-hpc/scripts/__pycache__/scnet_credentials.cpython-312.pyc +0 -0
- package/skills/scnet-hpc/scripts/__pycache__/scnet_version.cpython-312.pyc +0 -0
- package/skills/scnet-hpc/scripts/scnet.py +1573 -0
- package/skills/scnet-hpc/scripts/scnet_backends/__init__.py +53 -0
- package/skills/scnet-hpc/scripts/scnet_backends/base.py +64 -0
- package/skills/scnet-hpc/scripts/scnet_backends/external.py +92 -0
- package/skills/scnet-hpc/scripts/scnet_backends/openapi.py +771 -0
- package/skills/scnet-hpc/scripts/scnet_backends/profile.py +78 -0
- package/skills/scnet-hpc/scripts/scnet_backends/ssh.py +258 -0
- package/skills/scnet-hpc/scripts/scnet_config.py +175 -0
- package/skills/scnet-hpc/scripts/scnet_credentials.py +160 -0
- package/skills/scnet-hpc/scripts/scnet_sdk/__init__.py +11 -0
- package/skills/scnet-hpc/scripts/scnet_sdk/cli.py +394 -0
- package/skills/scnet-hpc/scripts/scnet_sdk/client.py +240 -0
- package/skills/scnet-hpc/scripts/scnet_sdk/notebook.py +379 -0
- package/skills/scnet-hpc/scripts/scnet_version.py +20 -0
- package/skills/scnet-hpc/scripts/setup-ssh.sh +33 -1
- package/skills/scnet-hpc/scripts/setup.sh +471 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SCNet HPC configuration and credential-rotation panel.
|
|
3
|
+
#
|
|
4
|
+
# This is the dependency-light entrypoint for new machines. Advanced OpenAPI
|
|
5
|
+
# discovery remains available through: python3 scripts/scnet.py setup
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
+
# shellcheck disable=SC1091
|
|
12
|
+
. "$SCRIPT_DIR/_common.sh"
|
|
13
|
+
|
|
14
|
+
BACKEND=""
|
|
15
|
+
CLUSTER=""
|
|
16
|
+
ACTION=""
|
|
17
|
+
SKIP_CONNECT=no
|
|
18
|
+
SSH_KEY=""
|
|
19
|
+
SSH_USER=""
|
|
20
|
+
|
|
21
|
+
usage() {
|
|
22
|
+
sed -n '2,8p' "$0" | sed 's/^# \?//'
|
|
23
|
+
cat <<'EOF'
|
|
24
|
+
|
|
25
|
+
用法:
|
|
26
|
+
./scripts/setup.sh
|
|
27
|
+
./scripts/setup.sh new|modify|status|reset
|
|
28
|
+
./scripts/setup.sh --cluster <profile> --backend ssh
|
|
29
|
+
./scripts/setup.sh --skip-connect
|
|
30
|
+
|
|
31
|
+
action 可选:new、modify、status、reset。
|
|
32
|
+
backend 可选:ssh、openapi、both。无参数时进入交互管理面板。
|
|
33
|
+
EOF
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
while [ $# -gt 0 ]; do
|
|
37
|
+
case "$1" in
|
|
38
|
+
--cluster)
|
|
39
|
+
[ $# -ge 2 ] || die "--cluster 缺少参数"
|
|
40
|
+
CLUSTER="$2"
|
|
41
|
+
shift 2
|
|
42
|
+
;;
|
|
43
|
+
--cluster=*) CLUSTER="${1#*=}"; shift ;;
|
|
44
|
+
--backend)
|
|
45
|
+
[ $# -ge 2 ] || die "--backend 缺少参数"
|
|
46
|
+
BACKEND="$2"
|
|
47
|
+
shift 2
|
|
48
|
+
;;
|
|
49
|
+
--backend=*) BACKEND="${1#*=}"; shift ;;
|
|
50
|
+
--skip-connect) SKIP_CONNECT=yes; shift ;;
|
|
51
|
+
--ssh-key)
|
|
52
|
+
[ $# -ge 2 ] || die "--ssh-key 缺少参数"
|
|
53
|
+
SSH_KEY="$2"
|
|
54
|
+
shift 2
|
|
55
|
+
;;
|
|
56
|
+
--ssh-key=*) SSH_KEY="${1#*=}"; shift ;;
|
|
57
|
+
--username)
|
|
58
|
+
[ $# -ge 2 ] || die "--username 缺少参数"
|
|
59
|
+
SSH_USER="$2"
|
|
60
|
+
shift 2
|
|
61
|
+
;;
|
|
62
|
+
--username=*) SSH_USER="${1#*=}"; shift ;;
|
|
63
|
+
new|modify|status|reset)
|
|
64
|
+
[ -z "$ACTION" ] || die "只能指定一个 action"
|
|
65
|
+
ACTION="$1"
|
|
66
|
+
shift
|
|
67
|
+
;;
|
|
68
|
+
-h|--help) usage; exit 0 ;;
|
|
69
|
+
*) die "未知参数: $1" ;;
|
|
70
|
+
esac
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
require_tty() {
|
|
74
|
+
[ -t 0 ] || die "配置面板需要交互式终端;非交互部署请预先生成 config.json 或使用 Python/CI 专用流程"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ask() {
|
|
78
|
+
local prompt="$1"
|
|
79
|
+
local default="${2:-}"
|
|
80
|
+
local answer
|
|
81
|
+
if [ -n "$default" ]; then
|
|
82
|
+
printf '%s [%s]: ' "$prompt" "$default" >&2
|
|
83
|
+
else
|
|
84
|
+
printf '%s: ' "$prompt" >&2
|
|
85
|
+
fi
|
|
86
|
+
IFS= read -r answer
|
|
87
|
+
printf '%s' "${answer:-$default}"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
choose() {
|
|
91
|
+
local prompt="$1"
|
|
92
|
+
shift
|
|
93
|
+
local choices=("$@")
|
|
94
|
+
local index choice
|
|
95
|
+
if [ -t 0 ] && [ -t 2 ] && [ "${TERM:-dumb}" != "dumb" ]; then
|
|
96
|
+
local selected=0
|
|
97
|
+
local number=""
|
|
98
|
+
local key sequence value
|
|
99
|
+
local line_count=$(( ${#choices[@]} + 1 ))
|
|
100
|
+
printf '%s\n' "$prompt" >&2
|
|
101
|
+
printf '使用 ↑/↓ 移动,Enter 确认;也可输入编号后按 Enter;q 取消。\n' >&2
|
|
102
|
+
|
|
103
|
+
render_choices() {
|
|
104
|
+
local first="${1:-no}"
|
|
105
|
+
local render_index pointer typed
|
|
106
|
+
if [ "$first" != yes ]; then
|
|
107
|
+
printf '\033[%dA' "$line_count" >&2
|
|
108
|
+
fi
|
|
109
|
+
for render_index in "${!choices[@]}"; do
|
|
110
|
+
pointer=" "
|
|
111
|
+
[ "$render_index" -eq "$selected" ] && pointer="▶"
|
|
112
|
+
printf '\r\033[2K %s %d. %s\n' \
|
|
113
|
+
"$pointer" "$((render_index + 1))" \
|
|
114
|
+
"${choices[$render_index]}" >&2
|
|
115
|
+
done
|
|
116
|
+
typed="编号:(可直接按 Enter)"
|
|
117
|
+
[ -n "$number" ] && typed="编号:$number"
|
|
118
|
+
printf '\r\033[2K %s\n' "$typed" >&2
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
render_choices yes
|
|
122
|
+
while true; do
|
|
123
|
+
IFS= read -rsn1 key
|
|
124
|
+
case "$key" in
|
|
125
|
+
"")
|
|
126
|
+
if [ -n "$number" ]; then
|
|
127
|
+
value=$((10#$number))
|
|
128
|
+
if [ "$value" -ge 1 ] \
|
|
129
|
+
&& [ "$value" -le "${#choices[@]}" ]; then
|
|
130
|
+
selected=$((value - 1))
|
|
131
|
+
break
|
|
132
|
+
fi
|
|
133
|
+
number=""
|
|
134
|
+
render_choices
|
|
135
|
+
else
|
|
136
|
+
break
|
|
137
|
+
fi
|
|
138
|
+
;;
|
|
139
|
+
$'\033')
|
|
140
|
+
sequence=""
|
|
141
|
+
IFS= read -rsn2 -t 1 sequence || true
|
|
142
|
+
case "$sequence" in
|
|
143
|
+
"[A") selected=$(( (selected - 1 + ${#choices[@]}) % ${#choices[@]} )); number=""; render_choices ;;
|
|
144
|
+
"[B") selected=$(( (selected + 1) % ${#choices[@]} )); number=""; render_choices ;;
|
|
145
|
+
esac
|
|
146
|
+
;;
|
|
147
|
+
q|Q) die "用户取消配置" ;;
|
|
148
|
+
$'\177')
|
|
149
|
+
number="${number%?}"
|
|
150
|
+
render_choices
|
|
151
|
+
;;
|
|
152
|
+
[0-9])
|
|
153
|
+
number="${number}${key}"
|
|
154
|
+
render_choices
|
|
155
|
+
;;
|
|
156
|
+
esac
|
|
157
|
+
done
|
|
158
|
+
printf '已选择:%d. %s\n' \
|
|
159
|
+
"$((selected + 1))" "${choices[$selected]}" >&2
|
|
160
|
+
printf '%s' "${choices[$selected]}"
|
|
161
|
+
return
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
printf '%s\n' "$prompt" >&2
|
|
165
|
+
for index in "${!choices[@]}"; do
|
|
166
|
+
printf ' %d. %s\n' "$((index + 1))" "${choices[$index]}" >&2
|
|
167
|
+
done
|
|
168
|
+
printf '请输入 1-%d 的数字并按 Enter;直接按 Enter 使用默认项 1。\n' \
|
|
169
|
+
"${#choices[@]}" >&2
|
|
170
|
+
printf '当前终端不支持交互导航,已使用数字输入模式。\n' >&2
|
|
171
|
+
local selected
|
|
172
|
+
selected=$(ask "选择" "1")
|
|
173
|
+
[[ "$selected" =~ ^[0-9]+$ ]] || die "选择必须是数字"
|
|
174
|
+
[ "$selected" -ge 1 ] && [ "$selected" -le "${#choices[@]}" ] \
|
|
175
|
+
|| die "选择超出范围"
|
|
176
|
+
choice="${choices[$((selected - 1))]}"
|
|
177
|
+
printf '%s' "$choice"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
validate_identifier() {
|
|
181
|
+
local name="$1"
|
|
182
|
+
local value="$2"
|
|
183
|
+
[[ "$value" =~ ^[A-Za-z0-9._:-]+$ ]] || die "$name 包含不安全字符"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
json_string() {
|
|
187
|
+
local value="$1"
|
|
188
|
+
value="${value//\\/\\\\}"
|
|
189
|
+
value="${value//\"/\\\"}"
|
|
190
|
+
value="${value//$'\n'/\\n}"
|
|
191
|
+
value="${value//$'\r'/\\r}"
|
|
192
|
+
printf '"%s"' "$value"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
write_config() {
|
|
196
|
+
local config_root="${XDG_CONFIG_HOME:-$HOME/.config}/scnet-hpc"
|
|
197
|
+
local config_path="$config_root/config.json"
|
|
198
|
+
mkdir -p "$config_root"
|
|
199
|
+
chmod 700 "$config_root"
|
|
200
|
+
|
|
201
|
+
local temporary
|
|
202
|
+
temporary=$(mktemp "$config_root/.config.XXXXXX")
|
|
203
|
+
chmod 600 "$temporary"
|
|
204
|
+
{
|
|
205
|
+
printf '{\n "version": 1,\n "cluster": '
|
|
206
|
+
json_string "$CLUSTER"
|
|
207
|
+
printf ',\n "default_backend": '
|
|
208
|
+
json_string "$DEFAULT_BACKEND"
|
|
209
|
+
printf '\n}\n'
|
|
210
|
+
} >"$temporary"
|
|
211
|
+
mv "$temporary" "$config_path"
|
|
212
|
+
printf '%s\n' "$config_path"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
write_ssh_profile() {
|
|
216
|
+
[ -n "$SSH_USER" ] || return 0
|
|
217
|
+
local config_root="${XDG_CONFIG_HOME:-$HOME/.config}/scnet-hpc"
|
|
218
|
+
local ssh_root="$config_root/ssh"
|
|
219
|
+
local profile_path="$ssh_root/$CLUSTER.json"
|
|
220
|
+
local expiry=""
|
|
221
|
+
local key_base=""
|
|
222
|
+
local installed_key="$HOME/.ssh/id_rsa_$CLUSTER_ID"
|
|
223
|
+
local fingerprint=""
|
|
224
|
+
local updated_at=""
|
|
225
|
+
local existing_key_path=""
|
|
226
|
+
local existing_source=""
|
|
227
|
+
local existing_expiry=""
|
|
228
|
+
local existing_fingerprint=""
|
|
229
|
+
local existing_updated=""
|
|
230
|
+
mkdir -p "$ssh_root"
|
|
231
|
+
chmod 700 "$ssh_root"
|
|
232
|
+
existing_key_path=$(json_string_value "$profile_path" key_path)
|
|
233
|
+
existing_source=$(json_string_value "$profile_path" key_source_name)
|
|
234
|
+
existing_expiry=$(json_string_value "$profile_path" key_expires_at)
|
|
235
|
+
existing_fingerprint=$(json_string_value "$profile_path" key_fingerprint)
|
|
236
|
+
existing_updated=$(json_string_value "$profile_path" updated_at)
|
|
237
|
+
if [ -n "$SSH_KEY" ]; then
|
|
238
|
+
key_base=$(basename "$SSH_KEY")
|
|
239
|
+
expiry=$(printf '%s' "$key_base" | sed -nE \
|
|
240
|
+
's/.*RsaKeyExpireTime[_-]([0-9]{4}-[0-9]{2}-[0-9]{2}).*/\1/p')
|
|
241
|
+
updated_at=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
|
242
|
+
if [ -f "$installed_key" ]; then
|
|
243
|
+
fingerprint=$(ssh-keygen -lf "$installed_key" 2>/dev/null \
|
|
244
|
+
| awk '{print $2; exit}')
|
|
245
|
+
fi
|
|
246
|
+
else
|
|
247
|
+
installed_key="$existing_key_path"
|
|
248
|
+
key_base="$existing_source"
|
|
249
|
+
expiry="$existing_expiry"
|
|
250
|
+
fingerprint="$existing_fingerprint"
|
|
251
|
+
updated_at="$existing_updated"
|
|
252
|
+
fi
|
|
253
|
+
local temporary
|
|
254
|
+
temporary=$(mktemp "$ssh_root/.$CLUSTER.XXXXXX")
|
|
255
|
+
chmod 600 "$temporary"
|
|
256
|
+
{
|
|
257
|
+
printf '{\n "username": '
|
|
258
|
+
json_string "$SSH_USER"
|
|
259
|
+
if [ -n "$installed_key" ]; then
|
|
260
|
+
printf ',\n "key_path": '
|
|
261
|
+
json_string "$installed_key"
|
|
262
|
+
fi
|
|
263
|
+
if [ -n "$key_base" ]; then
|
|
264
|
+
printf ',\n "key_source_name": '
|
|
265
|
+
json_string "$key_base"
|
|
266
|
+
fi
|
|
267
|
+
if [ -n "$updated_at" ]; then
|
|
268
|
+
printf ',\n "updated_at": '
|
|
269
|
+
json_string "$updated_at"
|
|
270
|
+
fi
|
|
271
|
+
if [ -n "$expiry" ]; then
|
|
272
|
+
printf ',\n "key_expires_at": '
|
|
273
|
+
json_string "$expiry"
|
|
274
|
+
fi
|
|
275
|
+
if [ -n "$fingerprint" ]; then
|
|
276
|
+
printf ',\n "key_fingerprint": '
|
|
277
|
+
json_string "$fingerprint"
|
|
278
|
+
fi
|
|
279
|
+
printf '\n}\n'
|
|
280
|
+
} >"$temporary"
|
|
281
|
+
mv "$temporary" "$profile_path"
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
json_string_value() {
|
|
285
|
+
local path="$1"
|
|
286
|
+
local key="$2"
|
|
287
|
+
[ -f "$path" ] || return 0
|
|
288
|
+
sed -nE "s/.*\"${key}\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\\1/p" \
|
|
289
|
+
"$path" | head -1
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
detect_ssh_user() {
|
|
293
|
+
local key_path="${1:-}"
|
|
294
|
+
local config_root="${XDG_CONFIG_HOME:-$HOME/.config}/scnet-hpc"
|
|
295
|
+
local saved
|
|
296
|
+
saved=$(json_string_value "$config_root/ssh/$CLUSTER.json" username)
|
|
297
|
+
[ -n "$saved" ] && { printf '%s' "$saved"; return; }
|
|
298
|
+
|
|
299
|
+
if [ -n "$key_path" ] && [ -n "${KEY_NAME_MARKER:-}" ] \
|
|
300
|
+
&& [[ "$KEY_NAME_MARKER" != *"<"* ]]; then
|
|
301
|
+
local base
|
|
302
|
+
base=$(basename "$key_path")
|
|
303
|
+
case "$base" in
|
|
304
|
+
*"$KEY_NAME_MARKER"*)
|
|
305
|
+
printf '%s' "${base%%"$KEY_NAME_MARKER"*}"
|
|
306
|
+
return
|
|
307
|
+
;;
|
|
308
|
+
esac
|
|
309
|
+
fi
|
|
310
|
+
if [ -n "$key_path" ] && [ -n "${SSH_HOST:-}" ]; then
|
|
311
|
+
local host_marker="_${SSH_HOST}_"
|
|
312
|
+
local base
|
|
313
|
+
base=$(basename "$key_path")
|
|
314
|
+
case "$base" in
|
|
315
|
+
*"$host_marker"*)
|
|
316
|
+
printf '%s' "${base%%"$host_marker"*}"
|
|
317
|
+
return
|
|
318
|
+
;;
|
|
319
|
+
esac
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
local resolved_host resolved_user
|
|
323
|
+
resolved_host=$(ssh -G "$CLUSTER_ID" 2>/dev/null \
|
|
324
|
+
| awk '/^hostname /{print $2; exit}')
|
|
325
|
+
resolved_user=$(ssh -G "$CLUSTER_ID" 2>/dev/null \
|
|
326
|
+
| awk '/^user /{print $2; exit}')
|
|
327
|
+
if [ "$resolved_host" = "${SSH_HOST:-}" ]; then
|
|
328
|
+
printf '%s' "$resolved_user"
|
|
329
|
+
fi
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
require_tty
|
|
333
|
+
|
|
334
|
+
printf '\nSCNet HPC 配置/维护面板(Bash)\n' >&2
|
|
335
|
+
printf '==================================\n' >&2
|
|
336
|
+
printf '此面板只保存非敏感选择;AK/SK/token 不会写入配置文件。\n\n' >&2
|
|
337
|
+
case "$(uname -s)" in
|
|
338
|
+
Darwin)
|
|
339
|
+
printf '%s\n' \
|
|
340
|
+
"macOS:在 Terminal 或 iTerm2 中使用 ↑/↓ 和 Enter,也可输入编号;Ctrl+C 可取消。" \
|
|
341
|
+
"敏感输入不会回显;OpenAPI 凭据可保存到 macOS Keychain。" >&2
|
|
342
|
+
;;
|
|
343
|
+
Linux)
|
|
344
|
+
if [ -r /etc/os-release ] \
|
|
345
|
+
&& grep -Eq '^ID=(ubuntu|debian)$' /etc/os-release; then
|
|
346
|
+
printf '%s\n' \
|
|
347
|
+
"Ubuntu/Debian:在 Terminal 中使用 ↑/↓ 和 Enter,也可输入编号;Ctrl+C 可取消。" \
|
|
348
|
+
"如需安全保存 AK/SK:sudo apt install libsecret-tools" >&2
|
|
349
|
+
else
|
|
350
|
+
printf '%s\n' "Linux:使用 ↑/↓ 和 Enter,也可输入编号;Ctrl+C 可取消。" >&2
|
|
351
|
+
fi
|
|
352
|
+
;;
|
|
353
|
+
esac
|
|
354
|
+
printf '\n' >&2
|
|
355
|
+
|
|
356
|
+
available=$(list_clusters)
|
|
357
|
+
[ -n "$available" ] || die "clusters/ 下没有 profile"
|
|
358
|
+
if [ -z "$ACTION" ]; then
|
|
359
|
+
action_label=$(choose "选择配置操作:" \
|
|
360
|
+
"查看当前状态(status)" \
|
|
361
|
+
"新增连接(new)" \
|
|
362
|
+
"修改现有配置(modify)" \
|
|
363
|
+
"重置配置(reset)")
|
|
364
|
+
case "$action_label" in
|
|
365
|
+
*status*) ACTION=status ;;
|
|
366
|
+
*new*) ACTION=new ;;
|
|
367
|
+
*modify*) ACTION=modify ;;
|
|
368
|
+
*reset*) ACTION=reset ;;
|
|
369
|
+
esac
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
if [ "$ACTION" = status ] || [ "$ACTION" = reset ]; then
|
|
373
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
374
|
+
exec python3 "$SCRIPT_DIR/scnet.py" setup "$ACTION"
|
|
375
|
+
fi
|
|
376
|
+
[ "$ACTION" = status ] || die "reset 需要 Python 3 以执行安全的分范围重置"
|
|
377
|
+
config_root="${XDG_CONFIG_HOME:-$HOME/.config}/scnet-hpc"
|
|
378
|
+
printf '配置目录:%s\n' "$config_root"
|
|
379
|
+
find "$config_root" -maxdepth 2 -type f -print 2>/dev/null || true
|
|
380
|
+
exit 0
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
if [ -z "$BACKEND" ]; then
|
|
384
|
+
BACKEND=$(choose "选择要管理的 backend:" \
|
|
385
|
+
"ssh(区域级用户名和私钥)" \
|
|
386
|
+
"openapi(平台级 AK/SK 和多区域)" \
|
|
387
|
+
"both(同时管理)")
|
|
388
|
+
case "$BACKEND" in
|
|
389
|
+
ssh*) BACKEND=ssh ;;
|
|
390
|
+
openapi*) BACKEND=openapi ;;
|
|
391
|
+
both*) BACKEND=both ;;
|
|
392
|
+
esac
|
|
393
|
+
fi
|
|
394
|
+
case "$BACKEND" in
|
|
395
|
+
ssh|openapi|both) ;;
|
|
396
|
+
*) die "backend 必须是 ssh、openapi 或 both" ;;
|
|
397
|
+
esac
|
|
398
|
+
|
|
399
|
+
if { [ "$BACKEND" = ssh ] || [ "$BACKEND" = both ]; } \
|
|
400
|
+
&& [ -z "$CLUSTER" ]; then
|
|
401
|
+
cluster_choices=()
|
|
402
|
+
while IFS= read -r cluster_choice; do
|
|
403
|
+
[ -n "$cluster_choice" ] || continue
|
|
404
|
+
metadata="${XDG_CONFIG_HOME:-$HOME/.config}/scnet-hpc/ssh/$cluster_choice.json"
|
|
405
|
+
if [ "$ACTION" = new ] && [ ! -f "$metadata" ]; then
|
|
406
|
+
cluster_choices+=("$cluster_choice")
|
|
407
|
+
elif [ "$ACTION" = modify ] && [ -f "$metadata" ]; then
|
|
408
|
+
cluster_choices+=("$cluster_choice")
|
|
409
|
+
fi
|
|
410
|
+
done <<EOF
|
|
411
|
+
$available
|
|
412
|
+
EOF
|
|
413
|
+
[ "${#cluster_choices[@]}" -gt 0 ] || {
|
|
414
|
+
if [ "$ACTION" = new ]; then
|
|
415
|
+
die "所有 SSH profiles 已配置;请使用 setup.sh modify"
|
|
416
|
+
fi
|
|
417
|
+
die "没有已配置的 SSH profile;请使用 setup.sh new"
|
|
418
|
+
}
|
|
419
|
+
CLUSTER=$(choose "选择默认集群 profile:" "${cluster_choices[@]}")
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
if [ "$BACKEND" = ssh ] || [ "$BACKEND" = both ]; then
|
|
423
|
+
[[ "$CLUSTER" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] \
|
|
424
|
+
|| die "集群 profile 名称包含不安全字符"
|
|
425
|
+
export SCNET_HPC_LIVE_NODE_COUNT=no
|
|
426
|
+
load_cluster "$CLUSTER"
|
|
427
|
+
unset SCNET_HPC_LIVE_NODE_COUNT
|
|
428
|
+
fi
|
|
429
|
+
|
|
430
|
+
DEFAULT_BACKEND="$BACKEND"
|
|
431
|
+
if [ "$BACKEND" = both ]; then
|
|
432
|
+
DEFAULT_BACKEND=$(choose "选择默认 backend:" "ssh" "openapi")
|
|
433
|
+
fi
|
|
434
|
+
|
|
435
|
+
if [ "$BACKEND" = ssh ] || [ "$BACKEND" = both ]; then
|
|
436
|
+
if [ "$SKIP_CONNECT" = no ]; then
|
|
437
|
+
SSH_KEY="${SSH_KEY:-$(ask "私钥文件路径")}"
|
|
438
|
+
SSH_KEY="${SSH_KEY/#\~/$HOME}"
|
|
439
|
+
[ -f "$SSH_KEY" ] || die "找不到私钥文件: $SSH_KEY"
|
|
440
|
+
fi
|
|
441
|
+
detected_user=$(detect_ssh_user "$SSH_KEY")
|
|
442
|
+
SSH_USER="${SSH_USER:-$(ask "远端用户名(已自动探测,可直接回车)" "$detected_user")}"
|
|
443
|
+
validate_identifier "远端用户名" "$SSH_USER"
|
|
444
|
+
[ -n "$SSH_USER" ] || die "无法自动探测 SSH 用户名,请手工输入"
|
|
445
|
+
if [ "$SKIP_CONNECT" = no ]; then
|
|
446
|
+
"$SCRIPT_DIR/setup-ssh.sh" --cluster "$CLUSTER" "$SSH_KEY" "$SSH_USER"
|
|
447
|
+
else
|
|
448
|
+
printf '已跳过 SSH 私钥安装和连接测试。\n' >&2
|
|
449
|
+
fi
|
|
450
|
+
fi
|
|
451
|
+
|
|
452
|
+
if [ "$BACKEND" = openapi ] || [ "$BACKEND" = both ]; then
|
|
453
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
454
|
+
printf '%s\n' \
|
|
455
|
+
"OpenAPI 使用一组平台 AK/SK 自动发现全部授权区域。" >&2
|
|
456
|
+
openapi_args=(setup "$ACTION" --mode openapi)
|
|
457
|
+
[ "$SKIP_CONNECT" = yes ] && openapi_args+=(--skip-connect)
|
|
458
|
+
python3 "$SCRIPT_DIR/scnet.py" "${openapi_args[@]}"
|
|
459
|
+
else
|
|
460
|
+
printf '%s\n' \
|
|
461
|
+
"OpenAPI backend 需要 Python 3;SSH 配置不受影响。" \
|
|
462
|
+
"安装 Python 3 后运行:python3 scripts/scnet.py setup --mode openapi" >&2
|
|
463
|
+
fi
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
write_ssh_profile
|
|
467
|
+
if [ "$BACKEND" = ssh ] || [ "$BACKEND" = both ]; then
|
|
468
|
+
config_path=$(write_config)
|
|
469
|
+
printf '\n已保存配置:%s\n' "$config_path" >&2
|
|
470
|
+
fi
|
|
471
|
+
printf '下一步:%s/scripts/scnet.py doctor\n' "$REPO_ROOT" >&2
|