diff --git a/hosts/heather/caddy.nix b/hosts/heather/caddy.nix index 82ced77..f9f70fa 100644 --- a/hosts/heather/caddy.nix +++ b/hosts/heather/caddy.nix @@ -113,6 +113,16 @@ Allow: /" 200 reverse_proxy 127.0.0.1:6767 ''; + # JupyterLab (ботать аналитику с iPad). Два слоя защиты: + # Caddy basicauth (пароль в pass iscg.dev/jupyter/basicauth) + # + Jupyter token внутри. Лимиты памяти/CPU в jupyter.nix. + "jupyter.iscg.dev".extraConfig = '' + basicauth { + mikl $2a$14$u4jCpuLFeymewgx1uuGRs.6.L1JYf7vMtgwhU/Wvw5iuXJflynvSu + } + reverse_proxy 127.0.0.1:8890 + '';; + # File explorer (directory listing) для раздачи файлов через браузер. # browse включает Caddy's built-in directory listing. Корень /var/www/pub. # Без auth — класть только публичные файлы. diff --git a/hosts/heather/default.nix b/hosts/heather/default.nix index ce18786..caad7c1 100644 --- a/hosts/heather/default.nix +++ b/hosts/heather/default.nix @@ -21,6 +21,7 @@ ./syncthing.nix # sync + relay ./tuwunel-admin.nix # matrix admin web UI (systemd, статический бинарник) ./paseo.nix # paseo daemon (web UI для CLI-агентов, systemd + npm global) + ./jupyter.nix # JupyterLab (systemd, localhost:8890, лимиты) ./mesh-clients.nix # Sol-style client pool (wg-clients1) ./mesh-egress.nix # Sol-style selected-destination egress policy ]; diff --git a/hosts/heather/jupyter.nix b/hosts/heather/jupyter.nix new file mode 100644 index 0000000..d98350e --- /dev/null +++ b/hosts/heather/jupyter.nix @@ -0,0 +1,67 @@ +# heather — JupyterLab (systemd, не контейнер). +# +# Среда собирается uv'ом в /var/lib/jupyter-env (venv с jupyterlab + LSP + +# SQL + темы). Устанавливается однократно вручную (nix run nixpkgs#uv), сама +# установка не в nix store — пакеты летят из PyPI, пинить нечего. +# +# Безопасность: слушает только 127.0.0.1:8890, наружу через Caddy +# (jupyter.iscg.dev). Caddy basicauth (см. caddy.nix jupyterBasicAuth) + +# Jupyter token (пасс iscg.dev/jupyter/token, env-файл jupyter-secrets). +# +# Лимиты: MemoryMax 2G, CPUQuota 50% — ноутбук не сможет съесть сервер. + +{ config, pkgs, lib, ... }: + +let + jupyterEnv = "/var/lib/jupyter-env"; + jupyterBin = "${jupyterEnv}/bin/jupyter-lab"; +in { + # JupyterLab управляет собственным env-файлом? нет — используем + # EnvironmentFile с токеном: /var/lib/jupyter-secrets/jupyter.env + # (JUPYTER_TOKEN=..., mikl:users 0600) + systemd.services.jupyterlab = { + description = "JupyterLab notebook server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + path = with pkgs; [ bash ]; + environment = { + HOME = "/home/mikl"; + PATH = "${jupyterEnv}/bin:/run/current-system/sw/bin"; + }; + serviceConfig = { + Type = "simple"; + User = "mikl"; + Group = "users"; + EnvironmentFile = "/var/lib/jupyter-secrets/jupyter.env"; + ExecStart = '' + ${jupyterBin} \ + --no-browser \ + --ip=127.0.0.1 \ + --port=8890 \ + --ServerApp.token="$JUPYTER_TOKEN" \ + --ServerApp.root_dir=/srv/jupyter \ + --ServerApp.password_required=True + ''; + Restart = "on-failure"; + RestartSec = 5; + # --- лимиты ресурсов --- + MemoryMax = "2G"; + MemoryHigh = "1536M"; + CPUQuota = "50%"; + TasksMax = 256; + # --- sandbox (лёгкий, т.к. ноутбукам нужен доступ к ~/srv) --- + NoNewPrivileges = true; + PrivateTmp = true; + ProtectSystem = "full"; + ReadWritePaths = [ "/srv/jupyter" "/home/mikl" ]; + }; + }; + + # Каталог ноутбуков и env-директория + systemd.tmpfiles.rules = [ + "d /srv/jupyter 0755 mikl users -" + "d /var/lib/jupyter-secrets 0750 mikl users -" + "d /var/lib/jupyter-env 0755 mikl users -" + ]; +}