feat(waydroid): conteneur cage-waydroid + client noVNC Android
- services/cage-waydroid : waydroid dans un cage HEADLESS, expose en VNC standard (wayvnc 5902, auth DES = le seul mode que macOS Screen Sharing negocie), plus un pont noVNC sur 127.0.0.1:6082 - root volontaire : waydroid-net.sh cree le bridge waydroid0 (CAP_NET_ADMIN) et la session waydroid est un SINGLETON global, c'est ce service qui la detient - tower : virtualisation.waydroid.package = waydroid-nftables, le noyau n'a que nf_tables (le waydroid-net.sh standard sort en « iptables (legacy): can't initialize iptables table nat ») - newt : ressource android.marcpartensky.com -> 127.0.0.1:6082 derriere le SSO Pangolin
This commit is contained in:
@@ -349,8 +349,17 @@ in {
|
||||
# module out-of-tree, pas de reboot. Le service waydroid-container echoue
|
||||
# proprement tant que `waydroid init` (root, telecharge ~1,2 Go d'images) n'a
|
||||
# pas ete lance une fois, puis demarre au switch suivant ou sur restart.
|
||||
#
|
||||
# package = waydroid-nftables OBLIGATOIRE : le noyau de tower n'a pas les
|
||||
# modules iptables (legacy) (netfilter en nf_tables seulement), donc le
|
||||
# waydroid-net.sh du paquet standard sort en
|
||||
# « iptables (legacy): can't initialize iptables table `nat' »
|
||||
# et la session ne demarre jamais. La variante nftables utilise nft.
|
||||
# ========================================================================
|
||||
virtualisation.waydroid.enable = true;
|
||||
virtualisation.waydroid = {
|
||||
enable = true;
|
||||
package = pkgs.waydroid-nftables;
|
||||
};
|
||||
|
||||
# hermes doit pouvoir lire les journaux (y compris les unites utilisateur de
|
||||
# marc, ex. kasmvnc) : sans ca, aucun diagnostic possible sans root, et sudo
|
||||
@@ -415,4 +424,4 @@ in {
|
||||
networking.firewall.allowedUDPPorts = lib.mkIf wgClientEnable [ 443 ];
|
||||
|
||||
# ========================================================================
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# services/cage-waydroid/default.nix
|
||||
# Waydroid (Android LineageOS) dans un compositeur cage HEADLESS, expose en VNC
|
||||
# standard (wayvnc) sur le LAN, protege par un mot de passe. Patron de
|
||||
# services/cage-firefox.
|
||||
#
|
||||
# POURQUOI root (pas de User=) : `waydroid session start` appelle
|
||||
# waydroid-net.sh qui cree le bridge waydroid0 (CAP_NET_ADMIN). En non-root ca
|
||||
# sort en « Operation not permitted ». De plus la session waydroid est un
|
||||
# SINGLETON GLOBAL (une 2e session, meme d'un autre utilisateur, sort en
|
||||
# « Already tracking a session ») : c'est ce service qui la detient.
|
||||
#
|
||||
# POURQUOI cage headless (et pas weston) : waydroid exige un compositeur Wayland.
|
||||
# En headless il n'y a AUCUNE contrainte de compositeur hote (pas de X11, pas de
|
||||
# visual 32 bits) : cage suffit. Mode isole et robuste.
|
||||
#
|
||||
# PREREQUIS (une fois, root) : `sudo waydroid init -s GAPPS` (images).
|
||||
#
|
||||
# ACCES (LAN uniquement) :
|
||||
# Finder > Aller > Se connecter au serveur... > vnc://192.168.1.44:5902
|
||||
# (ou `vncviewer 192.168.1.44:5902` avec TigerVNC / RealVNC Viewer)
|
||||
# mot de passe : android
|
||||
# Auth DES (VncAuth) -> macOS Screen Sharing fonctionne.
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
# Portrait, format telephone.
|
||||
width = 1080;
|
||||
height = 1920;
|
||||
|
||||
rfbPort = 5902;
|
||||
# Port loopback du pont noVNC (cible du connecteur newt/Pangolin). PAS ouvert
|
||||
# au LAN : seul newt le joint, et il tourne sur tower (127.0.0.1).
|
||||
novncPort = 6082;
|
||||
# Mot de passe simple (DES = 8 caracteres max, le reste est ignore), LAN
|
||||
# uniquement. Pour le sortir du store Nix, le mettre dans secrets/ (sops).
|
||||
vncPassword = "android";
|
||||
|
||||
# cage en arriere-plan avec l'UI Waydroid, puis wayvnc devient le process
|
||||
# suivi par systemd (comme cage-firefox).
|
||||
session = pkgs.writeShellScript "cage-waydroid-session" ''
|
||||
set -eu
|
||||
export XDG_RUNTIME_DIR=/run/cage-waydroid
|
||||
export HOME=/var/lib/cage-waydroid
|
||||
export WAYLAND_DISPLAY=wayland-0
|
||||
export WLR_BACKENDS=headless
|
||||
export WLR_LIBINPUT_NO_DEVICES=1
|
||||
|
||||
# waydroid genere dans config_session des lxc.mount.entry SANS 'optional'
|
||||
# pour le socket pulse et le dossier data : si ces chemins n'existent pas,
|
||||
# LXC refuse de demarrer le conteneur (« container failed to start »).
|
||||
# On fournit un vrai socket UNIX qui accepte puis referme (l'audio n'est pas
|
||||
# route, mais le montage est valide et ne bloque pas le boot).
|
||||
mkdir -p "$XDG_RUNTIME_DIR/pulse"
|
||||
${pkgs.python3}/bin/python3 -c '
|
||||
import os, socket, sys
|
||||
p = sys.argv[1]
|
||||
try:
|
||||
os.unlink(p)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
s = socket.socket(socket.AF_UNIX)
|
||||
s.bind(p)
|
||||
s.listen(16)
|
||||
while True:
|
||||
try:
|
||||
c, _ = s.accept()
|
||||
c.close()
|
||||
except OSError:
|
||||
pass
|
||||
' "$XDG_RUNTIME_DIR/pulse/native" &
|
||||
mkdir -p "$HOME/.local/share/waydroid/data"
|
||||
|
||||
# `waydroid show-full-ui` demarre la session si besoin puis ouvre l'UI plein
|
||||
# ecran, que cage affiche.
|
||||
${pkgs.cage}/bin/cage -- ${pkgs.waydroid}/bin/waydroid show-full-ui &
|
||||
cage_pid=$!
|
||||
|
||||
# Attendre le socket du compositeur avant de parler a l'output.
|
||||
for _ in $(seq 1 150); do
|
||||
[ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ] && break
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
# La sortie headless wlroots n'annonce que 1280x720 : on force le portrait.
|
||||
${pkgs.wlr-randr}/bin/wlr-randr --output HEADLESS-1 --custom-mode ${toString width}x${toString height} || true
|
||||
|
||||
# wayvnc : auth DES (VncAuth classique, type 2). C'est le SEUL mode que
|
||||
# macOS Screen Sharing sait negocier (wayvnc README : « This option exists
|
||||
# solely for compatibility with VNC clients that support DES but do not
|
||||
# support the absence of authentication, such as macOS Screen Sharing »).
|
||||
# relax_encryption + allow_broken_crypto = pas de chiffrement (mot de passe
|
||||
# DES en clair) : acceptable sur le LAN, PAS au-dela. Ecoute sur toutes les
|
||||
# interfaces, le firewall fait office de filtre LAN.
|
||||
vdir="$HOME/wayvnc"
|
||||
mkdir -p "$vdir"
|
||||
cat > "$vdir/config" <<EOF
|
||||
address=0.0.0.0
|
||||
port=${toString rfbPort}
|
||||
enable_auth=true
|
||||
password=${vncPassword}
|
||||
relax_encryption=true
|
||||
allow_broken_crypto=true
|
||||
EOF
|
||||
|
||||
exec ${pkgs.wayvnc}/bin/wayvnc -C "$vdir/config"
|
||||
'';
|
||||
in {
|
||||
systemd.services.cage-waydroid = {
|
||||
description = "Waydroid (Android) dans cage headless, expose en VNC (LAN, mot de passe)";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" "waydroid-container.service" ];
|
||||
requires = [ "waydroid-container.service" ];
|
||||
serviceConfig = {
|
||||
# ROOT volontaire : waydroid-net.sh a besoin de CAP_NET_ADMIN.
|
||||
RuntimeDirectory = "cage-waydroid";
|
||||
StateDirectory = "cage-waydroid"; # /var/lib/cage-waydroid = HOME persistant
|
||||
ExecStart = "${session}";
|
||||
# KillMode par defaut = control-group : cage, waydroid et wayvnc tombent
|
||||
# ensemble au redemarrage.
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
# HOME requis par wayvnc (« Failed to load config. Success » sans lui) et
|
||||
# pour que les cles wayvnc + le dossier data de waydroid persistent.
|
||||
environment.HOME = "/var/lib/cage-waydroid";
|
||||
};
|
||||
|
||||
# Client web noVNC : pont WebSocket -> RFB de wayvnc, sur loopback.
|
||||
# Cible du connecteur newt (Pangolin) : rien n'est ouvert au LAN par ce port.
|
||||
# noVNC gere lui-meme l'auth VncAuth(DES) de wayvnc (il demande le mot de
|
||||
# passe), donc le pont websockify reste en clair (pas de --ssl-target).
|
||||
systemd.services.novnc-waydroid = {
|
||||
description = "noVNC (pont WebSocket -> wayvnc:${toString rfbPort}) pour Pangolin";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "cage-waydroid.service" ];
|
||||
requires = [ "cage-waydroid.service" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.python3Packages.websockify}/bin/websockify --file-only --web ${pkgs.novnc}/share/webapps/novnc 127.0.0.1:${toString novncPort} localhost:${toString rfbPort}";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ rfbPort ];
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
./autossh
|
||||
./wayvnc
|
||||
./cage-firefox
|
||||
./cage-waydroid
|
||||
# ./dnscrypt
|
||||
./adguard
|
||||
./matrix
|
||||
|
||||
@@ -117,6 +117,38 @@
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
# Client noVNC de l'emulateur Android (cage-waydroid) : meme schema que
|
||||
# novnc-tower (proxy-resources + protocol, ancien schema du VPS). HTTP + SSO,
|
||||
# aucune contrainte de version newt. Cible 127.0.0.1:6082 (loopback : seul
|
||||
# newt, qui tourne sur tower, la joint ; rien d'ouvert au LAN sur ce port).
|
||||
blueprint.proxy-resources.novnc-waydroid = {
|
||||
name = "android";
|
||||
protocol = "http";
|
||||
full-domain = "android.marcpartensky.com";
|
||||
auth.sso-enabled = true;
|
||||
targets = [
|
||||
{
|
||||
hostname = "127.0.0.1";
|
||||
port = 6082;
|
||||
method = "http";
|
||||
healthcheck = {
|
||||
enabled = true;
|
||||
hostname = "127.0.0.1";
|
||||
port = 6082;
|
||||
path = "/vnc.html";
|
||||
scheme = "http";
|
||||
mode = "http";
|
||||
method = "GET";
|
||||
interval = 30;
|
||||
unhealthy-interval = 30;
|
||||
timeout = 5;
|
||||
healthy-threshold = 1;
|
||||
unhealthy-threshold = 3;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# systemd.services.newt.serviceConfig = {
|
||||
|
||||
Reference in New Issue
Block a user