diff --git a/scripts/deploy-neko-dual.sh b/scripts/deploy-neko-dual.sh new file mode 100644 index 0000000..93ee38b --- /dev/null +++ b/scripts/deploy-neko-dual.sh @@ -0,0 +1,313 @@ +#!/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" <"$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" <"$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}" diff --git a/scripts/deploy-neko-google-chrome-2.sh b/scripts/deploy-neko-google-chrome-2.sh new file mode 100644 index 0000000..0e63027 --- /dev/null +++ b/scripts/deploy-neko-google-chrome-2.sh @@ -0,0 +1,205 @@ +#!/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" <"$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}" diff --git a/tcp_forward_qwen.py b/tcp_forward_qwen.py new file mode 100644 index 0000000..906ffc5 --- /dev/null +++ b/tcp_forward_qwen.py @@ -0,0 +1,50 @@ +import socket +import threading + + +LISTEN_HOST = "127.0.0.1" +LISTEN_PORT = 18007 +TARGET_HOST = "192.168.41.184" +TARGET_PORT = 8007 + + +def pipe(src: socket.socket, dst: socket.socket) -> None: + try: + while True: + data = src.recv(65536) + if not data: + break + dst.sendall(data) + except OSError: + pass + finally: + for sock in (src, dst): + try: + sock.shutdown(socket.SHUT_RDWR) + except OSError: + pass + try: + sock.close() + except OSError: + pass + + +def handle_client(client: socket.socket) -> None: + upstream = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + upstream.connect((TARGET_HOST, TARGET_PORT)) + threading.Thread(target=pipe, args=(client, upstream), daemon=True).start() + threading.Thread(target=pipe, args=(upstream, client), daemon=True).start() + + +def main() -> None: + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind((LISTEN_HOST, LISTEN_PORT)) + server.listen() + while True: + client, _ = server.accept() + threading.Thread(target=handle_client, args=(client,), daemon=True).start() + + +if __name__ == "__main__": + main()