mjolnir-cli 0.2.0__py3-none-any.whl
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.
- mjolnir_cli/__init__.py +8 -0
- mjolnir_cli/__main__.py +4 -0
- mjolnir_cli/accuracy.py +333 -0
- mjolnir_cli/autoresearch.py +539 -0
- mjolnir_cli/benchy.py +475 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4/config.yaml +78 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_DFLASH2/config.yaml +103 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_DSPARK/config.yaml +102 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_FA2/config.yaml +84 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_FA4hd256/config.yaml +104 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR/config.yaml +86 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3/config.yaml +93 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_DFLASH2/config.yaml +123 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_DSPARK/config.yaml +124 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8/.env +2 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8/config.yaml +98 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT32K/.env +4 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT32K/config.yaml +98 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/.env +4 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/config.yaml +102 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_VT32K/.env +3 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_VT32K/config.yaml +98 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/draft_vocab.json +1 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/kv_scales.json +35197 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8_chat_template.jinja +456 -0
- mjolnir_cli/bundled/docker/litellm/docker-compose.yaml +113 -0
- mjolnir_cli/bundled/docker/litellm/litellm.env.example +51 -0
- mjolnir_cli/bundled/docker/litellm/litellm_config.template.example.yaml +192 -0
- mjolnir_cli/bundled/docker/perses/README.md +89 -0
- mjolnir_cli/bundled/docker/perses/dashboards/global-datasource.yaml +17 -0
- mjolnir_cli/bundled/docker/perses/dashboards/performance_statistics.yaml +771 -0
- mjolnir_cli/bundled/docker/perses/dashboards/project.yaml +13 -0
- mjolnir_cli/bundled/docker/perses/dashboards/query_statistics.yaml +399 -0
- mjolnir_cli/bundled/docker/perses/docker-compose.yaml +51 -0
- mjolnir_cli/bundled/docker/perses/perses.yml +16 -0
- mjolnir_cli/bundled/docker/perses/prometheus.template.yaml +20 -0
- mjolnir_cli/bundled/scripts/hw/setup-thor.sh +1287 -0
- mjolnir_cli/cachectl.py +146 -0
- mjolnir_cli/charts.py +1687 -0
- mjolnir_cli/cli.py +1480 -0
- mjolnir_cli/clidoc.py +170 -0
- mjolnir_cli/config.py +346 -0
- mjolnir_cli/dockerctl.py +533 -0
- mjolnir_cli/drivers/fa2_driver.py +89 -0
- mjolnir_cli/drivers/fi_driver.py +134 -0
- mjolnir_cli/e2e.py +302 -0
- mjolnir_cli/fonts/InterDisplay-Bold.ttf +0 -0
- mjolnir_cli/fonts/InterDisplay-Medium.ttf +0 -0
- mjolnir_cli/fonts/InterDisplay-Regular.ttf +0 -0
- mjolnir_cli/fonts/InterDisplay-SemiBold.ttf +0 -0
- mjolnir_cli/fonts/OFL.txt +92 -0
- mjolnir_cli/gate.py +233 -0
- mjolnir_cli/history.py +159 -0
- mjolnir_cli/interactive.py +383 -0
- mjolnir_cli/legs.py +109 -0
- mjolnir_cli/litellm.py +254 -0
- mjolnir_cli/perses.py +214 -0
- mjolnir_cli/picker.py +70 -0
- mjolnir_cli/readme.py +1020 -0
- mjolnir_cli/stack.py +340 -0
- mjolnir_cli/tasks.py +122 -0
- mjolnir_cli/theme.py +281 -0
- mjolnir_cli/thor.py +108 -0
- mjolnir_cli/verify/__init__.py +2 -0
- mjolnir_cli/verify/cli.py +816 -0
- mjolnir_cli/verify/docker.py +334 -0
- mjolnir_cli/verify/kvcache.py +180 -0
- mjolnir_cli/verify/legs.py +134 -0
- mjolnir_cli/verify/ncu.py +141 -0
- mjolnir_cli/verify/profile.py +312 -0
- mjolnir_cli/verify/report.py +177 -0
- mjolnir_cli/verify/results.py +157 -0
- mjolnir_cli/verify/spec.py +279 -0
- mjolnir_cli/verify/stages.py +817 -0
- mjolnir_cli-0.2.0.dist-info/METADATA +87 -0
- mjolnir_cli-0.2.0.dist-info/RECORD +80 -0
- mjolnir_cli-0.2.0.dist-info/WHEEL +4 -0
- mjolnir_cli-0.2.0.dist-info/entry_points.txt +2 -0
- mjolnir_cli-0.2.0.dist-info/licenses/LICENSE +202 -0
- mjolnir_cli-0.2.0.dist-info/licenses/NOTICE +64 -0
|
@@ -0,0 +1,1287 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# setup-thor.sh — Jetson AGX Thor (sm_110a) host pre-configuration.
|
|
4
|
+
#
|
|
5
|
+
# Ported from the ~/thor repo's scripts/setup.sh + utils.sh, made
|
|
6
|
+
# self-contained and Thor-only (SoM=thor, 128 GB, NVIDIA carrier — the
|
|
7
|
+
# constants setup.sh hardcoded): no external ROOT/scripts dependency, no
|
|
8
|
+
# CI branches. This is for a physical box and needs sudo. Every step is
|
|
9
|
+
# idempotent — re-running where already configured is a no-op.
|
|
10
|
+
#
|
|
11
|
+
# The fan step also installs the two tuned performance fan profiles
|
|
12
|
+
# (`recommended` + `max`, per-profile gated, with the .bck rollback point)
|
|
13
|
+
# and selects the default — see the FAN section below.
|
|
14
|
+
#
|
|
15
|
+
# Preferred entry point: `mjolnir hw setup` (maps CLI flags to the options
|
|
16
|
+
# below):
|
|
17
|
+
#
|
|
18
|
+
# -y, --yes don't prompt before starting
|
|
19
|
+
# --no-upgrade skip apt update + full-upgrade (default: run)
|
|
20
|
+
# --keep-gui don't switch the boot target to multi-user
|
|
21
|
+
# --fan-profile <p> recommended (default) | max | cool | quiet
|
|
22
|
+
# --swap-size <GB> swap file size (default: 32)
|
|
23
|
+
# --skip <a,b,c> skip steps: gui,upgrade,pip,docker,jtop,
|
|
24
|
+
# memory,fan,host,clocks,power
|
|
25
|
+
# --reboot reboot at the end if a step required one
|
|
26
|
+
# --dry-run print the plan, change nothing
|
|
27
|
+
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
|
|
30
|
+
# ── Thor constants (from setup.sh's hardcoded block) ─────────────────────────
|
|
31
|
+
SOC_TYPE="thor"
|
|
32
|
+
SOC_MEMORY=128
|
|
33
|
+
IS_NVIDIA_CARRIER=true
|
|
34
|
+
|
|
35
|
+
REBOOT_REQUIRED=0
|
|
36
|
+
|
|
37
|
+
ASSUME_YES=0
|
|
38
|
+
UPGRADE=1
|
|
39
|
+
KEEP_GUI=0
|
|
40
|
+
FAN_PROFILE="recommended"
|
|
41
|
+
SWAP_SIZE=32
|
|
42
|
+
SKIP=""
|
|
43
|
+
DO_REBOOT=0
|
|
44
|
+
DRY_RUN=0
|
|
45
|
+
|
|
46
|
+
usage() {
|
|
47
|
+
grep '^# ' "$0" | sed 's/^# //'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
while [[ $# -gt 0 ]]; do
|
|
51
|
+
case "$1" in
|
|
52
|
+
-y|--yes) ASSUME_YES=1; shift ;;
|
|
53
|
+
--no-upgrade) UPGRADE=0; shift ;;
|
|
54
|
+
--keep-gui) KEEP_GUI=1; shift ;;
|
|
55
|
+
--fan-profile) FAN_PROFILE="${2:?}"; shift 2 ;;
|
|
56
|
+
--swap-size) SWAP_SIZE="${2:?}"; shift 2 ;;
|
|
57
|
+
--skip) SKIP="${2:?}"; shift 2 ;;
|
|
58
|
+
--reboot) DO_REBOOT=1; shift ;;
|
|
59
|
+
--dry-run) DRY_RUN=1; shift ;;
|
|
60
|
+
-h|--help) usage; exit 0 ;;
|
|
61
|
+
*) echo "❌ unknown option: $1" >&2; usage >&2; exit 2 ;;
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
# ================================================================================
|
|
66
|
+
# UTILITIES
|
|
67
|
+
# ================================================================================
|
|
68
|
+
|
|
69
|
+
is_true() {
|
|
70
|
+
[[ "${1,,}" =~ ^(true|1|yes|enabled|y)$ ]]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
file_exists() {
|
|
74
|
+
local file=${1:-}
|
|
75
|
+
[[ -n "$file" && -f "$file" ]]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
is_command_available() {
|
|
79
|
+
local command=$1
|
|
80
|
+
if command -v "${command}" &> /dev/null; then
|
|
81
|
+
return 0
|
|
82
|
+
else
|
|
83
|
+
return 1
|
|
84
|
+
fi
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
is_user_in_group() {
|
|
88
|
+
getent group "$1" | grep -qw "$USER"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Convert size string to bytes (e.g., "4G" to bytes)
|
|
92
|
+
convert_to_bytes() {
|
|
93
|
+
local size=$1
|
|
94
|
+
local value=${size%[GM]}
|
|
95
|
+
local unit=${size#$value}
|
|
96
|
+
|
|
97
|
+
case $unit in
|
|
98
|
+
G)
|
|
99
|
+
echo $((value * 1024 * 1024 * 1024))
|
|
100
|
+
;;
|
|
101
|
+
M)
|
|
102
|
+
echo $((value * 1024 * 1024))
|
|
103
|
+
;;
|
|
104
|
+
*)
|
|
105
|
+
echo "Error: Invalid size unit. Use M for MB or G for GB"
|
|
106
|
+
exit 1
|
|
107
|
+
;;
|
|
108
|
+
esac
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
is_skipped() {
|
|
112
|
+
case ",${SKIP}," in *",${1},"*) return 0 ;; esac
|
|
113
|
+
return 1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
ask_yes_no() {
|
|
117
|
+
while true; do
|
|
118
|
+
read -p "❔ ${1} (y/n): " yn
|
|
119
|
+
case $yn in
|
|
120
|
+
[Yy]* ) return 0;;
|
|
121
|
+
[Nn]* ) return 1;;
|
|
122
|
+
* ) echo "Please answer Y or y for YES or N or n for NO.";;
|
|
123
|
+
esac
|
|
124
|
+
done
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
ask_should_proceed() {
|
|
128
|
+
echo
|
|
129
|
+
while true; do
|
|
130
|
+
read -p "❔ Do you want to continue? (Press ENTER for Yes, 'q' to Quit) " input
|
|
131
|
+
if [ -z "$input" ] || is_true "$input"; then
|
|
132
|
+
echo "✅ Continuing..."
|
|
133
|
+
echo
|
|
134
|
+
return 0
|
|
135
|
+
elif [[ "$input" == "q" ]]; then
|
|
136
|
+
echo "❌ Exiting..."
|
|
137
|
+
exit 0
|
|
138
|
+
else
|
|
139
|
+
echo "❌ Invalid input: ${input}. Please press ENTER to continue or 'q' to quit."
|
|
140
|
+
fi
|
|
141
|
+
done
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
check_systemd() {
|
|
145
|
+
if ! is_command_available systemctl; then
|
|
146
|
+
echo "Error: This script requires systemd"
|
|
147
|
+
exit 1
|
|
148
|
+
fi
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
systemctl_stop_service() {
|
|
152
|
+
check_systemd
|
|
153
|
+
sudo systemctl stop "$1"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
systemctl_enable_service() {
|
|
157
|
+
check_systemd
|
|
158
|
+
sudo systemctl enable --now "$1"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
systemctl_start_service() {
|
|
162
|
+
check_systemd
|
|
163
|
+
sudo systemctl start "$1"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
systemctl_restart_service() {
|
|
167
|
+
check_systemd
|
|
168
|
+
sudo systemctl restart "$1"
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
systemctl_service_exists() {
|
|
172
|
+
check_systemd
|
|
173
|
+
|
|
174
|
+
# status exit code 4 = unit not found
|
|
175
|
+
systemctl status "$1" &>/dev/null
|
|
176
|
+
[[ $? -ne 4 ]]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# Return 0 if the unit is active (running), 1 otherwise
|
|
180
|
+
systemctl_service_is_active() {
|
|
181
|
+
check_systemd
|
|
182
|
+
systemctl is-active --quiet "$1"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Return 0 if the unit is enabled, 1 otherwise
|
|
186
|
+
systemctl_service_is_enabled() {
|
|
187
|
+
check_systemd
|
|
188
|
+
systemctl is-enabled --quiet "$1"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
systemctl_disable_service() {
|
|
192
|
+
if ! systemctl_service_exists "$1"; then
|
|
193
|
+
return 0
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
if systemctl_service_is_active "$1"; then
|
|
197
|
+
echo "ℹ️ Stopping the $1 service..."
|
|
198
|
+
systemctl_stop_service "$1"
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
if systemctl_service_is_enabled "$1"; then
|
|
202
|
+
echo "ℹ️ Disabling the $1 service..."
|
|
203
|
+
sudo systemctl disable "$1"
|
|
204
|
+
fi
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
ensure_installed() {
|
|
208
|
+
# Usage: ensure_installed <command> <package>
|
|
209
|
+
local cmd="$1" pkg="${2:-$1}"
|
|
210
|
+
|
|
211
|
+
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
212
|
+
echo "📦 Installing $pkg..."
|
|
213
|
+
sudo apt update
|
|
214
|
+
sudo apt install -y "$pkg"
|
|
215
|
+
else
|
|
216
|
+
echo "✅ $pkg is already installed at $(command -v "$cmd")"
|
|
217
|
+
fi
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
l4t_root_device() {
|
|
221
|
+
local root_device
|
|
222
|
+
root_device=$(findmnt -n -o SOURCE /)
|
|
223
|
+
|
|
224
|
+
if [[ $root_device == *"nvme"* ]]; then
|
|
225
|
+
echo "nvme"
|
|
226
|
+
elif [[ $root_device == *"mmcblk"* ]]; then
|
|
227
|
+
# eMMC devices have mmcblk0boot0 and mmcblk0boot1 partitions
|
|
228
|
+
if [[ -b /dev/mmcblk0boot0 ]] && [[ -b /dev/mmcblk0boot1 ]]; then
|
|
229
|
+
echo "emmc"
|
|
230
|
+
else
|
|
231
|
+
echo "sdcard"
|
|
232
|
+
fi
|
|
233
|
+
elif [[ $root_device == *"/dev/sd"* ]]; then
|
|
234
|
+
echo "usb_sata"
|
|
235
|
+
else
|
|
236
|
+
echo "unknown"
|
|
237
|
+
fi
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
is_l4t_installed_on_sdcard() { [[ "$(l4t_root_device)" == "sdcard" ]]; }
|
|
241
|
+
|
|
242
|
+
# ================================================================================
|
|
243
|
+
# SYSTEM / HOST
|
|
244
|
+
# ================================================================================
|
|
245
|
+
|
|
246
|
+
upgrade_system() {
|
|
247
|
+
if is_true "$UPGRADE"; then
|
|
248
|
+
echo "📥 Updating package lists..."
|
|
249
|
+
sudo apt update &> /dev/null
|
|
250
|
+
|
|
251
|
+
echo "📥 Upgrading system..."
|
|
252
|
+
sudo apt full-upgrade -y &> /dev/null
|
|
253
|
+
echo "✅ System upgraded."
|
|
254
|
+
|
|
255
|
+
ensure_installed nvidia-jetpack &> /dev/null
|
|
256
|
+
else
|
|
257
|
+
echo "ℹ️ Skipping system upgrade (--no-upgrade)."
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
ensure_installed curl curl
|
|
261
|
+
ensure_installed wget wget
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
# ================================================================================
|
|
265
|
+
# INSTALLERS
|
|
266
|
+
# ================================================================================
|
|
267
|
+
|
|
268
|
+
install_pip() {
|
|
269
|
+
# If pip already on PATH, we're done
|
|
270
|
+
if is_command_available pip; then
|
|
271
|
+
echo "✅ pip is already installed."
|
|
272
|
+
return
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
echo "ℹ️ Installing pip and supporting tools..."
|
|
276
|
+
if ! sudo apt install -y python3-pip python3-setuptools python3-pkg-resources; then
|
|
277
|
+
echo "⚠️ Attempting to fix broken packages..."
|
|
278
|
+
sudo apt --fix-broken install -y
|
|
279
|
+
if ! sudo apt install -y python3-pip python3-setuptools python3-pkg-resources; then
|
|
280
|
+
echo "🚨 Falling back to get-pip.py bootstrap..."
|
|
281
|
+
|
|
282
|
+
ensure_installed curl curl
|
|
283
|
+
|
|
284
|
+
curl -sSL https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py
|
|
285
|
+
sudo python3 /tmp/get-pip.py
|
|
286
|
+
rm -f /tmp/get-pip.py
|
|
287
|
+
fi
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
echo "ℹ️ Checking for pip3 in standard paths..."
|
|
291
|
+
if ! is_command_available pip3; then
|
|
292
|
+
echo "⚠️ pip3 not found in PATH — checking ~/.local/bin..."
|
|
293
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
294
|
+
if ! is_command_available pip3; then
|
|
295
|
+
echo "❌ pip3 still not found. Check your installation manually."
|
|
296
|
+
return 1
|
|
297
|
+
else
|
|
298
|
+
echo "✅ Found pip3 in ~/.local/bin — added to PATH for this session."
|
|
299
|
+
fi
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
echo "ℹ️ Verifying pip3 version..."
|
|
303
|
+
pip3 --version
|
|
304
|
+
|
|
305
|
+
echo "ℹ️ Upgrading pip and tools..."
|
|
306
|
+
python3 -m pip install --upgrade --user pip setuptools wheel
|
|
307
|
+
|
|
308
|
+
echo "ℹ️ Ensuring ~/.local/bin is in PATH..."
|
|
309
|
+
if ! echo "$PATH" | grep -q "$HOME/.local/bin"; then
|
|
310
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
|
|
311
|
+
echo '✅ Added ~/.local/bin to ~/.bashrc for future sessions.'
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
echo "✅ pip3 installation complete and usable in this session."
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
install_jtop() {
|
|
318
|
+
# https://github.com/rbonghi/jetson_stats
|
|
319
|
+
if ! command -v jtop &> /dev/null; then
|
|
320
|
+
echo "📦 Installing jtop..."
|
|
321
|
+
pip3 install -U "setuptools<71.0.0"
|
|
322
|
+
sudo env "PATH=$HOME/.local/bin:$PATH" pip3 install -U jetson-stats
|
|
323
|
+
systemctl_restart_service jtop.service
|
|
324
|
+
echo "✅ jtop was installed successfully."
|
|
325
|
+
else
|
|
326
|
+
echo "✅ jtop is already installed."
|
|
327
|
+
fi
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
# ================================================================================
|
|
331
|
+
# DOCKER
|
|
332
|
+
# ================================================================================
|
|
333
|
+
|
|
334
|
+
ensure_docker_repo() {
|
|
335
|
+
local docker_keyring_path="/etc/apt/keyrings/docker.asc"
|
|
336
|
+
|
|
337
|
+
if ! file_exists "${docker_keyring_path}"; then
|
|
338
|
+
echo "🔑 Adding Docker GPG key and repository..."
|
|
339
|
+
|
|
340
|
+
sudo apt update
|
|
341
|
+
sudo apt install ca-certificates curl
|
|
342
|
+
sudo install -m 0755 -d /etc/apt/keyrings
|
|
343
|
+
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
|
344
|
+
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
|
345
|
+
|
|
346
|
+
# Add the repository to Apt sources:
|
|
347
|
+
echo \
|
|
348
|
+
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
|
349
|
+
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
|
350
|
+
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
351
|
+
sudo apt update
|
|
352
|
+
fi
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
install_docker() {
|
|
356
|
+
ensure_docker_repo
|
|
357
|
+
|
|
358
|
+
if ! is_command_available docker; then
|
|
359
|
+
echo "📦 Installing docker..."
|
|
360
|
+
|
|
361
|
+
sudo apt update
|
|
362
|
+
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin nvidia-container-toolkit
|
|
363
|
+
|
|
364
|
+
echo "✅ docker was installed successfully."
|
|
365
|
+
else
|
|
366
|
+
echo "✅ docker is already installed."
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
if ! docker compose version &> /dev/null; then
|
|
370
|
+
echo "📦 Installing docker-compose..."
|
|
371
|
+
sudo apt install -y docker-compose-plugin
|
|
372
|
+
echo "✅ docker-compose was installed successfully."
|
|
373
|
+
else
|
|
374
|
+
echo "✅ docker-compose is already installed."
|
|
375
|
+
fi
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
# Add user to docker group
|
|
379
|
+
setup_docker_group() {
|
|
380
|
+
# Check if user is in docker group
|
|
381
|
+
if is_user_in_group docker; then
|
|
382
|
+
echo "✅ User '$USER' is already in the docker group."
|
|
383
|
+
else
|
|
384
|
+
echo "Adding $USER to the docker group..."
|
|
385
|
+
sudo usermod -aG docker "$USER"
|
|
386
|
+
|
|
387
|
+
# Group membership takes effect at next login; the live socket may
|
|
388
|
+
# stay unwritable until then.
|
|
389
|
+
if [[ -S /var/run/docker.sock && ! -w /var/run/docker.sock ]]; then
|
|
390
|
+
echo "ℹ️ The docker socket stays unwritable until you log in again."
|
|
391
|
+
fi
|
|
392
|
+
fi
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
# Function to check Docker runtime configuration
|
|
396
|
+
check_docker_runtime() {
|
|
397
|
+
local daemon_config="/etc/docker/daemon.json"
|
|
398
|
+
|
|
399
|
+
if ! is_command_available docker; then
|
|
400
|
+
return 1
|
|
401
|
+
fi
|
|
402
|
+
|
|
403
|
+
if ! file_exists $daemon_config; then
|
|
404
|
+
return 1
|
|
405
|
+
fi
|
|
406
|
+
|
|
407
|
+
if grep -q '"default-runtime": "nvidia"' $daemon_config 2>/dev/null; then
|
|
408
|
+
return 0
|
|
409
|
+
else
|
|
410
|
+
return 1
|
|
411
|
+
fi
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
# Configure Docker with NVIDIA runtime as default
|
|
415
|
+
configure_docker_runtime() {
|
|
416
|
+
local daemon_config="/etc/docker/daemon.json"
|
|
417
|
+
|
|
418
|
+
# THOR:
|
|
419
|
+
# -----
|
|
420
|
+
# sudo nvidia-ctk runtime configure --runtime=docker
|
|
421
|
+
# to configure it to following state:
|
|
422
|
+
# {
|
|
423
|
+
# "runtimes": {
|
|
424
|
+
# "nvidia": {
|
|
425
|
+
# "args": [],
|
|
426
|
+
# "path": "nvidia-container-runtime"
|
|
427
|
+
# }
|
|
428
|
+
# }
|
|
429
|
+
# }
|
|
430
|
+
# and always use --runtime=nvidia when running docker docntainers with GPU
|
|
431
|
+
# -----
|
|
432
|
+
|
|
433
|
+
if check_docker_runtime; then
|
|
434
|
+
echo "✅ NVIDIA runtime is already set as default in Docker: ${daemon_config}"
|
|
435
|
+
else
|
|
436
|
+
echo "❌ NVIDIA runtime is NOT set as default - configuring now..."
|
|
437
|
+
|
|
438
|
+
# Check if jq is installed
|
|
439
|
+
ensure_installed jq jq
|
|
440
|
+
|
|
441
|
+
# Configure NVIDIA runtime as default
|
|
442
|
+
local tmp
|
|
443
|
+
tmp=$(mktemp)
|
|
444
|
+
if file_exists $daemon_config; then
|
|
445
|
+
sudo jq '.
|
|
446
|
+
| .runtimes += {
|
|
447
|
+
"nvidia": {
|
|
448
|
+
"path": "nvidia-container-runtime",
|
|
449
|
+
"runtimeArgs": []
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
| .["default-runtime"] = "nvidia"
|
|
453
|
+
' "$daemon_config" > "$tmp"
|
|
454
|
+
sudo mv "$tmp" "$daemon_config"
|
|
455
|
+
else
|
|
456
|
+
cat > "$tmp" <<'EOF'
|
|
457
|
+
{
|
|
458
|
+
"runtimes": {
|
|
459
|
+
"nvidia": {
|
|
460
|
+
"path": "nvidia-container-runtime",
|
|
461
|
+
"runtimeArgs": []
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
"default-runtime": "nvidia"
|
|
465
|
+
}
|
|
466
|
+
EOF
|
|
467
|
+
sudo mv "$tmp" "$daemon_config"
|
|
468
|
+
fi
|
|
469
|
+
|
|
470
|
+
# The tmp file is user-owned; daemon.json must be root:root 644.
|
|
471
|
+
sudo chown root:root "$daemon_config"
|
|
472
|
+
sudo chmod 644 "$daemon_config"
|
|
473
|
+
|
|
474
|
+
# Restart Docker to apply changes
|
|
475
|
+
echo "ℹ️ Restarting Docker service to apply changes..."
|
|
476
|
+
systemctl_restart_service docker
|
|
477
|
+
echo "✅ NVIDIA runtime is now set as default."
|
|
478
|
+
fi
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
ensure_docker_running_and_enabled() {
|
|
482
|
+
# 1. Does the unit exist?
|
|
483
|
+
if ! systemctl_service_exists docker; then
|
|
484
|
+
echo "❌ Docker service is not installed. Please install Docker first."
|
|
485
|
+
return 1
|
|
486
|
+
fi
|
|
487
|
+
|
|
488
|
+
# 2. Start it if it's not running
|
|
489
|
+
if ! systemctl_service_is_active docker; then
|
|
490
|
+
echo "▶️ Starting Docker service..."
|
|
491
|
+
systemctl_start_service docker
|
|
492
|
+
|
|
493
|
+
if systemctl_service_is_active docker; then
|
|
494
|
+
echo "✅ Docker service started."
|
|
495
|
+
else
|
|
496
|
+
echo "❌ Failed to start Docker service."
|
|
497
|
+
return 1
|
|
498
|
+
fi
|
|
499
|
+
else
|
|
500
|
+
echo "✅ Docker service is already running."
|
|
501
|
+
fi
|
|
502
|
+
|
|
503
|
+
# 3. Enable it at boot if it's not enabled
|
|
504
|
+
if ! systemctl_service_is_enabled docker; then
|
|
505
|
+
echo "⚙️ Enabling Docker service at boot..."
|
|
506
|
+
systemctl_enable_service docker
|
|
507
|
+
|
|
508
|
+
if systemctl_service_is_enabled docker; then
|
|
509
|
+
echo "✅ Docker service is now enabled at boot."
|
|
510
|
+
else
|
|
511
|
+
echo "❌ Failed to enable Docker service at boot."
|
|
512
|
+
return 1
|
|
513
|
+
fi
|
|
514
|
+
else
|
|
515
|
+
echo "✅ Docker service is already enabled at boot."
|
|
516
|
+
fi
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
# ================================================================================
|
|
520
|
+
# SWAP
|
|
521
|
+
# ================================================================================
|
|
522
|
+
|
|
523
|
+
check_swap_exists() {
|
|
524
|
+
local swap_file="${1:-}"
|
|
525
|
+
|
|
526
|
+
# non-empty and regular file
|
|
527
|
+
file_exists "${swap_file}" || return 1
|
|
528
|
+
|
|
529
|
+
# check if it's active
|
|
530
|
+
if swapon --noheadings --raw --show=NAME | grep -Fqx "${swap_file}"; then
|
|
531
|
+
return 0
|
|
532
|
+
else
|
|
533
|
+
return 1
|
|
534
|
+
fi
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
# Check if swap file exists and is active
|
|
538
|
+
check_swap_file_status() {
|
|
539
|
+
local swap_file="${1:-}"
|
|
540
|
+
|
|
541
|
+
if file_exists "$swap_file"; then
|
|
542
|
+
if check_swap_exists "$swap_file"; then
|
|
543
|
+
echo "active"
|
|
544
|
+
else
|
|
545
|
+
echo "inactive"
|
|
546
|
+
fi
|
|
547
|
+
else
|
|
548
|
+
echo "missing"
|
|
549
|
+
fi
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
# Helper function for robust swap file cleanup
|
|
553
|
+
cleanup_swap_file() {
|
|
554
|
+
local swap_file="$1"
|
|
555
|
+
|
|
556
|
+
[[ -z "${swap_file}" ]] && return 0
|
|
557
|
+
|
|
558
|
+
sync 2>/dev/null || true
|
|
559
|
+
sudo swapoff "${swap_file}" 2>/dev/null || true
|
|
560
|
+
sudo rm -f "${swap_file}" 2>/dev/null || true
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
# Create and enable swap file with safety checks
|
|
564
|
+
setup_swap_file() {
|
|
565
|
+
local swap_size=$SWAP_SIZE
|
|
566
|
+
local swap_file_path="/mnt/${swap_size}GB.swap"
|
|
567
|
+
local swap_state
|
|
568
|
+
local size_bytes
|
|
569
|
+
swap_state=$(check_swap_file_status "${swap_file_path}")
|
|
570
|
+
size_bytes=$(convert_to_bytes "${swap_size}G")
|
|
571
|
+
|
|
572
|
+
# Validate inputs
|
|
573
|
+
if [[ -z "${swap_file_path}" ]]; then
|
|
574
|
+
echo "❌ Error: swap_file_path is not set"
|
|
575
|
+
return 1
|
|
576
|
+
fi
|
|
577
|
+
|
|
578
|
+
if [[ -z "${size_bytes}" ]] || [[ "${size_bytes}" -le 0 ]]; then
|
|
579
|
+
echo "❌ Error: Invalid swap size: ${swap_size}"
|
|
580
|
+
return 1
|
|
581
|
+
fi
|
|
582
|
+
|
|
583
|
+
# Validate swap file path (should be absolute and in safe location)
|
|
584
|
+
if [[ "${swap_file_path}" != /* ]]; then
|
|
585
|
+
echo "❌ Error: swap_file_path must be absolute: ${swap_file_path}"
|
|
586
|
+
return 1
|
|
587
|
+
fi
|
|
588
|
+
|
|
589
|
+
# Ensure path is in a safe location (not root, boot, etc.)
|
|
590
|
+
case "${swap_file_path}" in
|
|
591
|
+
/boot/*|/proc/*|/sys/*|/dev/*|/run/*)
|
|
592
|
+
echo "❌ Error: Unsafe swap file location: ${swap_file_path}"
|
|
593
|
+
return 1
|
|
594
|
+
;;
|
|
595
|
+
esac
|
|
596
|
+
|
|
597
|
+
# Get directory and check if it exists
|
|
598
|
+
local swap_dir=$(dirname "${swap_file_path}")
|
|
599
|
+
if [[ ! -d "${swap_dir}" ]]; then
|
|
600
|
+
echo "❌ Error: Directory does not exist: ${swap_dir}"
|
|
601
|
+
return 1
|
|
602
|
+
fi
|
|
603
|
+
|
|
604
|
+
# Check write permissions on directory
|
|
605
|
+
if [[ ! -w "${swap_dir}" ]] && ! sudo test -w "${swap_dir}"; then
|
|
606
|
+
echo "❌ Error: No write permission for directory: ${swap_dir}"
|
|
607
|
+
return 1
|
|
608
|
+
fi
|
|
609
|
+
|
|
610
|
+
# Check available space (add 10% buffer)
|
|
611
|
+
local available_space
|
|
612
|
+
available_space=$(df --output=avail -B1 "${swap_dir}" | tail -n1)
|
|
613
|
+
if [[ "${size_bytes}" -gt "${available_space}" ]]; then
|
|
614
|
+
echo "❌ Error: Insufficient disk space"
|
|
615
|
+
echo " Requested: $(numfmt --to=iec "${size_bytes}" 2>/dev/null || echo "${size_bytes} bytes")"
|
|
616
|
+
echo " Available: $(numfmt --to=iec "${available_space}" 2>/dev/null || echo "${available_space} bytes")"
|
|
617
|
+
return 1
|
|
618
|
+
fi
|
|
619
|
+
|
|
620
|
+
# If swap file exists, disable and remove it first
|
|
621
|
+
if [ "${swap_state}" != "missing" ]; then
|
|
622
|
+
cleanup_swap_file "${swap_file_path}"
|
|
623
|
+
# Remove from fstab if present
|
|
624
|
+
sudo sed -i "\\#^${swap_file_path}#d" /etc/fstab
|
|
625
|
+
fi
|
|
626
|
+
|
|
627
|
+
# Create swap only when not installed on sdcard
|
|
628
|
+
if ! is_l4t_installed_on_sdcard; then
|
|
629
|
+
# Calculate count more safely
|
|
630
|
+
local count_mb=$((size_bytes/1024/1024))
|
|
631
|
+
if [[ $((count_mb * 1024 * 1024)) -ne "${size_bytes}" ]]; then
|
|
632
|
+
echo "❌ Error: Size calculation overflow or precision loss"
|
|
633
|
+
return 1
|
|
634
|
+
fi
|
|
635
|
+
|
|
636
|
+
# Use fallocate if available (faster and safer than dd)
|
|
637
|
+
if command -v fallocate >/dev/null 2>&1; then
|
|
638
|
+
if ! sudo fallocate -l "${size_bytes}" "${swap_file_path}"; then
|
|
639
|
+
echo "❌ Error: Failed to create swap file with fallocate"
|
|
640
|
+
return 1
|
|
641
|
+
fi
|
|
642
|
+
else
|
|
643
|
+
# Fallback to dd with additional safety
|
|
644
|
+
if ! sudo dd if=/dev/zero of="${swap_file_path}" bs=1M count="${count_mb}" status=progress conv=fsync 2>/dev/null; then
|
|
645
|
+
echo "❌ Error: Failed to create swap file with dd"
|
|
646
|
+
# Clean up partial file
|
|
647
|
+
cleanup_swap_file "${swap_file_path}"
|
|
648
|
+
return 1
|
|
649
|
+
fi
|
|
650
|
+
fi
|
|
651
|
+
|
|
652
|
+
# Set proper permissions
|
|
653
|
+
if ! sudo chmod 600 "${swap_file_path}"; then
|
|
654
|
+
echo "❌ Error: Failed to set swap file permissions"
|
|
655
|
+
cleanup_swap_file "${swap_file_path}"
|
|
656
|
+
return 1
|
|
657
|
+
fi
|
|
658
|
+
|
|
659
|
+
# Initialize swap
|
|
660
|
+
if ! sudo mkswap "${swap_file_path}" >/dev/null; then
|
|
661
|
+
echo "❌ Error: Failed to initialize swap file"
|
|
662
|
+
cleanup_swap_file "${swap_file_path}"
|
|
663
|
+
return 1
|
|
664
|
+
fi
|
|
665
|
+
|
|
666
|
+
# Enable swap
|
|
667
|
+
if ! sudo swapon "${swap_file_path}"; then
|
|
668
|
+
echo "❌ Error: Failed to enable swap file"
|
|
669
|
+
cleanup_swap_file "${swap_file_path}"
|
|
670
|
+
return 1
|
|
671
|
+
fi
|
|
672
|
+
|
|
673
|
+
# Add to fstab if not already present (using awk for precise matching)
|
|
674
|
+
if ! awk -v path="${swap_file_path}" '$1 == path && $3 == "swap" {found=1} END {exit !found}' /etc/fstab 2>/dev/null; then
|
|
675
|
+
if ! echo "${swap_file_path} none swap sw,pri=1 0 0" | sudo tee -a /etc/fstab >/dev/null; then
|
|
676
|
+
echo "⚠️ Warning: Failed to add swap to fstab (swap is still active)"
|
|
677
|
+
fi
|
|
678
|
+
fi
|
|
679
|
+
|
|
680
|
+
# Final verification
|
|
681
|
+
if check_swap_exists "${swap_file_path}"; then
|
|
682
|
+
echo "✅ Swap file setup complete ($(numfmt --to=iec ${size_bytes}))"
|
|
683
|
+
return 0
|
|
684
|
+
else
|
|
685
|
+
echo "❌ Failed to verify swap file setup"
|
|
686
|
+
return 1
|
|
687
|
+
fi
|
|
688
|
+
else
|
|
689
|
+
echo "✅ Skipping swap file creation (L4T installed on SD card)"
|
|
690
|
+
return 0
|
|
691
|
+
fi
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
# ================================================================================
|
|
695
|
+
# zRAM
|
|
696
|
+
# ================================================================================
|
|
697
|
+
|
|
698
|
+
check_zram_state() {
|
|
699
|
+
if swapon --noheadings --raw --show=NAME | grep -q 'zram'; then
|
|
700
|
+
echo "active"
|
|
701
|
+
elif file_exists /etc/modules-load.d/zram.conf; then
|
|
702
|
+
echo "configured"
|
|
703
|
+
else
|
|
704
|
+
echo "disabled"
|
|
705
|
+
fi
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
# Disable zRAM (the Thor setup uses a file-backed swap instead)
|
|
709
|
+
setup_zram() {
|
|
710
|
+
local zram_enabled=${1:-"no"}
|
|
711
|
+
|
|
712
|
+
# If zRAM should be enabled
|
|
713
|
+
if is_true "${zram_enabled}"; then
|
|
714
|
+
echo "❌ zRAM is not enabled by the mjolnir Thor setup (file-backed swap instead)."
|
|
715
|
+
return 1
|
|
716
|
+
fi
|
|
717
|
+
|
|
718
|
+
local zram_state
|
|
719
|
+
zram_state=$(check_zram_state)
|
|
720
|
+
# Disable zRAM if it's enabled
|
|
721
|
+
if [ "${zram_state}" != "disabled" ]; then
|
|
722
|
+
# Stop and disable the zram service
|
|
723
|
+
systemctl_disable_service zram
|
|
724
|
+
systemctl_disable_service nvzramconfig
|
|
725
|
+
|
|
726
|
+
local d
|
|
727
|
+
for d in /dev/zram*; do
|
|
728
|
+
[[ -e "$d" ]] || continue
|
|
729
|
+
sudo swapoff "$d" 2>/dev/null || true
|
|
730
|
+
echo 1 | sudo tee /sys/block/"$(basename "$d")"/reset >/dev/null || true
|
|
731
|
+
echo "✅ Disabled $d..."
|
|
732
|
+
done
|
|
733
|
+
|
|
734
|
+
# Unload the zram module
|
|
735
|
+
sudo rmmod zram 2>/dev/null || true
|
|
736
|
+
|
|
737
|
+
sudo rm -f /etc/modules-load.d/zram.conf /etc/modprobe.d/zram.conf \
|
|
738
|
+
/etc/udev/rules.d/99-zram.rules /etc/systemd/system/zram.service
|
|
739
|
+
sudo systemctl daemon-reload
|
|
740
|
+
|
|
741
|
+
echo "✅ zRAM has been disabled"
|
|
742
|
+
else
|
|
743
|
+
echo "✅ zRAM is already disabled"
|
|
744
|
+
fi
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
# Configure memory settings
|
|
748
|
+
configure_jetson_memory() {
|
|
749
|
+
# Setup swap file
|
|
750
|
+
setup_swap_file
|
|
751
|
+
|
|
752
|
+
# Configure zRAM (the Thor setup keeps it off)
|
|
753
|
+
setup_zram "false"
|
|
754
|
+
|
|
755
|
+
# Display current memory configuration
|
|
756
|
+
echo -e "\n=== Current Memory Configuration ==="
|
|
757
|
+
free -h
|
|
758
|
+
|
|
759
|
+
# Display swap configuration if supported
|
|
760
|
+
if ! is_l4t_installed_on_sdcard; then
|
|
761
|
+
echo -e "\nSwap configuration:"
|
|
762
|
+
swapon -s
|
|
763
|
+
fi
|
|
764
|
+
|
|
765
|
+
echo
|
|
766
|
+
echo "✅ Memory setup complete"
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
# ================================================================================
|
|
770
|
+
# GUI
|
|
771
|
+
# ================================================================================
|
|
772
|
+
|
|
773
|
+
# Function to get current GUI state
|
|
774
|
+
get_gui_state() {
|
|
775
|
+
if systemctl get-default | grep -q "graphical.target"; then
|
|
776
|
+
echo "enabled"
|
|
777
|
+
else
|
|
778
|
+
echo "disabled"
|
|
779
|
+
fi
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
# Function to enable GUI
|
|
783
|
+
enable_gui() {
|
|
784
|
+
sudo systemctl set-default graphical.target
|
|
785
|
+
|
|
786
|
+
if is_true "$(get_gui_state)"; then
|
|
787
|
+
echo "✅ GUI has been enabled on boot"
|
|
788
|
+
return 0
|
|
789
|
+
else
|
|
790
|
+
echo "❌ Failed to enable GUI"
|
|
791
|
+
return 1
|
|
792
|
+
fi
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
# Function to disable GUI
|
|
796
|
+
disable_gui() {
|
|
797
|
+
sudo systemctl set-default multi-user.target
|
|
798
|
+
|
|
799
|
+
if ! is_true "$(get_gui_state)"; then
|
|
800
|
+
echo "✅ GUI has been disabled on boot"
|
|
801
|
+
return 0
|
|
802
|
+
else
|
|
803
|
+
echo "❌ Failed to disable GUI"
|
|
804
|
+
return 1
|
|
805
|
+
fi
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
configure_gui() {
|
|
809
|
+
local gui_enabled=${1-"no"}
|
|
810
|
+
local current_state
|
|
811
|
+
current_state=$(get_gui_state)
|
|
812
|
+
|
|
813
|
+
if is_true "$gui_enabled"; then
|
|
814
|
+
if [ "$current_state" = "disabled" ]; then
|
|
815
|
+
enable_gui
|
|
816
|
+
else
|
|
817
|
+
echo "✅ GUI is already enabled"
|
|
818
|
+
fi
|
|
819
|
+
else
|
|
820
|
+
if [ "$current_state" = "enabled" ]; then
|
|
821
|
+
disable_gui
|
|
822
|
+
else
|
|
823
|
+
echo "✅ GUI is already disabled"
|
|
824
|
+
fi
|
|
825
|
+
fi
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
# ================================================================================
|
|
829
|
+
# FAN (merged from scripts/hw/fan-profiles.sh — one process with the rest)
|
|
830
|
+
#
|
|
831
|
+
# 1. BACKUP (first run only): untouched $CONF is dumped to $CONF.bck.
|
|
832
|
+
# The .bck ALWAYS holds the original — the clean rollback point.
|
|
833
|
+
# 2. INJECT (per-profile gated): the two performance fan profiles
|
|
834
|
+
# (tuned on the AGX Thor) are inserted before the THERMAL_GROUP
|
|
835
|
+
# section — each ONLY if its block is not already present:
|
|
836
|
+
# FAN_PROFILE recommended — balanced warmth/acoustics
|
|
837
|
+
# FAN_PROFILE max — sustained full-load cooling (bench)
|
|
838
|
+
# 3. FAN MODE SELECTION: FAN_DEFAULT_PROFILE <--fan-profile> (default
|
|
839
|
+
# "recommended", not the stock "cool").
|
|
840
|
+
# 4. Restart nvfancontrol and print the daemon's active state.
|
|
841
|
+
# ================================================================================
|
|
842
|
+
|
|
843
|
+
FAN_CONF="${MJOLNIR_NVFANCONF:-/etc/nvfancontrol.conf}"
|
|
844
|
+
|
|
845
|
+
write_fan_profile_blocks() {
|
|
846
|
+
local dir="$1"
|
|
847
|
+
cat > "$dir/profile_recommended.conf" <<'EOF'
|
|
848
|
+
FAN_PROFILE recommended {
|
|
849
|
+
#TEMP HYST PWM RPM
|
|
850
|
+
0 0 255 5371
|
|
851
|
+
10 0 220 4700
|
|
852
|
+
20 0 180 3900
|
|
853
|
+
30 0 140 3000
|
|
854
|
+
40 0 102 2400
|
|
855
|
+
55 0 90 2100
|
|
856
|
+
70 0 80 1800
|
|
857
|
+
115 0 80 1800
|
|
858
|
+
}
|
|
859
|
+
EOF
|
|
860
|
+
|
|
861
|
+
cat > "$dir/profile_max.conf" <<'EOF'
|
|
862
|
+
FAN_PROFILE max {
|
|
863
|
+
#TEMP HYST PWM RPM
|
|
864
|
+
0 0 255 5371
|
|
865
|
+
15 0 240 5000
|
|
866
|
+
25 0 220 4700
|
|
867
|
+
35 0 195 4200
|
|
868
|
+
50 0 170 3700
|
|
869
|
+
70 0 120 2700
|
|
870
|
+
115 0 120 2700
|
|
871
|
+
}
|
|
872
|
+
EOF
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
# $1=src $2=dst — inserts the missing profile block(s) before the
|
|
876
|
+
# THERMAL_GROUP anchor and selects the fan mode (FAN_DEFAULT_PROFILE).
|
|
877
|
+
fan_transform() {
|
|
878
|
+
awk -v do_rec="$DO_REC" -v do_max="$DO_MAX" -v sel="$FAN_PROFILE" \
|
|
879
|
+
-v src="$1" \
|
|
880
|
+
-v f_rec="$FAN_TMP/profile_recommended.conf" \
|
|
881
|
+
-v f_max="$FAN_TMP/profile_max.conf" '
|
|
882
|
+
/^[[:space:]]*FAN_DEFAULT_PROFILE/ { sub(/FAN_DEFAULT_PROFILE.*/, "FAN_DEFAULT_PROFILE " sel) }
|
|
883
|
+
!done && $0 ~ /^[[:space:]]*THERMAL_GROUP[[:space:]]0/ {
|
|
884
|
+
done=1
|
|
885
|
+
if (do_rec) while ((getline l < f_rec) > 0) print l
|
|
886
|
+
if (do_max) while ((getline l < f_max) > 0) print l
|
|
887
|
+
}
|
|
888
|
+
{ print }
|
|
889
|
+
END {
|
|
890
|
+
if (!done) {
|
|
891
|
+
print "setup-thor: no THERMAL_GROUP anchor in " src " — refusing to guess" > "/dev/stderr"
|
|
892
|
+
exit 3
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
' "$1" > "$2"
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
do_fan() {
|
|
899
|
+
local conf="$FAN_CONF"
|
|
900
|
+
local bck="$conf.bck"
|
|
901
|
+
|
|
902
|
+
if ! file_exists "$conf"; then
|
|
903
|
+
echo "ℹ️ No nvfancontrol conf at $conf — skipping fan."
|
|
904
|
+
return 0
|
|
905
|
+
fi
|
|
906
|
+
|
|
907
|
+
if ! is_command_available nvfancontrol; then
|
|
908
|
+
echo "ℹ️ nvfancontrol not available — skipping fan."
|
|
909
|
+
return 0
|
|
910
|
+
fi
|
|
911
|
+
|
|
912
|
+
local have_rec have_max cur_default
|
|
913
|
+
have_rec=$(grep -cE "FAN_PROFILE recommended[[:space:]]*\{" "$conf" || true)
|
|
914
|
+
have_max=$(grep -cE "FAN_PROFILE max[[:space:]]*\{" "$conf" || true)
|
|
915
|
+
cur_default="$(awk '/^[[:space:]]*FAN_DEFAULT_PROFILE/ {print $2; exit}' "$conf")"
|
|
916
|
+
cur_default="${cur_default:-<unset>}"
|
|
917
|
+
|
|
918
|
+
local do_rec=0 do_max=0
|
|
919
|
+
[ "$have_rec" -eq 0 ] && do_rec=1
|
|
920
|
+
[ "$have_max" -eq 0 ] && do_max=1
|
|
921
|
+
DO_REC=$do_rec
|
|
922
|
+
DO_MAX=$do_max
|
|
923
|
+
|
|
924
|
+
# Stock profiles ship with the L4T conf; recommended/max are installed here.
|
|
925
|
+
case "$FAN_PROFILE" in
|
|
926
|
+
cool|quiet)
|
|
927
|
+
if ! grep -qE "FAN_PROFILE ${FAN_PROFILE}[[:space:]]*\{" "$conf"; then
|
|
928
|
+
echo "❌ fan profile '$FAN_PROFILE' not in $conf" >&2
|
|
929
|
+
return 1
|
|
930
|
+
fi
|
|
931
|
+
;;
|
|
932
|
+
esac
|
|
933
|
+
|
|
934
|
+
local pending=0
|
|
935
|
+
[ -e "$bck" ] || pending=1
|
|
936
|
+
[ "$do_rec" = 1 ] && pending=1
|
|
937
|
+
[ "$do_max" = 1 ] && pending=1
|
|
938
|
+
[ "$cur_default" != "$FAN_PROFILE" ] && pending=1
|
|
939
|
+
|
|
940
|
+
echo "fan profiles: $conf"
|
|
941
|
+
if [ -e "$bck" ]; then
|
|
942
|
+
echo " backup: $bck (exists — original preserved)"
|
|
943
|
+
else
|
|
944
|
+
echo " backup: $conf -> $bck (first run — will be created)"
|
|
945
|
+
fi
|
|
946
|
+
if [ "$do_rec" = 1 ]; then
|
|
947
|
+
echo " inject: FAN_PROFILE recommended (not present — will be added)"
|
|
948
|
+
else
|
|
949
|
+
echo " inject: FAN_PROFILE recommended (already present — skipped)"
|
|
950
|
+
fi
|
|
951
|
+
if [ "$do_max" = 1 ]; then
|
|
952
|
+
echo " inject: FAN_PROFILE max (not present — will be added)"
|
|
953
|
+
else
|
|
954
|
+
echo " inject: FAN_PROFILE max (already present — skipped)"
|
|
955
|
+
fi
|
|
956
|
+
if [ "$cur_default" != "$FAN_PROFILE" ]; then
|
|
957
|
+
echo " fan mode: FAN_DEFAULT_PROFILE $cur_default -> $FAN_PROFILE"
|
|
958
|
+
else
|
|
959
|
+
echo " fan mode: FAN_DEFAULT_PROFILE $FAN_PROFILE (already selected)"
|
|
960
|
+
fi
|
|
961
|
+
|
|
962
|
+
if [ "$pending" = 0 ]; then
|
|
963
|
+
echo "✅ Fan already configured (profile: $FAN_PROFILE)."
|
|
964
|
+
return 0
|
|
965
|
+
fi
|
|
966
|
+
|
|
967
|
+
FAN_TMP="$(mktemp -d)"
|
|
968
|
+
trap 'rm -rf "$FAN_TMP"' EXIT
|
|
969
|
+
write_fan_profile_blocks "$FAN_TMP"
|
|
970
|
+
|
|
971
|
+
# First run only: the .bck always holds the untouched original.
|
|
972
|
+
[ -e "$bck" ] || sudo cp -a -- "$conf" "$bck"
|
|
973
|
+
fan_transform "$conf" "$FAN_TMP/new.conf"
|
|
974
|
+
sudo cp -- "$FAN_TMP/new.conf" "$conf"
|
|
975
|
+
|
|
976
|
+
# Remove the old status so the daemon reloads fresh, then restart.
|
|
977
|
+
sudo rm -f /var/lib/nvfancontrol/status
|
|
978
|
+
systemctl_restart_service nvfancontrol
|
|
979
|
+
|
|
980
|
+
echo "✅ Fan profile installed; default: $FAN_PROFILE."
|
|
981
|
+
echo " active state:"
|
|
982
|
+
sudo nvfancontrol -q 2>&1 | grep -iE 'profile|governor' | sed 's/^/ /' || true
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
# ================================================================================
|
|
986
|
+
# HOST / SYSTEM CONFIGURATION
|
|
987
|
+
# ================================================================================
|
|
988
|
+
|
|
989
|
+
host_optimizations() {
|
|
990
|
+
systemctl_disable_service nvargus-daemon.service
|
|
991
|
+
systemctl_disable_service cups
|
|
992
|
+
systemctl_disable_service ModemManager
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
check_jetson_clocks() {
|
|
996
|
+
local state
|
|
997
|
+
state=$(sudo jetson_clocks --show | grep -oP '(?<=FreqOverride=)\d+')
|
|
998
|
+
|
|
999
|
+
is_true "$state"
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
start_jetson_clocks_service() {
|
|
1003
|
+
local service_name="jetson_clocks.service"
|
|
1004
|
+
local service_path="/etc/systemd/system/${service_name}"
|
|
1005
|
+
|
|
1006
|
+
# 1) Ensure the unit file exists
|
|
1007
|
+
if ! systemctl_service_exists "$service_name"; then
|
|
1008
|
+
echo "ℹ️ $service_name not found; creating at $service_path"
|
|
1009
|
+
sudo tee "$service_path" >/dev/null <<'EOF'
|
|
1010
|
+
[Unit]
|
|
1011
|
+
Description=Jetson Clocks Service
|
|
1012
|
+
After=multi-user.target
|
|
1013
|
+
|
|
1014
|
+
[Service]
|
|
1015
|
+
Type=oneshot
|
|
1016
|
+
ExecStart=/usr/bin/jetson_clocks
|
|
1017
|
+
RemainAfterExit=true
|
|
1018
|
+
|
|
1019
|
+
[Install]
|
|
1020
|
+
WantedBy=multi-user.target
|
|
1021
|
+
EOF
|
|
1022
|
+
|
|
1023
|
+
echo "ℹ️ Reloading systemd daemon"
|
|
1024
|
+
sudo systemctl daemon-reload
|
|
1025
|
+
|
|
1026
|
+
echo "ℹ️ Enabling $service_name to start on boot"
|
|
1027
|
+
systemctl_enable_service "$service_name"
|
|
1028
|
+
fi
|
|
1029
|
+
|
|
1030
|
+
# 2) Check if the service is active
|
|
1031
|
+
if ! systemctl_service_is_active "$service_name"; then
|
|
1032
|
+
echo "ℹ️ Starting $service_name"
|
|
1033
|
+
systemctl_start_service "$service_name"
|
|
1034
|
+
echo "✅ $service_name started."
|
|
1035
|
+
echo "ℹ️ Current clock settings:"
|
|
1036
|
+
sudo jetson_clocks --show
|
|
1037
|
+
fi
|
|
1038
|
+
|
|
1039
|
+
# 3) Final health check
|
|
1040
|
+
if check_jetson_clocks; then
|
|
1041
|
+
echo "✅ Jetson Clocks is enabled and running."
|
|
1042
|
+
else
|
|
1043
|
+
echo "❌ Failed to enable Jetson Clocks lock."
|
|
1044
|
+
return 1
|
|
1045
|
+
fi
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
# ================================================================================
|
|
1049
|
+
# POWER MODE
|
|
1050
|
+
# ================================================================================
|
|
1051
|
+
|
|
1052
|
+
check_nvpmodel_installed() {
|
|
1053
|
+
if ! is_command_available nvpmodel; then
|
|
1054
|
+
echo "❌ Error: Missing required dependency: nvpmodel"
|
|
1055
|
+
echo " Please check your Jetson system installation"
|
|
1056
|
+
exit 1
|
|
1057
|
+
fi
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
get_power_mode() {
|
|
1061
|
+
check_nvpmodel_installed
|
|
1062
|
+
|
|
1063
|
+
nvpmodel -q | grep "NV Power Mode" | cut -d':' -f2 | xargs
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
get_power_modes() {
|
|
1067
|
+
local file="${1:-/etc/nvpmodel.conf}"
|
|
1068
|
+
# declare global associative array
|
|
1069
|
+
declare -gA JETSON_POWER_MODES=()
|
|
1070
|
+
|
|
1071
|
+
local line id name
|
|
1072
|
+
while IFS= read -r line; do
|
|
1073
|
+
if [[ $line =~ \<[[:space:]]*POWER_MODEL[[:space:]]+ID=([0-9]+)[[:space:]]+NAME=([^[:space:]>]+) ]]; then
|
|
1074
|
+
id="${BASH_REMATCH[1]}"
|
|
1075
|
+
name="${BASH_REMATCH[2]}"
|
|
1076
|
+
JETSON_POWER_MODES[$id]="$name"
|
|
1077
|
+
fi
|
|
1078
|
+
done < "$file"
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
get_power_mode_index_by_name() {
|
|
1082
|
+
local substr="$1"
|
|
1083
|
+
local id
|
|
1084
|
+
|
|
1085
|
+
for id in "${!JETSON_POWER_MODES[@]}"; do
|
|
1086
|
+
if [[ "${JETSON_POWER_MODES[$id]}" == *"$substr"* ]]; then
|
|
1087
|
+
printf '%s\n' "$id"
|
|
1088
|
+
return 0
|
|
1089
|
+
fi
|
|
1090
|
+
done
|
|
1091
|
+
return 1
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
get_power_mode_config_file() {
|
|
1095
|
+
check_nvpmodel_installed
|
|
1096
|
+
|
|
1097
|
+
nvpmodel -q --verbose 2>&1 | grep -m1 'Config file:' | awk -F': ' '{print $NF}'
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
# Configure power mode
|
|
1101
|
+
setup_power_mode() {
|
|
1102
|
+
local current_mode
|
|
1103
|
+
local power_config_path
|
|
1104
|
+
current_mode="$(get_power_mode)"
|
|
1105
|
+
power_config_path="$(get_power_mode_config_file)"
|
|
1106
|
+
|
|
1107
|
+
get_power_modes
|
|
1108
|
+
|
|
1109
|
+
# First try "SUPER", then "MAXN"
|
|
1110
|
+
if MAXN_POWER_MODE_ID=$(get_power_mode_index_by_name SUPER); then
|
|
1111
|
+
:
|
|
1112
|
+
elif MAXN_POWER_MODE_ID=$(get_power_mode_index_by_name MAXN); then
|
|
1113
|
+
:
|
|
1114
|
+
else
|
|
1115
|
+
echo "❌ No matching MAXN or MAXN_SUPER power mode!"
|
|
1116
|
+
exit 1
|
|
1117
|
+
fi
|
|
1118
|
+
|
|
1119
|
+
if [ "$current_mode" = "${JETSON_POWER_MODES[$MAXN_POWER_MODE_ID]}" ]; then
|
|
1120
|
+
echo "✅ Power mode already set to $current_mode, skipping..."
|
|
1121
|
+
return 0
|
|
1122
|
+
fi
|
|
1123
|
+
|
|
1124
|
+
echo "ℹ️ Setting power mode to mode ${JETSON_POWER_MODES[$MAXN_POWER_MODE_ID]} (this will be applied after reboot)..."
|
|
1125
|
+
|
|
1126
|
+
# Use -f flag to suppress the interactive reboot prompt
|
|
1127
|
+
if sudo nvpmodel -m "$MAXN_POWER_MODE_ID" -f "$power_config_path" > /dev/null; then
|
|
1128
|
+
echo "✅ Power mode change scheduled. A reboot will be required to apply this change."
|
|
1129
|
+
REBOOT_REQUIRED=1
|
|
1130
|
+
return 0
|
|
1131
|
+
else
|
|
1132
|
+
echo "❌ Failed to set power mode"
|
|
1133
|
+
return 1
|
|
1134
|
+
fi
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
# ================================================================================
|
|
1138
|
+
# MAIN
|
|
1139
|
+
# ================================================================================
|
|
1140
|
+
|
|
1141
|
+
print_plan() {
|
|
1142
|
+
echo
|
|
1143
|
+
echo "Plan (dry-run — nothing will change):"
|
|
1144
|
+
|
|
1145
|
+
local i=0
|
|
1146
|
+
_plan() {
|
|
1147
|
+
local name="$1" label="$2"
|
|
1148
|
+
i=$((i + 1))
|
|
1149
|
+
if is_skipped "$name"; then
|
|
1150
|
+
printf ' %2d · %-8s %s\n' "$i" "$name" "$label (skipped)"
|
|
1151
|
+
else
|
|
1152
|
+
printf ' %2d → %-8s %s\n' "$i" "$name" "$label"
|
|
1153
|
+
fi
|
|
1154
|
+
}
|
|
1155
|
+
_plan gui "$( [ "$KEEP_GUI" = "1" ] && echo 'keep GUI (boot target unchanged)' || echo 'disable GUI on boot (multi-user.target)')"
|
|
1156
|
+
_plan upgrade "$( [ "$UPGRADE" = "1" ] && echo 'apt update + full-upgrade (+ nvidia-jetpack, curl, wget)' || echo 'skip the system upgrade')"
|
|
1157
|
+
_plan pip "pip + setuptools + wheel (get-pip.py fallback)"
|
|
1158
|
+
_plan docker "docker-ce + nvidia-container-toolkit, user in docker group, nvidia default runtime, service up + enabled"
|
|
1159
|
+
_plan jtop "jetson-stats (jtop)"
|
|
1160
|
+
_plan memory "${SWAP_SIZE} GB swap file at /mnt (zRAM off)"
|
|
1161
|
+
_plan fan "install recommended+max fan profiles (gated, .bck backup), FAN_DEFAULT_PROFILE: ${FAN_PROFILE}"
|
|
1162
|
+
_plan host "disable nvargus-daemon / cups / ModemManager"
|
|
1163
|
+
_plan clocks "jetson_clocks.service (lock max CPU/GPU/EMC clocks)"
|
|
1164
|
+
_plan power "nvpmodel → MAXN/SUPER (reboot to apply)"
|
|
1165
|
+
echo
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
# Bail early if not running on a Jetson (no device-tree)
|
|
1169
|
+
if [ ! -r /proc/device-tree/compatible ]; then
|
|
1170
|
+
echo "❌ /proc/device-tree/compatible not found; not a Jetson platform."
|
|
1171
|
+
exit 1
|
|
1172
|
+
fi
|
|
1173
|
+
|
|
1174
|
+
# Prepare a "table" with spaces or tabs
|
|
1175
|
+
cat <<EOF | column -t
|
|
1176
|
+
✅ User ${USER}
|
|
1177
|
+
✅ SoM ${SOC_TYPE}
|
|
1178
|
+
✅ Memory ${SOC_MEMORY}GB
|
|
1179
|
+
✅ DevKit ${IS_NVIDIA_CARRIER}
|
|
1180
|
+
EOF
|
|
1181
|
+
|
|
1182
|
+
echo
|
|
1183
|
+
if is_command_available nvidia-smi; then
|
|
1184
|
+
nvidia-smi
|
|
1185
|
+
fi
|
|
1186
|
+
|
|
1187
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
1188
|
+
print_plan
|
|
1189
|
+
exit 0
|
|
1190
|
+
fi
|
|
1191
|
+
|
|
1192
|
+
if [ "$ASSUME_YES" != "1" ]; then
|
|
1193
|
+
ask_should_proceed
|
|
1194
|
+
fi
|
|
1195
|
+
|
|
1196
|
+
# 1. Boot target: headless server (multi-user), GUI off by default
|
|
1197
|
+
if is_skipped gui; then
|
|
1198
|
+
echo "ℹ️ Skipping: gui"
|
|
1199
|
+
elif [ "$KEEP_GUI" = "1" ]; then
|
|
1200
|
+
echo "ℹ️ Keeping the GUI boot target (--keep-gui)."
|
|
1201
|
+
else
|
|
1202
|
+
configure_gui "no"
|
|
1203
|
+
fi
|
|
1204
|
+
|
|
1205
|
+
# 2. System upgrade (apt), JetPack + curl + wget
|
|
1206
|
+
if is_skipped upgrade; then
|
|
1207
|
+
echo "ℹ️ Skipping: upgrade"
|
|
1208
|
+
else
|
|
1209
|
+
upgrade_system
|
|
1210
|
+
fi
|
|
1211
|
+
|
|
1212
|
+
# 3. Python tooling
|
|
1213
|
+
if is_skipped pip; then
|
|
1214
|
+
echo "ℹ️ Skipping: pip"
|
|
1215
|
+
else
|
|
1216
|
+
install_pip
|
|
1217
|
+
fi
|
|
1218
|
+
|
|
1219
|
+
# 4. Docker + NVIDIA runtime
|
|
1220
|
+
if is_skipped docker; then
|
|
1221
|
+
echo "ℹ️ Skipping: docker"
|
|
1222
|
+
else
|
|
1223
|
+
install_docker
|
|
1224
|
+
setup_docker_group
|
|
1225
|
+
configure_docker_runtime
|
|
1226
|
+
ensure_docker_running_and_enabled
|
|
1227
|
+
fi
|
|
1228
|
+
|
|
1229
|
+
# 5. Monitor
|
|
1230
|
+
if is_skipped jtop; then
|
|
1231
|
+
echo "ℹ️ Skipping: jtop"
|
|
1232
|
+
else
|
|
1233
|
+
install_jtop
|
|
1234
|
+
fi
|
|
1235
|
+
|
|
1236
|
+
# 6. Memory: swap file + zRAM off
|
|
1237
|
+
if is_skipped memory; then
|
|
1238
|
+
echo "ℹ️ Skipping: memory"
|
|
1239
|
+
else
|
|
1240
|
+
configure_jetson_memory
|
|
1241
|
+
fi
|
|
1242
|
+
|
|
1243
|
+
# 7. Fan profile
|
|
1244
|
+
if is_skipped fan; then
|
|
1245
|
+
echo "ℹ️ Skipping: fan"
|
|
1246
|
+
else
|
|
1247
|
+
do_fan
|
|
1248
|
+
fi
|
|
1249
|
+
|
|
1250
|
+
# 8. Host service cleanup
|
|
1251
|
+
if is_skipped host; then
|
|
1252
|
+
echo "ℹ️ Skipping: host"
|
|
1253
|
+
else
|
|
1254
|
+
host_optimizations
|
|
1255
|
+
fi
|
|
1256
|
+
|
|
1257
|
+
# 9. Max clocks (jetson_clocks)
|
|
1258
|
+
if is_skipped clocks; then
|
|
1259
|
+
echo "ℹ️ Skipping: clocks"
|
|
1260
|
+
else
|
|
1261
|
+
start_jetson_clocks_service
|
|
1262
|
+
fi
|
|
1263
|
+
|
|
1264
|
+
# 10. Power mode (MAXN/SUPER)
|
|
1265
|
+
if is_skipped power; then
|
|
1266
|
+
echo "ℹ️ Skipping: power"
|
|
1267
|
+
else
|
|
1268
|
+
setup_power_mode
|
|
1269
|
+
fi
|
|
1270
|
+
|
|
1271
|
+
echo "✅ Jetson Thor configuration completed."
|
|
1272
|
+
|
|
1273
|
+
if [ "$REBOOT_REQUIRED" = "1" ]; then
|
|
1274
|
+
if [ "$DO_REBOOT" = "1" ]; then
|
|
1275
|
+
echo "Rebooting, please wait..."
|
|
1276
|
+
sudo reboot
|
|
1277
|
+
elif [ -t 0 ]; then
|
|
1278
|
+
if ask_yes_no "A reboot is required to apply the power-mode change — reboot now?"; then
|
|
1279
|
+
echo "Rebooting, please wait..."
|
|
1280
|
+
sudo reboot
|
|
1281
|
+
else
|
|
1282
|
+
echo "ℹ️ Reboot whenever convenient (the power mode is already scheduled)."
|
|
1283
|
+
fi
|
|
1284
|
+
else
|
|
1285
|
+
echo "ℹ️ Reboot required to apply the power-mode change (rerun with --reboot, or reboot manually)."
|
|
1286
|
+
fi
|
|
1287
|
+
fi
|