244 lines
9.2 KiB
Nix
244 lines
9.2 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
# Accès SSH de hermes vers le téléphone (modèle tmate : le tel sort vers le VPS
|
|
# `rack`, aucun port ouvert, aucun tiers, pas d'app VPN en plus).
|
|
#
|
|
# - sshd local (port 8023, clé hermes@tower autorisée, sftp) ;
|
|
# - `phone-tunnel-loop` : boucle `ssh -R 127.0.0.1:2224:127.0.0.1:8023 root@marcpartensky.com` ;
|
|
# - `phone-up` : démarrage idempotent (sshd + tunnel + watchdog + wake-lock borné),
|
|
# accroché à chaque session (.zprofile/.zshrc/.profile/.bashrc) ET à chaque prompt
|
|
# zsh (precmd rate-limité 60s : n'importe quelle touche dans l'app répare) ;
|
|
# - `phone-watchdog` : rejoue `phone-up` toutes les 2 min (filet de sécurité) ;
|
|
# - `phone-update` : met à jour la conf (switch nix-on-droid, tarball-ttl 0) ;
|
|
# - `phone-status` : diagnostic (verdicts + pids + logs).
|
|
#
|
|
# IMPORTANT (fiabilité Xiaomi/HyperOS) : accorder à l'app Nix la permission
|
|
# « Afficher des fenêtres pop-up en arrière-plan » (display over other apps),
|
|
# sinon le wake-lock échoue et MIUI tue les process d'arrière-plan.
|
|
#
|
|
# La clé privée du tunnel vit uniquement sur le tel (~/.ssh/relay_ed25519), jamais ici.
|
|
# Côté rack, l'entrée restreinte :
|
|
# command="false",restrict,port-forwarding,permitlisten="127.0.0.1:2224" ssh-ed25519 AAAA... phone-relay
|
|
|
|
let
|
|
inherit (config.user) home;
|
|
|
|
sshdDir = "${home}/sshd";
|
|
sshdPort = 8023;
|
|
|
|
relayHost = "marcpartensky.com";
|
|
relayPort = 2224;
|
|
relayKey = "${home}/.ssh/relay_ed25519";
|
|
|
|
# Clés autorisées sur le sshd du téléphone.
|
|
authorizedKeys = [
|
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKF3huPUXHP6P6SBXHHw9k7HGh6Cs8ntoRk2pnqrG2Hc hermes@tower"
|
|
];
|
|
|
|
sshdConfig = pkgs.writeText "phone-sshd_config" ''
|
|
Port ${toString sshdPort}
|
|
ListenAddress 0.0.0.0
|
|
HostKey ${sshdDir}/ssh_host_ed25519_key
|
|
PidFile ${sshdDir}/sshd.pid
|
|
AuthorizedKeysFile ${home}/.ssh/authorized_keys
|
|
PubkeyAuthentication yes
|
|
PasswordAuthentication no
|
|
KbdInteractiveAuthentication no
|
|
UsePAM no
|
|
AllowUsers ${config.user.userName}
|
|
StrictModes no
|
|
PrintMotd no
|
|
Subsystem sftp internal-sftp
|
|
'';
|
|
|
|
authorizedKeysFile = pkgs.writeText "phone-authorized_keys"
|
|
(lib.concatStringsSep "\n" authorizedKeys + "\n");
|
|
|
|
tunnelLoop = pkgs.writeScriptBin "phone-tunnel-loop" ''
|
|
#!${pkgs.runtimeShell}
|
|
while true; do
|
|
${pkgs.openssh}/bin/ssh -NT \
|
|
-i ${relayKey} \
|
|
-o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=15 \
|
|
-o ServerAliveInterval=25 -o ServerAliveCountMax=4 \
|
|
-o ExitOnForwardFailure=yes -o StrictHostKeyChecking=accept-new \
|
|
-o UserKnownHostsFile=${home}/.ssh/known_hosts \
|
|
-R 127.0.0.1:${toString relayPort}:127.0.0.1:${toString sshdPort} \
|
|
root@${relayHost}
|
|
${pkgs.coreutils}/bin/sleep 15
|
|
done
|
|
'';
|
|
|
|
phoneUp = pkgs.writeScriptBin "phone-up" ''
|
|
#!${pkgs.runtimeShell}
|
|
core=${pkgs.coreutils}/bin
|
|
flock=${lib.getBin pkgs.util-linux}/bin/flock
|
|
$core/mkdir -p ${sshdDir}
|
|
lock=${sshdDir}/up.lock
|
|
running=${sshdDir}/up.running
|
|
exec 9>>"$lock" || exit 0
|
|
if [ -x "$flock" ]; then
|
|
if ! "$flock" -n 9; then
|
|
# Verrou deja pris : si le porteur (up.running, sinon mtime du verrou)
|
|
# n'avance plus depuis 15 min, il est fige -> on le contourne en
|
|
# recreant le verrou sur un inode neuf (le fige garde l'ancien).
|
|
rts=$($core/stat -c %Y "$running" 2>/dev/null || echo 0)
|
|
[ "$rts" -eq 0 ] && rts=$($core/stat -c %Y "$lock" 2>/dev/null || echo 0)
|
|
now=$($core/date +%s)
|
|
if [ "$rts" -gt 0 ] && [ "$((now - rts))" -gt 900 ]; then
|
|
$core/rm -f "$lock"
|
|
exec 9>>"$lock" || exit 0
|
|
"$flock" -n 9 || exit 0
|
|
else
|
|
exit 0
|
|
fi
|
|
fi
|
|
fi
|
|
$core/touch "$running"
|
|
trap '$core/rm -f "$running"' EXIT
|
|
|
|
alive() {
|
|
[ -f "$1" ] && kill -0 "$($core/cat "$1" 2>/dev/null)" 2>/dev/null
|
|
}
|
|
|
|
# 1) sshd (retries : le demarrage peut echouer sur un port encore occupe)
|
|
if alive ${sshdDir}/sshd.pid; then
|
|
:
|
|
else
|
|
$core/rm -f ${sshdDir}/sshd.pid
|
|
n=0
|
|
while [ "$n" -lt 3 ]; do
|
|
n=$((n + 1))
|
|
${pkgs.openssh}/bin/sshd -f ${sshdDir}/sshd_config >> ${sshdDir}/up.log 2>&1
|
|
$core/sleep 1
|
|
if alive ${sshdDir}/sshd.pid; then
|
|
echo "[up $(date 2>/dev/null)] sshd demarre (essai $n)" >> ${sshdDir}/up.log
|
|
break
|
|
fi
|
|
$core/sleep 2
|
|
done
|
|
if ! alive ${sshdDir}/sshd.pid; then
|
|
echo "[up $(date 2>/dev/null)] ECHEC sshd apres 3 essais" >> ${sshdDir}/up.log
|
|
fi
|
|
fi
|
|
|
|
# 2) tunnel inverse (nécessite la clé posée au bootstrap)
|
|
if alive ${sshdDir}/tunnel.pid; then
|
|
:
|
|
elif [ -f ${relayKey} ]; then
|
|
$core/rm -f ${sshdDir}/tunnel.pid
|
|
$core/nohup ${tunnelLoop}/bin/phone-tunnel-loop >> ${sshdDir}/tunnel.log 2>&1 &
|
|
echo $! > ${sshdDir}/tunnel.pid
|
|
fi
|
|
|
|
# 3) watchdog : rejoue phone-up en boucle (relance ce qui est mort)
|
|
if alive ${sshdDir}/watchdog.pid; then
|
|
:
|
|
else
|
|
$core/rm -f ${sshdDir}/watchdog.pid
|
|
$core/nohup ${phoneWatchdog}/bin/phone-watchdog >> ${sshdDir}/watchdog.log 2>&1 &
|
|
echo $! > ${sshdDir}/watchdog.pid
|
|
fi
|
|
|
|
# 4) wake lock (anti-veille Android ; permission « fenêtres pop-up » requise).
|
|
# DÉTACHÉ (nohup, jamais attendu) : l'appel am peut se figer sans fin (vécu :
|
|
# `timeout 8` ne le tue pas, il tenait up.lock pour toujours et bloquait
|
|
# silencieusement TOUTES les relances suivantes).
|
|
wl=/nix/var/nix/profiles/nix-on-droid/nix-on-droid-path/bin/termux-wake-lock
|
|
if [ -x "$wl" ]; then
|
|
$core/nohup "$wl" >/dev/null 2>&1 &
|
|
fi
|
|
'';
|
|
|
|
phoneWatchdog = pkgs.writeScriptBin "phone-watchdog" ''
|
|
#!${pkgs.runtimeShell}
|
|
while true; do
|
|
${pkgs.coreutils}/bin/sleep 120
|
|
/nix/var/nix/profiles/nix-on-droid/nix-on-droid-path/bin/phone-up || true
|
|
done
|
|
'';
|
|
|
|
phoneUpdate = pkgs.writeScriptBin "phone-update" ''
|
|
#!${pkgs.runtimeShell}
|
|
export PATH=/nix/var/nix/profiles/nix-on-droid/nix-on-droid-path/bin:$PATH
|
|
cd ${home}
|
|
exec nix-on-droid switch --flake "git+https://github.com/MarcPartensky/nixos" --option tarball-ttl 0
|
|
'';
|
|
|
|
phoneStatus = pkgs.writeScriptBin "phone-status" ''
|
|
#!${pkgs.runtimeShell}
|
|
check() {
|
|
if [ -f "$2" ] && kill -0 "$(cat "$2" 2>/dev/null)" 2>/dev/null; then
|
|
echo "$1: UP (pid $(cat "$2"))"
|
|
else
|
|
echo "$1: DOWN"
|
|
fi
|
|
}
|
|
check sshd ${sshdDir}/sshd.pid
|
|
check tunnel ${sshdDir}/tunnel.pid
|
|
check watchdog ${sshdDir}/watchdog.pid
|
|
echo "== tunnel.log (5 dernieres lignes)"
|
|
${pkgs.coreutils}/bin/tail -n 5 ${sshdDir}/tunnel.log 2>/dev/null || true
|
|
echo "== up.log (5 dernieres lignes)"
|
|
${pkgs.coreutils}/bin/tail -n 5 ${sshdDir}/up.log 2>/dev/null || true
|
|
'';
|
|
in
|
|
{
|
|
environment.packages = with pkgs; [
|
|
openssh
|
|
util-linux # flock pour phone-up
|
|
gnugrep # pratique en session de debug
|
|
tunnelLoop # référencés ici -> installés sur le tel
|
|
phoneUp
|
|
phoneWatchdog
|
|
phoneUpdate
|
|
phoneStatus
|
|
];
|
|
|
|
android-integration = {
|
|
termux-wake-lock.enable = true;
|
|
termux-wake-unlock.enable = true;
|
|
termux-setup-storage.enable = true;
|
|
termux-open.enable = true;
|
|
termux-open-url.enable = true;
|
|
am.enable = true;
|
|
};
|
|
|
|
build.activation = {
|
|
sshd = ''
|
|
$DRY_RUN_CMD mkdir -p ${sshdDir}
|
|
if [ ! -f ${sshdDir}/ssh_host_ed25519_key ]; then
|
|
$VERBOSE_ECHO "phone: generating sshd host key"
|
|
$DRY_RUN_CMD ${pkgs.openssh}/bin/ssh-keygen -t ed25519 -N "" -f ${sshdDir}/ssh_host_ed25519_key
|
|
fi
|
|
$VERBOSE_ECHO "phone: installing sshd_config + authorized_keys"
|
|
$DRY_RUN_CMD ${pkgs.coreutils}/bin/mkdir -p ${home}/.ssh
|
|
$DRY_RUN_CMD ${pkgs.coreutils}/bin/chmod 700 ${home}/.ssh
|
|
$DRY_RUN_CMD ${pkgs.coreutils}/bin/install -m 600 ${sshdConfig} ${sshdDir}/sshd_config
|
|
$DRY_RUN_CMD ${pkgs.coreutils}/bin/install -m 600 ${authorizedKeysFile} ${home}/.ssh/authorized_keys
|
|
'';
|
|
|
|
termuxProps = ''
|
|
$DRY_RUN_CMD mkdir -p ${home}/.termux
|
|
if [ -z "''${DRY_RUN:-}" ]; then
|
|
if ! grep -qs allow-external-apps ${home}/.termux/termux.properties; then
|
|
printf 'allow-external-apps=true\n' >> ${home}/.termux/termux.properties
|
|
fi
|
|
fi
|
|
'';
|
|
|
|
autostart = ''
|
|
if [ -z "''${DRY_RUN:-}" ]; then
|
|
for f in ${home}/.zprofile ${home}/.zshrc ${home}/.profile ${home}/.bashrc; do
|
|
if ! grep -q nod-phone-autostart2 "$f" 2>/dev/null; then
|
|
printf '\n# nod-phone-autostart2\nif [ -x /nix/var/nix/profiles/nix-on-droid/nix-on-droid-path/bin/phone-up ]; then /nix/var/nix/profiles/nix-on-droid/nix-on-droid-path/bin/phone-up >/dev/null 2>&1 || true; fi\n' >> "$f"
|
|
fi
|
|
done
|
|
if ! grep -q nod-phone-autorepair3 ${home}/.zshrc 2>/dev/null; then
|
|
printf '\n# nod-phone-autorepair3\nalias up="phone-up"\n_nod_autorepair() {\n local p=/nix/var/nix/profiles/nix-on-droid/nix-on-droid-path/bin/phone-up\n [ -x "$p" ] || return 0\n local s="$HOME/sshd/.up-stamp" now last\n now=$(date +%s 2>/dev/null) || return 0\n last=0\n [ -f "$s" ] && last=$(stat -c %%Y "$s" 2>/dev/null || echo 0)\n if [ "$((now - last))" -gt 60 ]; then\n : > "$s"\n ( "$p" >/dev/null 2>&1 & )\n fi\n}\nprecmd_functions+=(_nod_autorepair)\n' >> ${home}/.zshrc
|
|
fi
|
|
fi
|
|
'';
|
|
};
|
|
}
|