Files
gitlab-instance-0a899031_salon/scripts/deploy-neko-dual.sh
2026-03-23 02:13:39 -05:00

314 lines
9.4 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "missing required command: $1" >&2
exit 1
fi
}
require_cmd docker
require_cmd getent
require_cmd ip
require_cmd awk
require_cmd sudo
ufw_udp_range() {
echo "${1//-/:}/udp"
}
if [[ "${EUID}" -ne 0 ]]; then
sudo -v
fi
if [[ "$(id -u)" -eq 0 && -n "${SUDO_USER:-}" ]]; then
DEFAULT_HOME="$(getent passwd "$SUDO_USER" | awk -F: '{print $6}')"
else
DEFAULT_HOME="$HOME"
fi
HOME_DIR="${HOME_DIR:-$DEFAULT_HOME}"
LAN_IP="${LAN_IP:-$(ip route get 1 | awk '{print $7; exit}')}"
NEKO_USER_PASSWORD="${NEKO_USER_PASSWORD:-user123}"
NEKO_ADMIN_PASSWORD="${NEKO_ADMIN_PASSWORD:-admin123}"
PROFILE_UID="${PROFILE_UID:-1000}"
PROFILE_GID="${PROFILE_GID:-1000}"
CHROMIUM_DIR="${CHROMIUM_DIR:-$HOME_DIR/neko-browser}"
GOOGLE_CHROME_DIR="${GOOGLE_CHROME_DIR:-$HOME_DIR/neko-google-chrome}"
CHROMIUM_PROFILE_DIR="${CHROMIUM_PROFILE_DIR:-/root/neko-chromium-profile}"
GOOGLE_CHROME_PROFILE_DIR="${GOOGLE_CHROME_PROFILE_DIR:-/root/neko-google-chrome-profile}"
CHROMIUM_HTTP_PORT="${CHROMIUM_HTTP_PORT:-8080}"
GOOGLE_CHROME_HTTP_PORT="${GOOGLE_CHROME_HTTP_PORT:-8081}"
CHROMIUM_UDP_RANGE="${CHROMIUM_UDP_RANGE:-56000-56100}"
GOOGLE_CHROME_UDP_RANGE="${GOOGLE_CHROME_UDP_RANGE:-56200-56300}"
CHROMIUM_DISPLAY="${CHROMIUM_DISPLAY:-:99.0}"
GOOGLE_CHROME_DISPLAY="${GOOGLE_CHROME_DISPLAY:-:100.0}"
mkdir -p \
"$CHROMIUM_DIR/policies" \
"$GOOGLE_CHROME_DIR/policies"
sudo mkdir -p \
"$CHROMIUM_PROFILE_DIR" \
"$GOOGLE_CHROME_PROFILE_DIR"
sudo chown -R "${PROFILE_UID}:${PROFILE_GID}" \
"$CHROMIUM_PROFILE_DIR" \
"$GOOGLE_CHROME_PROFILE_DIR"
cat >"$CHROMIUM_DIR/docker-compose.yaml" <<EOF
services:
neko-browser:
image: ghcr.io/m1k1o/neko/chromium:latest
restart: unless-stopped
network_mode: host
shm_size: "8gb"
cap_add:
- SYS_ADMIN
environment:
DISPLAY: "${CHROMIUM_DISPLAY}"
NEKO_DESKTOP_SCREEN: "1920x1080@60"
NEKO_SERVER_BIND: ":${CHROMIUM_HTTP_PORT}"
NEKO_MEMBER_PROVIDER: "multiuser"
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: "${NEKO_USER_PASSWORD}"
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: "${NEKO_ADMIN_PASSWORD}"
NEKO_WEBRTC_EPR: "${CHROMIUM_UDP_RANGE}"
NEKO_WEBRTC_ICELITE: "1"
NEKO_WEBRTC_NAT1TO1: "${LAN_IP}"
NEKO_PLUGINS_ENABLED: "true"
NEKO_PLUGINS_DIR: "/etc/neko/plugins/"
volumes:
- ${CHROMIUM_PROFILE_DIR}:/home/neko/.config/chromium
- ${CHROMIUM_DIR}/policies:/etc/chromium/policies/managed:ro
- ${CHROMIUM_DIR}/chromium.conf:/etc/neko/supervisord/chromium.conf:ro
EOF
cat >"$CHROMIUM_DIR/chromium.conf" <<'EOF'
[program:chromium]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/chromium
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/chromium
--password-store=basic
--no-first-run
--start-maximized
--force-dark-mode
--disable-file-system
--disable-gpu
--disable-software-rasterizer
--disable-dev-shm-usage
--disable-background-timer-throttling
--disable-backgrounding-occluded-windows
--disable-renderer-backgrounding
stopsignal=INT
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/chromium.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
EOF
cat >"$CHROMIUM_DIR/policies/policies.json" <<'EOF'
{
"ExtensionInstallForcelist": [
"dhdgffkkebhmkfjojejmpbldmpobfkfo;https://clients2.google.com/service/update2/crx"
],
"ExtensionInstallAllowlist": [
"*"
],
"ExtensionInstallBlocklist": [],
"DownloadRestrictions": 0,
"AllowFileSelectionDialogs": true,
"URLAllowlist": ["file:///home/neko/Downloads/*"],
"DefaultCookiesSetting": 1,
"RestoreOnStartup": 1
}
EOF
cat >"$GOOGLE_CHROME_DIR/docker-compose.yaml" <<EOF
services:
neko-google-chrome:
image: ghcr.io/m1k1o/neko/google-chrome:latest
restart: unless-stopped
network_mode: host
shm_size: "8gb"
cap_add:
- SYS_ADMIN
environment:
DISPLAY: "${GOOGLE_CHROME_DISPLAY}"
NEKO_DESKTOP_SCREEN: "1920x1080@60"
NEKO_SERVER_BIND: ":${GOOGLE_CHROME_HTTP_PORT}"
NEKO_MEMBER_PROVIDER: "multiuser"
NEKO_MEMBER_MULTIUSER_USER_PASSWORD: "${NEKO_USER_PASSWORD}"
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: "${NEKO_ADMIN_PASSWORD}"
NEKO_WEBRTC_EPR: "${GOOGLE_CHROME_UDP_RANGE}"
NEKO_WEBRTC_ICELITE: "1"
NEKO_WEBRTC_NAT1TO1: "${LAN_IP}"
NEKO_PLUGINS_ENABLED: "true"
NEKO_PLUGINS_DIR: "/etc/neko/plugins/"
volumes:
- ${GOOGLE_CHROME_PROFILE_DIR}:/home/neko/.config/google-chrome
- ${GOOGLE_CHROME_DIR}/policies:/etc/opt/chrome/policies/managed:ro
- ${GOOGLE_CHROME_DIR}/google-chrome.conf:/etc/neko/supervisord/google-chrome.conf:ro
EOF
cat >"$GOOGLE_CHROME_DIR/google-chrome.conf" <<'EOF'
[program:google-chrome]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/google-chrome
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/google-chrome
--password-store=basic
--no-first-run
--start-maximized
--force-dark-mode
--disable-file-system
--disable-gpu
--disable-software-rasterizer
--disable-dev-shm-usage
--disable-background-timer-throttling
--disable-backgrounding-occluded-windows
--disable-renderer-backgrounding
stopsignal=INT
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/google-chrome.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
EOF
cat >"$GOOGLE_CHROME_DIR/policies/policies.json" <<'EOF'
{
"ExtensionInstallForcelist": [
"dhdgffkkebhmkfjojejmpbldmpobfkfo;https://clients2.google.com/service/update2/crx"
],
"ExtensionInstallAllowlist": [
"*"
],
"ExtensionInstallBlocklist": [],
"DownloadRestrictions": 0,
"AllowFileSelectionDialogs": true,
"URLAllowlist": ["file:///home/neko/Downloads/*"],
"DefaultCookiesSetting": 1,
"RestoreOnStartup": 1
}
EOF
sudo ufw allow "${CHROMIUM_HTTP_PORT}" >/dev/null 2>&1 || true
sudo ufw allow "${GOOGLE_CHROME_HTTP_PORT}" >/dev/null 2>&1 || true
sudo ufw allow "$(ufw_udp_range "${CHROMIUM_UDP_RANGE}")" >/dev/null 2>&1 || true
sudo ufw allow "$(ufw_udp_range "${GOOGLE_CHROME_UDP_RANGE}")" >/dev/null 2>&1 || true
recreate_compose_service() {
local compose_dir="$1"
local service="$2"
local name_pattern="$3"
local existing_id=""
local pid=""
existing_id="$(cd "$compose_dir" && docker compose ps -q "$service" 2>/dev/null || true)"
if [[ -n "$existing_id" ]]; then
docker update --restart=no "$existing_id" >/dev/null 2>&1 || true
pid="$(docker inspect -f '{{.State.Pid}}' "$existing_id" 2>/dev/null || true)"
if [[ -n "$pid" && "$pid" != "0" ]]; then
sudo kill -TERM "$pid" >/dev/null 2>&1 || true
sleep 3
sudo kill -KILL "$pid" >/dev/null 2>&1 || true
sleep 2
fi
docker rm -f "$existing_id" >/dev/null 2>&1 || true
fi
docker ps -a --format '{{.ID}} {{.Names}}' \
| awk -v pat="$name_pattern" '$2 ~ pat {print $1}' \
| xargs -r docker rm -f >/dev/null 2>&1 || true
(cd "$compose_dir" && docker compose up -d)
}
wait_for_healthy() {
local compose_dir="$1"
local service="$2"
local container_id=""
local health_status=""
local state_status=""
for _ in $(seq 1 60); do
container_id="$(cd "$compose_dir" && docker compose ps -q "$service" 2>/dev/null || true)"
[[ -z "$container_id" ]] && sleep 2 && continue
health_status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container_id" 2>/dev/null || true)"
state_status="$(docker inspect -f '{{.State.Status}}' "$container_id" 2>/dev/null || true)"
if [[ "$state_status" == "running" && ( "$health_status" == "healthy" || "$health_status" == "none" ) ]]; then
echo "$container_id"
return 0
fi
if [[ "$health_status" == "unhealthy" ]]; then
echo "service ${service} became unhealthy" >&2
docker logs --tail 200 "$container_id" >&2 || true
return 1
fi
sleep 2
done
echo "timed out waiting for ${service}" >&2
[[ -n "$container_id" ]] && docker logs --tail 200 "$container_id" >&2 || true
return 1
}
recreate_compose_service "$CHROMIUM_DIR" "neko-browser" "neko-browser"
recreate_compose_service "$GOOGLE_CHROME_DIR" "neko-google-chrome" "neko-google-chrome"
CHROMIUM_ID="$(wait_for_healthy "$CHROMIUM_DIR" "neko-browser")"
GOOGLE_CHROME_ID="$(wait_for_healthy "$GOOGLE_CHROME_DIR" "neko-google-chrome")"
echo
echo "chromium:"
echo " url: http://${LAN_IP}:${CHROMIUM_HTTP_PORT}/"
echo " container: ${CHROMIUM_ID}"
echo " compose: ${CHROMIUM_DIR}"
echo
echo "google-chrome:"
echo " url: http://${LAN_IP}:${GOOGLE_CHROME_HTTP_PORT}/"
echo " container: ${GOOGLE_CHROME_ID}"
echo " compose: ${GOOGLE_CHROME_DIR}"