206 lines
6.2 KiB
Bash
206 lines
6.2 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}"
|
|
|
|
GOOGLE_CHROME_DIR="${GOOGLE_CHROME_DIR:-$HOME_DIR/neko-google-chrome-2}"
|
|
GOOGLE_CHROME_PROFILE_DIR="${GOOGLE_CHROME_PROFILE_DIR:-/root/neko-google-chrome-2-profile}"
|
|
|
|
GOOGLE_CHROME_HTTP_PORT="${GOOGLE_CHROME_HTTP_PORT:-8082}"
|
|
GOOGLE_CHROME_UDP_RANGE="${GOOGLE_CHROME_UDP_RANGE:-56400-56500}"
|
|
GOOGLE_CHROME_DISPLAY="${GOOGLE_CHROME_DISPLAY:-:101.0}"
|
|
|
|
mkdir -p "$GOOGLE_CHROME_DIR/policies"
|
|
|
|
sudo mkdir -p "$GOOGLE_CHROME_PROFILE_DIR"
|
|
sudo chown -R "${PROFILE_UID}:${PROFILE_GID}" "$GOOGLE_CHROME_PROFILE_DIR"
|
|
|
|
cat >"$GOOGLE_CHROME_DIR/docker-compose.yaml" <<EOF
|
|
services:
|
|
neko-google-chrome-2:
|
|
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 "${GOOGLE_CHROME_HTTP_PORT}" >/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 "$GOOGLE_CHROME_DIR" "neko-google-chrome-2" "neko-google-chrome-2"
|
|
GOOGLE_CHROME_ID="$(wait_for_healthy "$GOOGLE_CHROME_DIR" "neko-google-chrome-2")"
|
|
|
|
echo
|
|
echo "google-chrome-2:"
|
|
echo " url: http://${LAN_IP}:${GOOGLE_CHROME_HTTP_PORT}/"
|
|
echo " container: ${GOOGLE_CHROME_ID}"
|
|
echo " compose: ${GOOGLE_CHROME_DIR}"
|