Skip to content
Open
87 changes: 87 additions & 0 deletions ct/browseros-neo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/DevScripts/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
# Author: masterde
# License: MIT | https://github.com/community-scripts/DevScripts/raw/main/LICENSE
# Source: https://github.com/browseros-ai/BrowserOS

APP="BrowserOS-Neo"
var_tags="${var_tags:-browser;ai;mcp}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-10}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-no}" # GitHub releases ship an amd64 .deb only
var_unprivileged="${var_unprivileged:-1}"

export var_tailscale="${var_tailscale:-}"
export var_ts_authkey="${var_ts_authkey:-}"

header_info "$APP"
variables
color
catch_errors

function update_script() {
header_info
check_container_storage
check_container_resources

if [[ ! -f /etc/systemd/system/browseros-neo.service ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi

if check_for_gh_release "browseros-neo" "browseros-ai/BrowserOS" "" "" "browserclaw/v"; then
msg_info "Stopping BrowserOS Neo"
systemctl stop browseros-neo
msg_ok "Stopped BrowserOS Neo"

fetch_and_deploy_gh_release "browseros-neo" "browseros-ai/BrowserOS" "binary" "latest" "" "" "browserclaw/v"

msg_info "Starting BrowserOS Neo"
systemctl start browseros-neo
msg_ok "Started BrowserOS Neo"
msg_ok "Updated successfully!"
fi
exit
}

# Decided before the container exists: Tailscale needs the TUN device (var_tun).
if [[ -z "$var_tailscale" ]] && command -v pveversion >/dev/null 2>&1; then
if [[ -n "$var_ts_authkey" ]] || prompt_confirm "Add BrowserOS Neo to your Tailscale tailnet for HTTPS access?" "n" 120; then
var_tailscale="yes"
else
var_tailscale="no"
fi
fi
[[ "$var_tailscale" == "yes" ]] && export var_tun="yes"

start
build_container
description

msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Open the browser viewer (user: neo) using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:6080${CL}"
echo -e "${INFO}${YW} Connect MCP clients to the following URL with an Authorization: Bearer header:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:9010/mcp${CL}"
TS_NAME=""
[[ "$var_tailscale" == "yes" ]] && TS_NAME=$(pct exec "$CTID" -- bash -c "tailscale status --json 2>/dev/null | jq -r '.Self.DNSName // empty'" | sed 's/\.$//')
if [[ -n "$TS_NAME" ]]; then
echo -e "${INFO}${YW} On your tailnet:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}https://${TS_NAME}:8443${CL} (viewer)"
echo -e "${TAB}${GATEWAY}${BGN}https://${TS_NAME}/mcp${CL} (MCP)"
fi
echo -e "${INFO}${YW} The viewer password and MCP token are in /opt/browseros-neo_data/.env, show them with:${CL}"
echo -e "${TAB}pct exec ${CTID} -- cat /opt/browseros-neo_data/.env"
echo -e "${INFO}${YW} Add it to Claude Code (token from the .env file):${CL}"
if [[ -n "$TS_NAME" ]]; then
echo -e "${TAB}claude mcp add --transport http browseros-neo https://${TS_NAME}/mcp --header \"Authorization: Bearer <MCP_TOKEN>\""
else
echo -e "${TAB}claude mcp add --transport http browseros-neo http://${IP}:9010/mcp --header \"Authorization: Bearer <MCP_TOKEN>\""
fi
289 changes: 289 additions & 0 deletions install/browseros-neo-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
#!/usr/bin/env bash

# Copyright (c) 2021-2026 community-scripts ORG
# Author: masterde
# License: MIT | https://github.com/community-scripts/DevScripts/raw/main/LICENSE
# Source: https://github.com/browseros-ai/BrowserOS

source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os

# libasound2t64 goes first: the .deb's unversioned "Depends: libasound2" can
# otherwise resolve to an OSS shim, and BrowserOS Neo then fails to start.
msg_info "Installing Dependencies"
$STD apt install -y libasound2t64
$STD apt install -y \
tigervnc-standalone-server \
tigervnc-tools \
openbox \
novnc \
websockify \
supervisor \
nginx \
openssl \
fonts-liberation \
fonts-noto-core \
fonts-noto-color-emoji
msg_ok "Installed Dependencies"
systemctl disable -q --now supervisor

fetch_and_deploy_gh_release "browseros-neo" "browseros-ai/BrowserOS" "binary" "latest" "" "" "browserclaw/v"

msg_info "Configuring BrowserOS Neo"
mkdir -p /opt/browseros-neo_data/{profile,browserclaw,downloads} /etc/browseros-neo /var/log/browseros-neo /root/.config/gtk-3.0
ln -sfn /opt/browseros-neo_data/downloads /root/Downloads
VIEWER_PASS=$(random_password)
MCP_TOKEN=$(openssl rand -hex 32)
cat <<EOF >/opt/browseros-neo_data/.env
VIEWER_USER=neo
VIEWER_PASSWORD=${VIEWER_PASS}
MCP_TOKEN=${MCP_TOKEN}
EOF
chmod 600 /opt/browseros-neo_data/.env

# Openbox only keeps BrowserOS Neo maximized and re-fits it when noVNC resizes the
# screen; there is no desktop or taskbar, so the minimize button is hidden too.
cat <<'EOF' >/etc/browseros-neo/openbox-rc.xml
<?xml version="1.0" encoding="UTF-8"?>
<openbox_config xmlns="http://openbox.org/3.4/rc">
<focus>
<focusNew>yes</focusNew>
<followMouse>no</followMouse>
</focus>
<placement>
<policy>Smart</policy>
<center>yes</center>
</placement>
<desktops>
<number>1</number>
</desktops>
<keyboard/>
<applications>
<application type="normal">
<decor>no</decor>
<maximized>yes</maximized>
</application>
</applications>
</openbox_config>
EOF
cat <<'EOF' >/root/.config/gtk-3.0/settings.ini
[Settings]
gtk-decoration-layout=:maximize,close
EOF
# --no-sandbox is required to run Chromium as root; drop its warning bar for that flag.
mkdir -p /etc/chromium/policies/managed
cat <<'EOF' >/etc/chromium/policies/managed/browseros-neo.json
{"CommandLineFlagSecurityWarningsEnabled": false}
EOF
msg_ok "Configured BrowserOS Neo"

msg_info "Configuring Nginx"
cat <<EOF >/etc/nginx/browseros-neo.htpasswd
neo:$(openssl passwd -apr1 "${VIEWER_PASS}")
EOF
chown root:www-data /etc/nginx/browseros-neo.htpasswd
chmod 640 /etc/nginx/browseros-neo.htpasswd
# Port 9010 rewrites Host to loopback: BrowserOS Neo's MCP server only accepts
# Host: localhost/127.0.0.1 (DNS-rebinding guard) and has no setting to widen it.
cat <<EOF >/etc/nginx/sites-available/browseros-neo
map \$http_upgrade \$connection_upgrade {
default upgrade;
'' close;
}

server {
listen 6080;
auth_basic "BrowserOS Neo";
auth_basic_user_file /etc/nginx/browseros-neo.htpasswd;

location = / {
return 302 /vnc.html?autoconnect=1&resize=remote&reconnect=1&reconnect_delay=1000;
}

location / {
proxy_pass http://127.0.0.1:6081;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection \$connection_upgrade;
proxy_read_timeout 1d;
}
}

server {
listen 9010;

location / {
if (\$http_authorization != "Bearer ${MCP_TOKEN}") {
return 401;
}
proxy_pass http://127.0.0.1:19010;
proxy_http_version 1.1;
proxy_set_header Host 127.0.0.1:19010;
proxy_set_header Origin "";
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 1d;
}
}
EOF
nginx_enable_site "browseros-neo"
msg_ok "Configured Nginx"

msg_info "Creating Service"
cat <<'EOF' >/etc/browseros-neo/supervisord.conf
[supervisord]
nodaemon=true
user=root
logfile=/var/log/browseros-neo/supervisord.log
pidfile=/run/browseros-neo/supervisord.pid

[unix_http_server]
file=/run/browseros-neo/supervisor.sock
chmod=0700

[supervisorctl]
serverurl=unix:///run/browseros-neo/supervisor.sock

[rpcinterface:supervisor]
supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface

[program:xvnc]
command=/usr/bin/Xvnc :1 -geometry 1600x1000 -depth 24 -SecurityTypes None -localhost -rfbport 5900 -AlwaysShared -nolisten tcp
priority=100
autorestart=true
stdout_logfile=/var/log/browseros-neo/xvnc.log
redirect_stderr=true

[program:openbox]
environment=DISPLAY=":1",HOME="/root"
command=/usr/bin/openbox --config-file /etc/browseros-neo/openbox-rc.xml
priority=200
autorestart=true
stdout_logfile=/var/log/browseros-neo/openbox.log
redirect_stderr=true

[program:vncconfig]
environment=DISPLAY=":1"
command=/usr/bin/vncconfig -nowin
priority=200
autorestart=true
stdout_logfile=/var/log/browseros-neo/vncconfig.log
redirect_stderr=true

[program:websockify]
command=/usr/bin/websockify --web /usr/share/novnc 127.0.0.1:6081 127.0.0.1:5900
priority=200
autorestart=true
stdout_logfile=/var/log/browseros-neo/websockify.log
redirect_stderr=true

[program:browseros-neo]
environment=DISPLAY=":1",HOME="/root",LANG="C.UTF-8",XDG_RUNTIME_DIR="/run/browseros-neo",BROWSERCLAW_DIR="/opt/browseros-neo_data/browserclaw",BROWSERCLAW_MODE="hosted"
command=/usr/bin/browserclaw --no-sandbox --user-data-dir=/opt/browseros-neo_data/profile --no-first-run --no-default-browser-check --password-store=basic --browseros-proxy-port=19010 --browseros-cdp-port=9110 --browseros-server-port=9210 --hide-crash-restore-bubble --start-maximized
priority=300
autorestart=true
startsecs=5
stopwaitsecs=20
stdout_logfile=/var/log/browseros-neo/browseros-neo.log
redirect_stderr=true
EOF
# The profile lock outlives an unclean stop; nothing else uses this profile.
cat <<'EOF' >/etc/systemd/system/browseros-neo.service
[Unit]
Description=BrowserOS Neo
After=network.target

[Service]
Type=simple
RuntimeDirectory=browseros-neo
RuntimeDirectoryMode=0700
ExecStartPre=/bin/rm -f /opt/browseros-neo_data/profile/SingletonLock /opt/browseros-neo_data/profile/SingletonSocket /opt/browseros-neo_data/profile/SingletonCookie
ExecStart=/usr/bin/supervisord -c /etc/browseros-neo/supervisord.conf -n
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now browseros-neo
msg_ok "Created Service"

msg_info "Waiting for BrowserOS Neo"
for _ in $(seq 1 120); do
curl -fsS http://127.0.0.1:19010/system/ready >/dev/null 2>&1 && break
sleep 1
done
curl -fsS -X PUT http://127.0.0.1:19010/api/v1/settings/agent \
-H 'Content-Type: application/json' -d '{"humanHelpEnabled":false}' >/dev/null
msg_ok "BrowserOS Neo is ready"

if [[ "${var_tailscale:-no}" == "yes" ]]; then
setup_deb822_repo \
"tailscale" \
"https://pkgs.tailscale.com/stable/$(get_os_info id)/$(get_os_info codename).noarmor.gpg" \
"https://pkgs.tailscale.com/stable/$(get_os_info id)/" \
"$(get_os_info codename)"
msg_info "Installing Tailscale"
$STD apt install -y tailscale
msg_ok "Installed Tailscale"

# A Tailscale failure must not fail the install: BrowserOS Neo already works on the LAN.
if [[ -n "${var_ts_authkey:-}" ]]; then
# The key goes through a mode-600 file so it never shows up in the process list.
(
umask 077
cat <<EOF >/root/.ts-authkey
${var_ts_authkey}
EOF
)
tailscale up --auth-key=file:/root/.ts-authkey --hostname="$(hostname)" || true
rm -f /root/.ts-authkey
else
echo -e "${INFO}${YW} Open the URL below and approve the device to add BrowserOS Neo to your tailnet:${CL}"
tailscale up --hostname="$(hostname)" --timeout=10m || true
fi

if [[ "$(tailscale status --json 2>/dev/null | jq -r '.BackendState')" == "Running" ]]; then
msg_info "Serving BrowserOS Neo over HTTPS on your tailnet"
$STD tailscale serve --bg --https=443 http://127.0.0.1:9010
$STD tailscale serve --bg --https=8443 http://127.0.0.1:6080
msg_ok "Serving BrowserOS Neo over HTTPS on your tailnet"
else
msg_warn "Tailscale login did not complete. Once the device is approved, run in the container:"
echo -e "${TAB}tailscale serve --bg --https=443 http://127.0.0.1:9010"
echo -e "${TAB}tailscale serve --bg --https=8443 http://127.0.0.1:6080"
echo -e "${TAB}(and 'tailscale up' first if you never opened the login URL)"
fi
fi

msg_info "Configuring Login Hints"
cat <<'EOF' >/etc/profile.d/browseros-neo-hint.sh
if [ "$(id -u)" -eq 0 ]; then
_bn_ip=$(hostname -I | awk '{print $1}')
_bn_ts=""
command -v tailscale >/dev/null 2>&1 && _bn_ts=$(tailscale status --json 2>/dev/null | jq -r '.Self.DNSName // empty' | sed 's/\.$//')
_bn_mcp="http://${_bn_ip}:9010/mcp"
echo " BrowserOS Neo viewer: http://${_bn_ip}:6080 (user neo)"
echo " BrowserOS Neo MCP: ${_bn_mcp}"
if [ -n "$_bn_ts" ]; then
_bn_mcp="https://${_bn_ts}/mcp"
echo " Tailnet viewer: https://${_bn_ts}:8443"
echo " Tailnet MCP: ${_bn_mcp}"
fi
echo " Viewer password and MCP token: cat /opt/browseros-neo_data/.env"
echo " Claude Code: claude mcp add --transport http browseros-neo ${_bn_mcp} --header \"Authorization: Bearer <MCP_TOKEN>\""
echo ""
unset _bn_ip _bn_ts _bn_mcp
fi
EOF
msg_ok "Configured Login Hints"

motd_ssh
customize
cleanup_lxc
Loading
Loading