Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
H
hermes-node-agent
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
lisa
hermes-node-agent
Commits
9f498292
Commit
9f498292
authored
May 13, 2026
by
Lisa
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: harden installer permission defaults
parent
aa563f0a
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
208 additions
and
28 deletions
+208
-28
install.sh
install.sh
+197
-25
install.sh
package-hermes-node-agent/install.sh
+1
-1
config-template.json
windows/config-template.json
+10
-2
No files found.
install.sh
View file @
9f498292
...
@@ -42,14 +42,32 @@ if ! command -v pip3 &> /dev/null; then
...
@@ -42,14 +42,32 @@ if ! command -v pip3 &> /dev/null; then
exit
1
exit
1
fi
fi
# Install websockets library
# Install websockets library only if missing
# On modern Debian/Ubuntu with PEP 668, system pip may be externally managed.
echo
"[1/5] Installing Python dependencies..."
echo
"[1/5] Installing Python dependencies..."
pip3
install
--quiet
websockets 2>/dev/null
||
pip
install
--quiet
websockets
if
python3
-c
"import websockets"
2>/dev/null
;
then
if
[
$?
-ne
0
]
;
then
echo
"✓ websockets library already installed"
echo
"❌ Failed to install websockets. Try: pip3 install websockets"
else
PIP_OK
=
false
if
command
-v
pip3
>
/dev/null 2>&1
;
then
if
pip3
install
--quiet
--user
websockets 2>/dev/null
;
then
PIP_OK
=
true
fi
fi
if
[
"
$PIP_OK
"
!=
true
]
&&
command
-v
pip
>
/dev/null 2>&1
;
then
if
pip
install
--quiet
--user
websockets 2>/dev/null
;
then
PIP_OK
=
true
fi
fi
if
[
"
$PIP_OK
"
!=
true
]
;
then
echo
"❌ Failed to install websockets in user site-packages."
echo
" Try one of:"
echo
" - apt install python3-websockets"
echo
" - python3 -m venv ~/hermes-node-venv && ~/hermes-node-venv/bin/pip install websockets"
exit
1
exit
1
fi
echo
"✓ websockets library installed"
fi
fi
echo
"✓ websockets library installed"
# Determine install locations
# Determine install locations
if
[
"
$RUN_AS_ROOT
"
=
true
]
;
then
if
[
"
$RUN_AS_ROOT
"
=
true
]
;
then
...
@@ -71,30 +89,184 @@ cp "$(pwd)/node-agent/hermes_node_agent.py" "$AGENT_DIR/hermes-node-agent"
...
@@ -71,30 +89,184 @@ cp "$(pwd)/node-agent/hermes_node_agent.py" "$AGENT_DIR/hermes-node-agent"
chmod
+x
"
$AGENT_DIR
/hermes-node-agent"
chmod
+x
"
$AGENT_DIR
/hermes-node-agent"
echo
"✓ Installed:
$AGENT_DIR
/hermes-node-agent"
echo
"✓ Installed:
$AGENT_DIR
/hermes-node-agent"
# Create config file if missing
# Create or update config interactively, using existing values as defaults
echo
"[3/5] Checking configuration..."
echo
"[3/5] Configuring node..."
if
[
!
-f
"
$CONFIG_DIR
/config.json"
]
;
then
EXISTING_GATEWAY_URL
=
"wss://YOUR-GATEWAY-HOST:8765"
echo
"[4/5] Creating config file..."
EXISTING_NODE_NAME
=
"
$(
hostname
)
"
TOKEN
=
$(
python3
-c
"import secrets; print(secrets.token_hex(16))"
)
EXISTING_TOKEN
=
"
$(
python3
-c
"import secrets; print(secrets.token_hex(16))"
)
"
cat
>
"
$CONFIG_DIR
/config.json"
<<
EOF
EXISTING_ENABLE_BROWSER
=
"false"
EXISTING_ENABLE_COMPUTER_CONTROL
=
"false"
EXISTING_ENABLE_DESKTOP_OBSERVE
=
"false"
EXISTING_ENABLE_AUDIO_CONTROL
=
"false"
if
[
-f
"
$CONFIG_DIR
/config.json"
]
;
then
EXISTING_GATEWAY_URL
=
$(
python3 -
<<
PY
import json
from pathlib import Path
p = Path(r'''
$CONFIG_DIR
/config.json''')
data = json.loads(p.read_text())
print(data.get('gateway_url', 'wss://YOUR-GATEWAY-HOST:8765'))
PY
)
EXISTING_NODE_NAME
=
$(
python3 -
<<
PY
import json
from pathlib import Path
p = Path(r'''
$CONFIG_DIR
/config.json''')
data = json.loads(p.read_text())
print(data.get('node_name', '
$(
hostname
)
'))
PY
)
EXISTING_TOKEN
=
$(
python3 -
<<
PY
import json
from pathlib import Path
p = Path(r'''
$CONFIG_DIR
/config.json''')
data = json.loads(p.read_text())
print(data.get('token', ''))
PY
)
EXISTING_ENABLE_BROWSER
=
$(
python3 -
<<
PY
import json
from pathlib import Path
p = Path(r'''
$CONFIG_DIR
/config.json''')
data = json.loads(p.read_text())
print(str(bool(data.get('enable_browser', False))).lower())
PY
)
EXISTING_ENABLE_COMPUTER_CONTROL
=
$(
python3 -
<<
PY
import json
from pathlib import Path
p = Path(r'''
$CONFIG_DIR
/config.json''')
data = json.loads(p.read_text())
print(str(bool(data.get('enable_computer_control', False))).lower())
PY
)
EXISTING_ENABLE_DESKTOP_OBSERVE
=
$(
python3 -
<<
PY
import json
from pathlib import Path
p = Path(r'''
$CONFIG_DIR
/config.json''')
data = json.loads(p.read_text())
print(str(bool(data.get('enable_desktop_observe', False))).lower())
PY
)
EXISTING_ENABLE_AUDIO_CONTROL
=
$(
python3 -
<<
PY
import json
from pathlib import Path
p = Path(r'''
$CONFIG_DIR
/config.json''')
data = json.loads(p.read_text())
print(str(bool(data.get('enable_audio_control', False))).lower())
PY
)
echo
" Existing config found:
$CONFIG_DIR
/config.json"
fi
read
-r
-p
"Gateway URL [
$EXISTING_GATEWAY_URL
]: "
GATEWAY_URL
GATEWAY_URL
=
${
GATEWAY_URL
:-
$EXISTING_GATEWAY_URL
}
read
-r
-p
"Node name [
$EXISTING_NODE_NAME
]: "
NODE_NAME
NODE_NAME
=
${
NODE_NAME
:-
$EXISTING_NODE_NAME
}
read
-r
-p
"Token [
$EXISTING_TOKEN
]: "
TOKEN
TOKEN
=
${
TOKEN
:-
$EXISTING_TOKEN
}
DEFAULT_BROWSER_CHOICE
=
"n"
[
"
$EXISTING_ENABLE_BROWSER
"
=
"true"
]
&&
DEFAULT_BROWSER_CHOICE
=
"y"
read
-r
-p
"Enable browser_control? (y/N) [
$DEFAULT_BROWSER_CHOICE
]: "
ENABLE_BROWSER_INPUT
ENABLE_BROWSER_INPUT
=
${
ENABLE_BROWSER_INPUT
:-
$DEFAULT_BROWSER_CHOICE
}
case
"
$ENABLE_BROWSER_INPUT
"
in
y|Y|yes|YES
)
ENABLE_BROWSER
=
true
;;
*
)
ENABLE_BROWSER
=
false
;;
esac
DEFAULT_COMPUTER_CHOICE
=
"n"
[
"
$EXISTING_ENABLE_COMPUTER_CONTROL
"
=
"true"
]
&&
DEFAULT_COMPUTER_CHOICE
=
"y"
read
-r
-p
"Enable computer_control? (y/N) [
$DEFAULT_COMPUTER_CHOICE
]: "
ENABLE_COMPUTER_INPUT
ENABLE_COMPUTER_INPUT
=
${
ENABLE_COMPUTER_INPUT
:-
$DEFAULT_COMPUTER_CHOICE
}
case
"
$ENABLE_COMPUTER_INPUT
"
in
y|Y|yes|YES
)
ENABLE_COMPUTER_CONTROL
=
true
;;
*
)
ENABLE_COMPUTER_CONTROL
=
false
;;
esac
DEFAULT_DESKTOP_CHOICE
=
"n"
[
"
$EXISTING_ENABLE_DESKTOP_OBSERVE
"
=
"true"
]
&&
DEFAULT_DESKTOP_CHOICE
=
"y"
read
-r
-p
"Enable desktop_observe? (y/N) [
$DEFAULT_DESKTOP_CHOICE
]: "
ENABLE_DESKTOP_INPUT
ENABLE_DESKTOP_INPUT
=
${
ENABLE_DESKTOP_INPUT
:-
$DEFAULT_DESKTOP_CHOICE
}
case
"
$ENABLE_DESKTOP_INPUT
"
in
y|Y|yes|YES
)
ENABLE_DESKTOP_OBSERVE
=
true
;;
*
)
ENABLE_DESKTOP_OBSERVE
=
false
;;
esac
DEFAULT_AUDIO_CHOICE
=
"n"
[
"
$EXISTING_ENABLE_AUDIO_CONTROL
"
=
"true"
]
&&
DEFAULT_AUDIO_CHOICE
=
"y"
read
-r
-p
"Enable audio_control? (y/N) [
$DEFAULT_AUDIO_CHOICE
]: "
ENABLE_AUDIO_INPUT
ENABLE_AUDIO_INPUT
=
${
ENABLE_AUDIO_INPUT
:-
$DEFAULT_AUDIO_CHOICE
}
case
"
$ENABLE_AUDIO_INPUT
"
in
y|Y|yes|YES
)
ENABLE_AUDIO_CONTROL
=
true
;;
*
)
ENABLE_AUDIO_CONTROL
=
false
;;
esac
CAPABILITIES
=
'["exec"]'
if
[
"
$ENABLE_BROWSER
"
=
true
]
;
then
CAPABILITIES
=
$(
python3 -
<<
'
PY
' "
$CAPABILITIES
"
import json, sys
caps = json.loads(sys.argv[1])
if 'browser_control' not in caps:
caps.append('browser_control')
print(json.dumps(caps))
PY
)
fi
if
[
"
$ENABLE_COMPUTER_CONTROL
"
=
true
]
;
then
CAPABILITIES
=
$(
python3 -
<<
'
PY
' "
$CAPABILITIES
"
import json, sys
caps = json.loads(sys.argv[1])
if 'computer_control' not in caps:
caps.append('computer_control')
print(json.dumps(caps))
PY
)
fi
if
[
"
$ENABLE_DESKTOP_OBSERVE
"
=
true
]
;
then
CAPABILITIES
=
$(
python3 -
<<
'
PY
' "
$CAPABILITIES
"
import json, sys
caps = json.loads(sys.argv[1])
if 'desktop_observe' not in caps:
caps.append('desktop_observe')
print(json.dumps(caps))
PY
)
fi
if
[
"
$ENABLE_AUDIO_CONTROL
"
=
true
]
;
then
CAPABILITIES
=
$(
python3 -
<<
'
PY
' "
$CAPABILITIES
"
import json, sys
caps = json.loads(sys.argv[1])
if 'audio_control' not in caps:
caps.append('audio_control')
print(json.dumps(caps))
PY
)
fi
cat
>
"
$CONFIG_DIR
/config.json"
<<
EOF
{
{
"gateway_url": "
ws://YOUR-GATEWAY-HOST:8765
",
"gateway_url": "
$GATEWAY_URL
",
"node_name": "
$
(
hostname
)
",
"node_name": "
$
NODE_NAME
",
"token": "
$TOKEN
",
"token": "
$TOKEN
",
"sexec_path": "
$HOME
/.openclaw/skills/sexec/sexec.sh",
"reconnect_interval": 5,
"reconnect_interval": 5,
"heartbeat_interval": 30
"heartbeat_interval": 30,
"capabilities":
$CAPABILITIES
,
"enable_browser":
$ENABLE_BROWSER
,
"enable_computer_control":
$ENABLE_COMPUTER_CONTROL
,
"enable_desktop_observe":
$ENABLE_DESKTOP_OBSERVE
,
"enable_audio_control":
$ENABLE_AUDIO_CONTROL
,
"permissions": {
"deny": ["sudo", "su", "doas", "dd if=", "mkfs", "fdisk", "wipe"],
"ask": ["rm -rf", "dd if=", "> /dev/", "chmod", "chown", "mv /", ":/usr/", ":/etc/", ":/bin/", ":/sbin/"],
"allow": []
}
}
}
EOF
EOF
echo
"✓ Config:
$CONFIG_DIR
/config.json"
echo
""
echo
"[4/5] Wrote config:
$CONFIG_DIR
/config.json"
echo
"⚠️ IMPORTANT: Edit
$CONFIG_DIR
/config.json"
echo
" → Set gateway_url to your gateway (e.g., wss://zeiss:8765)"
echo
" → Set token to match gateway's token for this node"
else
echo
"[4/5] Config already exists → skipping"
echo
" Config:
$CONFIG_DIR
/config.json"
fi
# Install SysV init service (root only)
# Install SysV init service (root only)
if
[
"
$RUN_AS_ROOT
"
=
true
]
&&
[
-f
"
$SERVICE_FILE
"
]
;
then
if
[
"
$RUN_AS_ROOT
"
=
true
]
&&
[
-f
"
$SERVICE_FILE
"
]
;
then
...
...
package-hermes-node-agent/install.sh
View file @
9f498292
...
@@ -261,7 +261,7 @@ cat > "$CONFIG_DIR/config.json" << EOF
...
@@ -261,7 +261,7 @@ cat > "$CONFIG_DIR/config.json" << EOF
"permissions": {
"permissions": {
"deny": ["sudo", "su", "doas", "dd if=", "mkfs", "fdisk", "wipe"],
"deny": ["sudo", "su", "doas", "dd if=", "mkfs", "fdisk", "wipe"],
"ask": ["rm -rf", "dd if=", "> /dev/", "chmod", "chown", "mv /", ":/usr/", ":/etc/", ":/bin/", ":/sbin/"],
"ask": ["rm -rf", "dd if=", "> /dev/", "chmod", "chown", "mv /", ":/usr/", ":/etc/", ":/bin/", ":/sbin/"],
"allow": ["cat", "ls", "pwd", "cd", "echo", "head", "tail", "wc", "grep", "find", "stat", "file", "which", "whereis", "df", "du", "free", "uptime", "who", "w", "last", "ps", "top", "htop", "uname", "env", "printenv"
]
"allow": [
]
}
}
}
}
EOF
EOF
...
...
windows/config-template.json
View file @
9f498292
...
@@ -2,8 +2,16 @@
...
@@ -2,8 +2,16 @@
"gateway_url"
:
"wss://YOUR-GATEWAY:8765"
,
"gateway_url"
:
"wss://YOUR-GATEWAY:8765"
,
"node_name"
:
"WIN-NODE"
,
"node_name"
:
"WIN-NODE"
,
"token"
:
"YOUR-TOKEN-HERE"
,
"token"
:
"YOUR-TOKEN-HERE"
,
"sexec_path"
:
"./sexec-template.ps1"
,
"reconnect_interval"
:
5
,
"reconnect_interval"
:
5
,
"heartbeat_interval"
:
30
,
"heartbeat_interval"
:
30
,
"capabilities"
:
[
"exec"
,
"browser_control"
,
"computer_control"
]
"capabilities"
:
[
"exec"
],
"enable_browser"
:
false
,
"enable_computer_control"
:
false
,
"enable_desktop_observe"
:
false
,
"enable_audio_control"
:
false
,
"permissions"
:
{
"deny"
:
[
"sudo"
,
"su"
,
"doas"
,
"dd if="
,
"mkfs"
,
"fdisk"
,
"wipe"
],
"ask"
:
[
"rm -rf"
,
"dd if="
,
"> /dev/"
,
"chmod"
,
"chown"
,
"mv /"
,
":/usr/"
,
":/etc/"
,
":/bin/"
,
":/sbin/"
],
"allow"
:
[]
}
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment