Hermes Agent: install, update and run everything as the hermes user (#18203)

hermes update refuses a checkout its caller does not own, so the
root-run update stopped working for every install. The installer and
hermes update now run as hermes, and the dashboard becomes a user unit
next to the gateways, so Hermes Desktop and the dashboard can update
and restart it themselves. libatomic1 is installed up front, the one
package Hermes would otherwise ask sudo for.

update moves existing containers over once: the system unit becomes a
user unit, leftover root processes are stopped and /home/hermes is
handed back to hermes.
This commit is contained in:
CanbiZ (MickLesk)
2026-10-25 19:51:57 +01:00
committed by GitHub
parent 65a4410c97
commit 94a3374039
2 changed files with 61 additions and 43 deletions
+44 -20
View File
@@ -33,36 +33,60 @@ function update_script() {
confirm_external_installer "https://hermes-agent.nousresearch.com/" "hermes update (https://hermes-agent.nousresearch.com/)" confirm_external_installer "https://hermes-agent.nousresearch.com/" "hermes update (https://hermes-agent.nousresearch.com/)"
msg_info "Stopping Services" if [[ -f /etc/systemd/system/hermes-dashboard.service ]]; then
systemctl stop hermes-dashboard msg_info "Moving Hermes to its own user"
msg_ok "Stopped Services" systemctl disable -q --now hermes-dashboard
rm -f /etc/systemd/system/hermes-dashboard.service
msg_info "Updating Hermes Agent" systemctl daemon-reload
$STD setsid --wait bash -c ' mapfile -t root_hermes_pids < <(ps -eo user=,pid=,args= | awk '$1 == "root" && /\/home\/hermes\/\.hermes\// && /hermes_cli[.]main/ { print $2 }')
set -a; source /etc/default/hermes; set +a if ((${#root_hermes_pids[@]})); then
/home/hermes/.local/bin/hermes update --yes kill -TERM "${root_hermes_pids[@]}" 2>/dev/null || true
' for pid in "${root_hermes_pids[@]}"; do
mapfile -t root_gateway_pids < <(ps -eo user=,pid=,args= | awk '$1 == "root" && /\/home\/hermes\/\.hermes\// && /hermes_cli[.]main/ && / gateway run --replace$/ { print $2 }') while kill -0 "$pid" 2>/dev/null; do
if ((${#root_gateway_pids[@]})); then sleep 0.1
kill -TERM "${root_gateway_pids[@]}" done
for pid in "${root_gateway_pids[@]}"; do
while kill -0 "$pid" 2>/dev/null; do
sleep 0.1
done done
done fi
fi ensure_dependencies libatomic1
if ps -eo user=,args= | awk '$1 == "root" && /\/home\/hermes\/\.hermes\// { found = 1 } END { exit !found }'; then sed -i '/^HOME=/d' /etc/default/hermes
msg_warn "A root-owned Hermes process is still running; it may keep writing root-owned files" grep -q '^GATEWAY_HEALTH_URL=' /etc/default/hermes || echo "GATEWAY_HEALTH_URL=http://127.0.0.1:8642/health" >>/etc/default/hermes
mkdir -p /home/hermes/.config/systemd/user
cat <<EOF >/home/hermes/.config/systemd/user/hermes-dashboard.service
[Unit]
Description=Hermes Agent Web Dashboard
After=network-online.target
[Service]
Type=simple
UMask=0077
WorkingDirectory=%h
EnvironmentFile=/etc/default/hermes
ExecStart=%h/.local/bin/hermes dashboard --host 127.0.0.1 --port 9119 --no-open
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
EOF
msg_ok "Moved Hermes to its own user"
fi fi
# A writer racing chown makes files vanish between readdir and chown; one retry covers it. # A writer racing chown makes files vanish between readdir and chown; one retry covers it.
if ! chown -R hermes:hermes /home/hermes 2>/dev/null; then if ! chown -R hermes:hermes /home/hermes 2>/dev/null; then
sleep 1 sleep 1
chown -R hermes:hermes /home/hermes || msg_warn "Could not take ownership of everything under /home/hermes" chown -R hermes:hermes /home/hermes || msg_warn "Could not take ownership of everything under /home/hermes"
fi fi
$STD runuser -l hermes -c 'systemctl --user daemon-reload && systemctl --user enable hermes-dashboard'
msg_info "Updating Hermes Agent"
$STD setsid --wait runuser -l hermes -c '
set -a; source /etc/default/hermes; set +a
hermes update --yes
'
msg_ok "Updated Hermes Agent" msg_ok "Updated Hermes Agent"
msg_info "Starting Services" msg_info "Starting Services"
systemctl start hermes-dashboard $STD runuser -l hermes -c 'systemctl --user start hermes-dashboard'
msg_ok "Started Services" msg_ok "Started Services"
msg_ok "Updated successfully!" msg_ok "Updated successfully!"
exit exit
+17 -23
View File
@@ -14,7 +14,9 @@ network_check
update_os update_os
msg_info "Installing Dependencies" msg_info "Installing Dependencies"
$STD apt install -y git $STD apt install -y \
git \
libatomic1
msg_ok "Installed Dependencies" msg_ok "Installed Dependencies"
NODE_VERSION="22" setup_nodejs NODE_VERSION="22" setup_nodejs
@@ -27,9 +29,9 @@ msg_ok "Created Hermes User"
msg_info "Configuring Service Environment" msg_info "Configuring Service Environment"
cat <<EOF >/etc/default/hermes cat <<EOF >/etc/default/hermes
HOME=/home/hermes
PATH=/home/hermes/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PATH=/home/hermes/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NODE_OPTIONS=${NODE_OPTIONS} NODE_OPTIONS=${NODE_OPTIONS}
GATEWAY_HEALTH_URL=http://127.0.0.1:8642/health
EOF EOF
msg_ok "Configured Service Environment" msg_ok "Configured Service Environment"
@@ -38,68 +40,60 @@ confirm_external_installer "https://hermes-agent.nousresearch.com/" "https://her
msg_info "Installing Hermes Agent" msg_info "Installing Hermes Agent"
curl_with_retry "https://hermes-agent.nousresearch.com/install.sh" /tmp/hermes-install.sh || curl_with_retry "https://hermes-agent.nousresearch.com/install.sh" /tmp/hermes-install.sh ||
curl_with_retry "https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh" /tmp/hermes-install.sh curl_with_retry "https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh" /tmp/hermes-install.sh
$STD setsid --wait bash -c ' chmod 644 /tmp/hermes-install.sh
$STD setsid --wait runuser -l hermes -c '
set -a; source /etc/default/hermes; set +a set -a; source /etc/default/hermes; set +a
export npm_config_yes=true export npm_config_yes=true
bash /tmp/hermes-install.sh --skip-setup --hermes-home /home/hermes/.hermes --dir /home/hermes/.hermes/hermes-agent bash /tmp/hermes-install.sh --skip-setup --hermes-home ~/.hermes --dir ~/.hermes/hermes-agent
' '
rm -f /tmp/hermes-install.sh rm -f /tmp/hermes-install.sh
chown -R hermes:hermes /home/hermes
chmod 750 /home/hermes chmod 750 /home/hermes
chmod 700 /home/hermes/.hermes chmod 700 /home/hermes/.hermes
git config --system --add safe.directory /home/hermes/.hermes/hermes-agent 2>/dev/null || true
msg_ok "Installed Hermes Agent" msg_ok "Installed Hermes Agent"
msg_info "Configuring API Server" msg_info "Configuring API Server"
API_SERVER_KEY=$(random_password 32) API_SERVER_KEY=$(random_password 32)
mkdir -p /home/hermes/.hermes
cat <<EOF >/home/hermes/.hermes/.env cat <<EOF >/home/hermes/.hermes/.env
API_SERVER_ENABLED=true API_SERVER_ENABLED=true
API_SERVER_HOST=0.0.0.0 API_SERVER_HOST=0.0.0.0
API_SERVER_PORT=8642 API_SERVER_PORT=8642
API_SERVER_KEY=${API_SERVER_KEY} API_SERVER_KEY=${API_SERVER_KEY}
EOF EOF
chown hermes:hermes /home/hermes/.hermes/.env
chmod 600 /home/hermes/.hermes/.env chmod 600 /home/hermes/.hermes/.env
msg_ok "Configured API Server" msg_ok "Configured API Server"
msg_info "Creating Dashboard Service" msg_info "Creating Dashboard Service"
cat <<EOF >/etc/systemd/system/hermes-dashboard.service mkdir -p /home/hermes/.config/systemd/user
cat <<EOF >/home/hermes/.config/systemd/user/hermes-dashboard.service
[Unit] [Unit]
Description=Hermes Agent Web Dashboard Description=Hermes Agent Web Dashboard
After=network-online.target After=network-online.target
Wants=network-online.target
[Service] [Service]
Type=simple Type=simple
User=hermes
Group=hermes
UMask=0077 UMask=0077
WorkingDirectory=/home/hermes WorkingDirectory=%h
ExecStart=/home/hermes/.local/bin/hermes dashboard --host 127.0.0.1 --port 9119 --no-open
EnvironmentFile=/etc/default/hermes EnvironmentFile=/etc/default/hermes
ExecStart=%h/.local/bin/hermes dashboard --host 127.0.0.1 --port 9119 --no-open
Restart=on-failure Restart=on-failure
RestartSec=5 RestartSec=5
ProtectProc=invisible
ProcSubset=pid
[Install] [Install]
WantedBy=multi-user.target WantedBy=default.target
EOF EOF
systemctl enable -q --now hermes-dashboard chown -R hermes:hermes /home/hermes/.config
$STD runuser -l hermes -c 'systemctl --user daemon-reload && systemctl --user enable --now hermes-dashboard'
msg_ok "Created Dashboard Service" msg_ok "Created Dashboard Service"
msg_info "Creating Setup Helper" msg_info "Creating Setup Helper"
cat <<'SETUP' >/usr/bin/hermes-setup cat <<'SETUP' >/usr/bin/hermes-setup
#!/usr/bin/env bash #!/usr/bin/env bash
set -a; source /etc/default/hermes; set +a su - hermes -c 'set -a; source /etc/default/hermes; set +a; hermes setup'
/home/hermes/.local/bin/hermes setup
chown -R hermes:hermes /home/hermes
chmod 750 /home/hermes
chmod 700 /home/hermes/.hermes
if [[ -f /home/hermes/.config/systemd/user/hermes-gateway.service ]]; then if [[ -f /home/hermes/.config/systemd/user/hermes-gateway.service ]]; then
su - hermes -c 'systemctl --user enable --now hermes-gateway' su - hermes -c 'systemctl --user enable --now hermes-gateway'
fi fi
echo "Hermes setup complete. File permissions restored." echo "Hermes setup complete."
SETUP SETUP
chmod +x /usr/bin/hermes-setup chmod +x /usr/bin/hermes-setup
msg_ok "Created Setup Helper" msg_ok "Created Setup Helper"