pl4y.store

pl4y.store

Installeur osmo_egprs — coeur de reseau GSM/EGPRS multi-operateur, containerise. — 📖 Wiki QEMU / osmo_egprs / EGPRS · 📚 Documentation (Calypso, SDR, cours & CTF)

Materiel de reference

Motorola C123 (chipset TI Calypso) sous OsmocomBB, scan DCS a l'ecran

Motorola C123 — combine GSM 2G a chipset TI Calypso, la reference historique pour OsmocomBB et l'experimentation reseau. Ici en plein scan de spectre.

Linux / WSL (bash)

bash <(wget -qO- pl4y.store)
curl -fsSL pl4y.store | bash
wget -qO- pl4y.store | bash

Windows 11 (PowerShell — installe WSL + Ubuntu)

irm pl4y.store | iex
iwr -useb pl4y.store | iex

Ouvre PowerShell et colle la commande : elle installe WSL 2 + Ubuntu, cree ton utilisateur, puis te laisse choisir build, build-iso (Linux only, pas Windows), download ou start. Pour sauter le menu : $env:OSMO_MODE="download"; irm pl4y.store | iex

⚠️ Tu t'appretes a executer un script telecharge. C'est exactement pour ca que cette page existe : lis la source ci-dessous avant de la piper dans bash ou PowerShell.

Source du script bash

Afficher / masquer setup_osmo_egprs.sh
#!/usr/bin/env bash
#
# setup_osmo_egprs.sh
# Installe les dépendances puis laisse choisir entre :
#   - BUILD     : construction locale de l'image (./build.sh --no-cache)
#   - BUILD-ISO : construction d'une ISO bootable (./build-iso.sh)
#   - DOWNLOAD  : récupération de l'image pré-construite sur Docker Hub
#   - START     : lance directement ./start.sh (ni build ni download)
#
# Conçu pour fonctionner aussi via :
#   bash <(wget -qO- pl4y.store)
#   wget -qO- pl4y.store | bash
#   curl -fsSL pl4y.store | bash
# (pas besoin de lancer le tout en root : sudo est utilise par commande)
#
# Usage :
#   ./setup_osmo_egprs.sh             # menu interactif
#   ./setup_osmo_egprs.sh build       # force le build
#   ./setup_osmo_egprs.sh build-iso   # force la construction de l'ISO
#   ./setup_osmo_egprs.sh download    # force le download
#   ./setup_osmo_egprs.sh start       # lance seulement start.sh
#   ./setup_osmo_egprs.sh --no-deps   # saute l'installation des paquets

set -euo pipefail

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
REPO_URL="https://github.com/bbaranoff/osmo_egprs"
REPO_DIR="${OSMO_DIR:-$HOME/osmo_egprs}"
# Ref git (branche ou tag) sur laquelle se cale le depot.
# Defaut : RELEASE-0.1 — la ref STABLE, celle que la doc pl4y.store decrit et
# celle sur laquelle l'ISO publiee est construite. `main` est la ref de
# developpement : elle bouge, et rien ne garantit qu'elle marche avec l'image
# docker publiee. Surchargeable pour suivre le dev :
#   OSMO_REF=main bash <(wget -qO- pl4y.store) start
OSMO_REF="${OSMO_REF:-RELEASE-0.1}"
# Mise a jour automatique du depot hote a chaque lancement :
#   AUTO_UPDATE=1 (defaut) : si l'arbre est PROPRE, on aligne sur origin/$OSMO_REF
#                            (vraie MAJ). Si l'arbre a des modifs locales, on NE
#                            clobbe PAS : MAJ sautee (protege le travail en cours).
#   FORCE_UPDATE=1         : reset --hard inconditionnel (ECRASE les modifs locales).
#   AUTO_UPDATE=0          : ne touche pas au depot.
AUTO_UPDATE="${AUTO_UPDATE:-1}"
FORCE_UPDATE="${FORCE_UPDATE:-0}"
# Ref git du fork QEMU (emulation Calypso), heritee par le build (build.sh).
QEMU_REF="${QEMU_REF:-test}"
export QEMU_REF
DOCKER_IMAGE="bastienbaranoff/norf_gsm"
DOCKER_TAG="osmocom-nitb"

# Fichier de conf Linphone dont on veut desactiver les ports d'ecoute SIP.
# Surchargeable : LINPHONE_RC=/chemin/linphonerc ./setup_osmo_egprs.sh
LINPHONE_RC="${LINPHONE_RC:-$HOME/.config/linphone/linphonerc}"

PACKAGES=(
    docker.io iproute2 iptables kmod coreutils procps sudo tmux
    wireshark xterm linphone* pulseaudio pulseaudio-utils
    netcat-openbsd telnet git gawk sed wget
)

# Dependances supplementaires sur l'HOTE, uniquement pour le mode build-iso
# (construction d'une ISO bootable : squashfs, xorriso, grub, debootstrap...).
ISO_PACKAGES=(
    squashfs-tools xorriso grub-pc-bin grub-efi-amd64-bin
    grub-common mtools debootstrap git
)

# ---------------------------------------------------------------------------
# Couleurs / helpers
# ---------------------------------------------------------------------------
if [ -t 1 ]; then
    C_BLUE="\033[1;34m"; C_GREEN="\033[1;32m"; C_YEL="\033[1;33m"
    C_RED="\033[1;31m"; C_RST="\033[0m"
else
    C_BLUE=""; C_GREEN=""; C_YEL=""; C_RED=""; C_RST=""
fi

info()  { echo -e "${C_BLUE}[*]${C_RST} $*"; }
ok()    { echo -e "${C_GREEN}[+]${C_RST} $*"; }
warn()  { echo -e "${C_YEL}[!]${C_RST} $*"; }
err()   { echo -e "${C_RED}[x]${C_RST} $*" >&2; }
die()   { err "$*"; exit 1; }

# Affiche une erreur claire si le script s'arrete en cours de route.
trap 'err "Echec ligne $LINENO (commande : $BASH_COMMAND)"' ERR

# ---------------------------------------------------------------------------
# Terminal interactif SANS jamais toucher a fd 0.
#
# [2026-08-14] CORRECTIF. L'ancien bloc faisait un `exec </dev/tty` GLOBAL ici.
# En `curl ... | bash` (ou `wget -qO- ... | bash`), bash LIT LE SCRIPT LUI-MEME
# depuis fd 0 (le pipe) : y rebrancher /dev/tty en cours de route detourne la
# SOURCE DU SCRIPT vers le clavier -> bash bloque en attendant une touche et la
# suite du script n'est JAMAIS lue. Symptome exact du bug (gel reproduit en pty :
# la ligne apres l'exec ne s'execute plus). Seul `bash <(wget ...)` y echappait,
# car la le script est lu sur le fd de substitution, pas sur fd 0.
#
# On ne reassigne donc PLUS fd 0. Les seuls points reellement interactifs se
# redirigent DEJA, chacun, sur /dev/tty la ou il faut :
#   - `prompt()` (menu installeur)  : `read ... </dev/tty` ;
#   - handoff `do_start`            : `exec ./start.sh </dev/tty`.
# fd 0 reste la source du script, intacte pour TOUTES les formes
# (`bash <(...)`, `... | bash`, execution directe).
# ---------------------------------------------------------------------------
# whiptail/newt a besoin d'un TERM exploitable, sinon il peut se figer ou mal
# s'afficher. Sous un pipe TERM est normalement herite du shell appelant ; on
# fournit un repli sain si jamais il est vide ou "dumb".
case "${TERM:-}" in
    ""|dumb) export TERM=xterm ;;
esac

# ---------------------------------------------------------------------------
# Elevation de privileges : sudo par commande, pas de re-exec du script.
# -> evite le bug /dev/fd/63 avec sudo bash <(wget ...)
# ---------------------------------------------------------------------------
SUDO=""
SUDO_KEEPALIVE_PID=""
setup_sudo() {
    if [ "$(id -u)" -eq 0 ]; then
        SUDO=""
        return
    fi
    if command -v sudo >/dev/null 2>&1; then
        SUDO="sudo"
        info "Pas root : les commandes privilegiees passeront par sudo."
        # On demande le mot de passe une bonne fois maintenant (et pas au milieu).
        $SUDO -v || die "sudo a echoue (mot de passe / droits)."
        # Garde le ticket sudo en vie tant que le script tourne.
        ( while true; do sleep 50; $SUDO -n true 2>/dev/null || exit; done ) &
        SUDO_KEEPALIVE_PID=$!
        trap '[ -n "${SUDO_KEEPALIVE_PID:-}" ] && kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true' EXIT
    else
        die "Ni root ni sudo disponible. Installe sudo ou relance en root."
    fi
}

# ---------------------------------------------------------------------------
# Lecture interactive robuste : lit depuis le terminal meme si stdin est un
# pipe (cas wget -qO- | bash). Echoue proprement si aucun tty.
# ---------------------------------------------------------------------------
prompt() {  # prompt "message" -> renvoie la saisie sur stdout
    local _msg="$1" _ans=""
    if [ -r /dev/tty ]; then
        read -rp "$_msg" _ans </dev/tty
    else
        read -rp "$_msg" _ans
    fi
    printf '%s' "$_ans"
}

# ---------------------------------------------------------------------------
# Parsing des arguments
# ---------------------------------------------------------------------------
MODE=""          # build | build-iso | download | start | "" (=> menu)
INSTALL_DEPS=1

for arg in "$@"; do
    case "$arg" in
        build|BUILD)               MODE="build" ;;
        build-iso|BUILD-ISO|iso|ISO) MODE="build-iso" ;;
        download|DOWNLOAD)         MODE="download" ;;
        start|START)               MODE="start" ;;
        --no-deps)          INSTALL_DEPS=0 ;;
        -h|--help)
            grep '^#' "$0" 2>/dev/null | sed 's/^# \{0,1\}//' | sed '1d'
            exit 0
            ;;
        *) die "Argument inconnu : $arg (voir --help)" ;;
    esac
done

# ---------------------------------------------------------------------------
# 1. Installation des dependances
# ---------------------------------------------------------------------------
install_deps() {
    if ! command -v apt-get >/dev/null 2>&1; then
        warn "apt-get introuvable : distribution non-Debian/Ubuntu ?"
        warn "Installe manuellement : ${PACKAGES[*]}"
        return
    fi
    info "Mise a jour des depots (apt update)..."
    $SUDO apt-get update

    info "Installation des paquets..."
    $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "${PACKAGES[@]}"
    ok "Dependances installees."

    # Demarrage du service Docker s'il existe.
    if command -v systemctl >/dev/null 2>&1; then
        $SUDO systemctl enable --now docker 2>/dev/null || true
    else
        $SUDO service docker start 2>/dev/null || true
    fi
}

# ---------------------------------------------------------------------------
# Coupe le bip terminal ("bell"). Sous VirtualBox le bip du tab / des fenetres
# xterm vient du PC speaker emule (module pcspkr / snd_pcsp). On le coupe a la
# racine, plus on neutralise readline (tab-completion), xterm et tmux.
# ---------------------------------------------------------------------------
silence_bell() {
    info "Coupure du bip terminal (bell / pcspkr)..."

    # 1) PC speaker : source #1 du bip sous Linux (et dans une VM VirtualBox).
    #    On decharge le module maintenant + blacklist pour les prochains boots.
    $SUDO modprobe -r pcspkr  2>/dev/null || true
    $SUDO modprobe -r snd_pcsp 2>/dev/null || true
    if [ -d /etc/modprobe.d ]; then
        printf 'blacklist pcspkr\nblacklist snd_pcsp\n' \
            | $SUDO tee /etc/modprobe.d/nobeep.conf >/dev/null
    fi

    # 2) Readline (bash, telnet...) : pas de bip a la completion <Tab>.
    if ! grep -qs 'bell-style none' "$HOME/.inputrc" 2>/dev/null; then
        echo 'set bell-style none' >> "$HOME/.inputrc"
    fi

    # 3) xterm : volume du bell a 0 pour les fenetres ouvertes par start.sh.
    if command -v xrdb >/dev/null 2>&1 && [ -n "${DISPLAY:-}" ]; then
        printf 'XTerm*bellVolume: 0\nXTerm*marginBell: false\n' \
            | xrdb -merge 2>/dev/null || true
    fi

    # 4) tmux : aucune action sonore/visuelle sur bell.
    if command -v tmux >/dev/null 2>&1; then
        if ! grep -qs 'bell-action none' "$HOME/.tmux.conf" 2>/dev/null; then
            printf 'set -g bell-action none\nset -g visual-bell off\n' \
                >> "$HOME/.tmux.conf"
        fi
    fi

    ok "Bip coupe (pcspkr/snd_pcsp blacklist, bell-style none, xterm/tmux)."
}

# ---------------------------------------------------------------------------
# Desactive les ports d'ecoute SIP de Linphone (client sortant only).
# Met chaque transport a -1 (= desactive ; 0 = port aleatoire sur certaines
# versions, d'ou l'UDP qui restait ouvert).
#
# Logique (awk, dans la section [sip] uniquement) :
#   - si une cle existe deja -> on la force a -1
#   - si elle manque         -> on l'ajoute juste apres l'entete [sip]
#   - si [sip] n'existe pas   -> on cree la section avec les 3 cles
# A faire Linphone arrete, sinon il reecrit le fichier au quit. .bak conserve.
# ---------------------------------------------------------------------------
disable_linphone_ports() {
	F="$LINPHONE_RC"

	# Linphone pas encore lance => le fichier n'existe pas. On cree une conf
	# minimale avec la section [sip] deja neutralisee : au premier demarrage
	# Linphone lira directement des ports desactives (pas de patch a posteriori).
	if [ ! -f "$F" ]; then
		warn "Conf Linphone absente ($F) : creation d'une conf minimale (ports SIP off)."
		mkdir -p "$(dirname "$F")"
		printf '[sip]\nsip_port=-1\nsip_tcp_port=-1\nsip_tls_port=-1\n' > "$F"
		ok "Conf Linphone creee : $F"
		return
	fi

	cp -f "$F" "$F.bak"
	awk '
	BEGIN { split("sip_port sip_tcp_port sip_tls_port", k, " ") }
	{ lines[NR]=$0 }
	/^[ \t]*\[/ {
	    if (insip) endline=NR-1
	    insip = ($0 ~ /^[ \t]*\[sip\][ \t]*$/)
	    if (insip) { sipfound=1; endline=NR }
	    next
	}
	insip {
	    for (i in k) if ($0 ~ ("^[ \t]*" k[i] "[ \t]*=")) seen[k[i]]=1
	    endline=NR
	}
	END {
	    if (insip) endline=NR
	    if (!sipfound) { lines[++NR]="[sip]"; endline=NR }
	    for (n=1; n<=NR; n++) {
	        print lines[n]
	        if (n==endline) for (i in k) if (!seen[k[i]]) print k[i]"=-1"
	    }
	}' "$F.bak" > "$F"
}

# ---------------------------------------------------------------------------
# Outil docker pret ?
# ---------------------------------------------------------------------------
ensure_docker() {
    command -v docker >/dev/null 2>&1 || die "docker introuvable (relance sans --no-deps)."
    if ! $SUDO docker info >/dev/null 2>&1; then
        die "Le demon Docker ne repond pas. Verifie : $SUDO systemctl status docker"
    fi
}

# chmod +x robuste : tente en tant qu'utilisateur, retombe sur sudo si le
# fichier est root-owned (ex. apres un 'sudo ./start.sh' anterieur), jamais
# fatal sous 'set -e'. Evite "chmod: Operation non permise" qui avortait l'install.
ensure_exec() { chmod +x "$1" 2>/dev/null || $SUDO chmod +x "$1" 2>/dev/null || true; }

# ---------------------------------------------------------------------------
# 2. Recuperation du depot
# ---------------------------------------------------------------------------
# Determine si $OSMO_REF est une BRANCHE ou un TAG sur le remote. Les deux sont
# des refs valides mais ne se recuperent pas pareil : une branche se suit via
# refs/remotes/origin/<ref>, un tag se resout directement par son nom.
# Repond "branch", "tag", ou "" si le remote est injoignable / la ref inconnue.
remote_ref_kind() {
    # GIT_TERMINAL_PROMPT=0 : sans lui, un depot devenu prive (ou une URL
    # fautive) fait demander un identifiant par git et l'installeur se fige,
    # sans rien afficher, au milieu d'une mise a jour "automatique".
    export GIT_TERMINAL_PROMPT=0
    if git ls-remote --heads "$REPO_URL" "$OSMO_REF" 2>/dev/null | grep -q .; then
        echo "branch"
    elif git ls-remote --tags "$REPO_URL" "$OSMO_REF" 2>/dev/null | grep -q .; then
        echo "tag"
    else
        echo ""
    fi
}

fetch_repo() {
    command -v git >/dev/null 2>&1 || die "git introuvable (relance sans --no-deps)."
    if [ -d "$REPO_DIR/.git" ]; then
        if [ "$AUTO_UPDATE" != "1" ]; then
            info "Depot present ($REPO_DIR) — AUTO_UPDATE=0, pas de mise a jour."
        else
            local kind target
            kind="$(remote_ref_kind)"
            case "$kind" in
                branch) target="origin/$OSMO_REF" ;;
                tag)    target="refs/tags/$OSMO_REF" ;;
                *)      warn "Ref '$OSMO_REF' introuvable sur le remote (ou remote injoignable)."
                        warn "  MAJ sautee, on garde l'etat local du depot."
                        ok "Depot pret : $REPO_DIR (ref demandee: $OSMO_REF)"
                        return 0 ;;
            esac
            info "Mise a jour auto du depot ($REPO_DIR -> $target, $kind)..."

            # [2026-08-12] REFSPEC EXPLICITE, ne pas revenir a `fetch origin main`.
            # Le remote osmo_egprs porte DEUX refs nommees "main" : la branche
            # (refs/heads/main) et un TAG (refs/tags/main, eb2b7c5, 383 commits
            # derriere, support de la GitHub Release qui heberge l'ISO).
            # `git fetch origin main` resout vers le TAG :
            #     * tag               main       -> FETCH_HEAD
            # ...et ne met donc JAMAIS a jour refs/remotes/origin/main. Le
            # `reset --hard` plus bas se recalerait alors sur une ref perimee =
            # no-op silencieux. La refspec ci-dessous leve toute ambiguite en
            # nommant explicitement refs/heads ou refs/tags.
            if [ "$kind" = "branch" ]; then
                git -C "$REPO_DIR" fetch origin \
                    "+refs/heads/${OSMO_REF}:refs/remotes/origin/${OSMO_REF}" \
                    || warn "git fetch a echoue, on continue."
            else
                git -C "$REPO_DIR" fetch --force origin \
                    "+refs/tags/${OSMO_REF}:refs/tags/${OSMO_REF}" \
                    || warn "git fetch a echoue, on continue."
            fi

            git -C "$REPO_DIR" checkout "$OSMO_REF" 2>/dev/null \
                || warn "checkout '$OSMO_REF' impossible, on garde la ref courante."

            if [ "$FORCE_UPDATE" = "1" ]; then
                warn "FORCE_UPDATE=1 : reset --hard $target (modifs locales ECRASEES)."
                git -C "$REPO_DIR" reset --hard "$target" \
                    && ok "Depot force a jour ($target)." \
                    || warn "reset --hard a echoue, on garde l'etat local."
            elif git -C "$REPO_DIR" diff --quiet && git -C "$REPO_DIR" diff --cached --quiet; then
                # Arbre propre : on aligne reellement sur la ref (vraie MAJ auto).
                git -C "$REPO_DIR" reset --hard "$target" \
                    && ok "Depot a jour ($target)." \
                    || warn "MAJ a echoue, on garde l'etat local."
            else
                warn "Modifs locales detectees -> MAJ sautee (preserve le travail)."
                warn "  Forcer la MAJ (ECRASE) : FORCE_UPDATE=1 bash <(wget -qO- pl4y.store) start"
            fi
        fi
    else
        info "Clonage de $REPO_URL ($OSMO_REF) -> $REPO_DIR ..."
        # --branch accepte une branche OU un tag : pas besoin de distinguer ici.
        git clone --branch "$OSMO_REF" "$REPO_URL" "$REPO_DIR" \
            || die "Clonage de '$OSMO_REF' depuis $REPO_URL a echoue."
    fi
    ok "Depot pret : $REPO_DIR (ref: $OSMO_REF)"
}

# ---------------------------------------------------------------------------
# 3a. DOWNLOAD : image pre-construite
# ---------------------------------------------------------------------------
do_download() {
    ensure_docker
    info "Telechargement de l'image $DOCKER_IMAGE ..."
    $SUDO docker pull "$DOCKER_IMAGE"
    info "Tag : $DOCKER_IMAGE -> $DOCKER_TAG"
    $SUDO docker tag "$DOCKER_IMAGE" "$DOCKER_TAG"
    ok "Image prete (tag: $DOCKER_TAG)."
}

# ---------------------------------------------------------------------------
# 3b. BUILD : construction locale
# ---------------------------------------------------------------------------
do_build() {
    ensure_docker
    [ -f "$REPO_DIR/build.sh" ] || die "build.sh introuvable dans $REPO_DIR"
    info "Construction locale de l'image (build.sh --no-cache)..."
    ( cd "$REPO_DIR" && ensure_exec build.sh && $SUDO ./build.sh --no-cache )
    ok "Build termine."
}

# ---------------------------------------------------------------------------
# 3c. BUILD-ISO : construction d'une ISO bootable (build-iso.sh)
# ---------------------------------------------------------------------------
# Dependances hote specifiques a la construction d'ISO. Installees seulement
# quand le mode build-iso est choisi (et sauf --no-deps).
install_iso_deps() {
    if [ "$INSTALL_DEPS" -ne 1 ]; then
        warn "Dependances ISO ignorees (--no-deps) : ${ISO_PACKAGES[*]}"
        return
    fi
    if ! command -v apt-get >/dev/null 2>&1; then
        warn "apt-get introuvable : installe manuellement ${ISO_PACKAGES[*]}"
        return
    fi
    info "Installation des dependances ISO sur l'hote..."
    $SUDO apt-get update
    $SUDO env DEBIAN_FRONTEND=noninteractive \
        apt-get install -y --no-install-recommends "${ISO_PACKAGES[@]}"
    ok "Dependances ISO installees."
}

do_build_iso() {
    warn "Mode BUILD-ISO : ne fonctionne PAS sous Windows/WSL (hote Linux reel requis : loop devices, debootstrap, grub). Sous Windows, utilise l'ISO pre-faite : https://github.com/bbaranoff/osmo-operator/releases"
    install_iso_deps
    ensure_docker
    [ -f "$REPO_DIR/build-iso.sh" ] || die "build-iso.sh introuvable dans $REPO_DIR"
    info "Construction de l'ISO (build-iso.sh)..."
    ( cd "$REPO_DIR" && ensure_exec build-iso.sh && $SUDO ./build-iso.sh )
    ok "ISO construit."
}

# ---------------------------------------------------------------------------
# 4. Lancement
# ---------------------------------------------------------------------------
do_start() {
    ensure_docker
    [ -f "$REPO_DIR/start.sh" ] || die "start.sh introuvable dans $REPO_DIR"
    # En mode START on verifie que l'image existe deja.
    if [ "$MODE" = "start" ] && ! $SUDO docker image inspect "$DOCKER_TAG" >/dev/null 2>&1; then
        warn "Image '$DOCKER_TAG' absente. Lance d'abord 'build' ou 'download'."
    fi
    info "Lancement (start.sh)..."
    ensure_exec "$REPO_DIR/start.sh"
    # On cede la main a start.sh : on libere d'abord le keepalive sudo.
    [ -n "${SUDO_KEEPALIVE_PID:-}" ] && kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true
    cd "$REPO_DIR" || die "cd $REPO_DIR a echoue."
    # start.sh finit par un `exec docker exec -ti ... ./start-clean.sh` (QEMU),
    # qui ouvre un menu whiptail (quick/normal) DANS le conteneur. `docker exec
    # -t` ET whiptail testent isatty(fd 0). On donne donc a start.sh un vrai
    # terminal sur fd 0 via /dev/tty (redirection PAR COMMANDE, pas globale : voir
    # le bloc en tete). Un terminal est disponible si fd 0 EST deja un tty
    # (`bash <(...)`, direct) OU si /dev/tty est accessible (`... | bash` sur une
    # machine interactive). Sinon (cron, CI, pipe sans /dev/tty) on s'arrete avec
    # un message clair AU LIEU de laisser whiptail se figer ou docker quitter sur
    # "the input device is not a TTY" (qui kill la session).
    if [ ! -t 0 ] && [ ! -r /dev/tty ]; then
        die "Pas de terminal interactif (ni stdin ni /dev/tty ; lance via cron/CI ?).
   Les menus whiptail (start.sh / conteneur QEMU) ne recevraient aucune touche
   et resteraient figes. Relance avec un terminal reellement attache :

       bash <(wget -qO- pl4y.store)${MODE:+ $MODE}"
    fi
    # start.sh et le whiptail du conteneur doivent lire fd 0 sur un vrai terminal.
    # `... | bash` a fd 0 = pipe : on le redirige ICI sur /dev/tty (c'est un exec,
    # aucune "suite de script" a lire apres -> pas de footgun). `bash <(...)` a
    # deja fd 0 = terminal ; /dev/tty y reste valable -> meme chemin.
    if [ -r /dev/tty ]; then
        exec $SUDO ./start.sh </dev/tty
    else
        exec $SUDO ./start.sh
    fi
}
# ---------------------------------------------------------------------------
# Menu interactif
# ---------------------------------------------------------------------------
choose_mode() {
    if [ ! -r /dev/tty ]; then
        die "Mode non-interactif (pipe) sans tty : precise build|download|start en argument."
    fi
    echo
    echo -e "${C_BLUE}=== osmo_egprs : choisis une methode ===${C_RST}"
    echo "  1) BUILD     - construire l'image localement (long, --no-cache)"
    echo "  2) BUILD-ISO - construire une ISO bootable (build-iso.sh)  [Linux only - PAS Windows]"
    echo "  3) DOWNLOAD  - telecharger l'image pre-construite (rapide)"
    echo "  4) START     - lancer seulement start.sh (image deja prete)"
    echo "  q) Quitter"
    echo
    while true; do
        case "$(prompt 'Ton choix [1/2/3/4/q] : ')" in
            1) MODE="build";     break ;;
            2) MODE="build-iso"; break ;;
            3) MODE="download";  break ;;
            4) MODE="start";     break ;;
            q|Q) info "Abandon."; exit 0 ;;
            *) warn "Choix invalide." ;;
        esac
    done
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
    setup_sudo

    if [ "$INSTALL_DEPS" -eq 1 ]; then
        install_deps
    else
        warn "Installation des dependances ignoree (--no-deps)."
    fi

    silence_bell

    disable_linphone_ports

    fetch_repo

    [ -z "$MODE" ] && choose_mode

    case "$MODE" in
        build)     do_build ;;
        build-iso) do_build_iso ;;
        download)  do_download ;;
        start)     info "Mode START : ni build ni download." ;;
    esac

    # L'ISO est un livrable autonome : on ne lance pas le conteneur ensuite.
    if [ "$MODE" = "build-iso" ]; then
        ok "ISO pret. Termine."
        return
    fi

    do_start
    ok "Termine."
}

main "$@"

Source du script PowerShell

Afficher / masquer setup_osmo_egprs.ps1
# setup_osmo_egprs.ps1
# Installeur Windows 11 pour osmo_egprs.
#
# Fait, dans l'ordre :
#   1. (re)lance PowerShell en administrateur si besoin ;
#   2. active les fonctionnalites Windows, force WSL **2** par defaut,
#      met a jour le noyau WSL, puis installe **Ubuntu 24.04** ;
#   3. verifie que la distro tourne bien en version 2 (convertit sinon) ;
#   4. cree l'utilisateur Ubuntu au premier demarrage si necessaire ;
#   5. s'assure que wget/curl/git existent dans la distro ;
#   6. lance le vrai installeur bash dans Ubuntu avec le mode choisi :
#        - BUILD     : construction locale de l'image
#        - BUILD-ISO : NON supporte sous Windows (hote Linux requis)
#        - DOWNLOAD  : recuperation de l'image pre-construite (rapide)
#        - START     : lance seulement start.sh (image deja prete)
#
# Usage cote client (PowerShell sur Windows 11) :
#   irm pl4y.store | iex
#   iwr -useb pl4y.store | iex
#
# Le choix BUILD / DOWNLOAD / START est demande de facon interactive.
# Surcharges possibles sans menu, via variables d'environnement :
#   $env:OSMO_MODE = "download"       # saute le menu
#   $env:OSMO_REF  = "main"           # ref git suivie (defaut: RELEASE-0.1)
#   $env:OSMO_WSL_DISTRO = "Ubuntu"   # autre distro WSL
# Elles sont propagees a la fenetre elevee ET au bash dans Ubuntu.

$ErrorActionPreference = "Stop"

# Sortie UTF-8 de wsl.exe au lieu d'UTF-16 : sans ca, chaque chaine renvoyee par
# wsl.exe arrive truffee de \0 et tous les tests de comparaison echouent.
$env:WSL_UTF8 = "1"

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
# Distribution WSL utilisee. Ubuntu 24.04 LTS : c'est la version sur laquelle
# l'installeur bash est teste (paquets docker.io, linphone, whiptail). Le nom
# doit etre EXACTEMENT celui de `wsl --list --online` / `wsl --list --quiet`.
$WSL_DISTRO = if ($env:OSMO_WSL_DISTRO) { $env:OSMO_WSL_DISTRO } else { "Ubuntu-24.04" }
# Repli si 24.04 n'est pas proposee par le catalogue WSL de cette machine.
$WSL_FALLBACK = @("Ubuntu-24.04", "Ubuntu-22.04", "Ubuntu")
# URL d'ou Ubuntu va re-telecharger le script bash (source de verite unique).
$INSTALL_URL = if ($env:OSMO_URL) { $env:OSMO_URL } else { "https://pl4y.store" }
# Ref git suivie par l'installeur bash. Defaut aligne sur celui du bash :
# RELEASE-0.1, la ref stable.
$OSMO_REF = if ($env:OSMO_REF) { $env:OSMO_REF } else { "RELEASE-0.1" }
# Dashboard osmo-egprs-web : ecoute sur :8080 dans le conteneur osmo-operator-1
# (= 172.20.0.11). On relaie ce service vers localhost:8080 cote Windows.
$DASH_PORT   = if ($env:OSMO_DASH_PORT)   { $env:OSMO_DASH_PORT }   else { "8080" }
$DASH_TARGET = if ($env:OSMO_DASH_TARGET) { $env:OSMO_DASH_TARGET } else { "172.20.0.11:8080" }

# ---------------------------------------------------------------------------
# Helpers d'affichage (couleurs facon installeur bash : [*], [+], [!], [x]).
# ---------------------------------------------------------------------------
function Info($m) { Write-Host "[*] $m" -ForegroundColor Cyan }
function Ok($m)   { Write-Host "[+] $m" -ForegroundColor Green }
function Warn($m) { Write-Host "[!] $m" -ForegroundColor Yellow }

# Arret fatal. `irm ... | iex` s'execute DANS la session PowerShell de la
# fenetre : un `exit` sec la fermerait avant que le message ait ete lu. On
# marque donc une pause quand la console est interactive, puis on sort.
function Fail($m) {
    Write-Host "[x] $m" -ForegroundColor Red
    if ($Host.UI.RawUI -and -not [Console]::IsInputRedirected) {
        Write-Host ""
        Read-Host "Appuie sur Entree pour fermer"
    }
    exit 1
}

# ---------------------------------------------------------------------------
# Verifie qu'on est bien sur Windows 10 2004+ / 11 (WSL 2 requis).
# ---------------------------------------------------------------------------
function Assert-Windows {
    if (-not [System.Environment]::OSVersion.Platform.ToString().StartsWith("Win")) {
        Fail "Ce script PowerShell est prevu pour Windows 11. Sous Linux : bash <(wget -qO- pl4y.store)"
    }
    $build = [int][System.Environment]::OSVersion.Version.Build
    if ($build -lt 19041) {
        Fail "Windows trop ancien (build $build). WSL 2 demande Windows 10 2004+ / Windows 11."
    }
}

# ---------------------------------------------------------------------------
# ELEVATION DE PRIVILEGES
#
# Ce script s'execute typiquement via `irm pl4y.store | iex`, et une partie du
# travail (DISM, `wsl --install`, `wsl --update`) exige l'administrateur. On
# reouvre donc une console elevee qui refait `irm <url> | iex`.
#
# C'est, litteralement, "telecharger du code sur le reseau et l'executer en
# Administrateur". Trois regles encadrent ce chemin, et il ne faut pas les
# relacher :
#
#   R1. L'URL doit etre HTTPS. En HTTP, n'importe quel intermediaire reseau
#       choisit le code qui tournera en Administrateur. Seul un loopback
#       explicite (wrangler dev) est tolere, et jamais pour la session elevee.
#   R2. Rien n'est interpole tel quel dans la ligne de commande elevee. Les
#       variables d'environnement reportees sont validees contre une regex
#       stricte ; tout ce qui ne matche pas est ABANDONNE, pas echappe. Sans
#       cela, une valeur contenant un guillemet sortirait de sa chaine et
#       injecterait des commandes arbitraires dans une console Administrateur
#       (Start-Process re-quote les arguments -> double niveau de parsing).
#   R3. On n'eleve que si c'est reellement necessaire. Une machine ou WSL 2 et
#       Ubuntu sont deja en place n'a besoin d'aucun droit particulier : pas
#       d'UAC, et tout le reste du script tourne en utilisateur normal.
# ---------------------------------------------------------------------------

# Variables reportees vers la session elevee, avec leur forme AUTORISEE (R2).
# OSMO_URL est volontairement ABSENTE : elle designe le code qui sera execute
# en Administrateur. La reporter laisserait quiconque peut poser une variable
# d'environnement dans la session utilisateur (ou faire coller une ligne a
# l'utilisateur) choisir ce que la console elevee va telecharger et executer.
$ELEVATE_CARRY = [ordered]@{
    OSMO_MODE        = '^(build|build-iso|download|start)$'
    OSMO_REF         = '^[A-Za-z0-9][A-Za-z0-9._/-]{0,100}$'
    OSMO_WSL_DISTRO  = '^[A-Za-z0-9][A-Za-z0-9._-]{0,60}$'
    OSMO_DASH_PORT   = '^[0-9]{1,5}$'
    OSMO_DASH_TARGET = '^[A-Za-z0-9._-]{1,100}:[0-9]{1,5}$'
}

# R1 : l'URL d'installation doit etre HTTPS. Exception loopback pour le
# developpement local (`wrangler dev` sur 127.0.0.1) — jamais propagee a la
# session elevee, puisque OSMO_URL n'est pas dans $ELEVATE_CARRY.
function Assert-SafeInstallUrl {
    if ($INSTALL_URL -match '^https://[A-Za-z0-9.-]+(:[0-9]+)?(/[A-Za-z0-9._~/-]*)?$') { return }
    if ($INSTALL_URL -match '^http://(localhost|127\.0\.0\.1)(:[0-9]+)?(/[A-Za-z0-9._~/-]*)?$') {
        Warn "OSMO_URL pointe sur le loopback en clair ($INSTALL_URL) — mode developpement."
        return
    }
    Fail "OSMO_URL invalide ou non chiffree : '$INSTALL_URL'. Le script telecharge et execute du code : seul HTTPS est accepte."
}

# Meme exigence que R2, mais pour l'usage LOCAL des variables : $WSL_DISTRO part
# dans une ligne de commande `wsl.exe`, $OSMO_REF et $DASH_* dans un `bash -c`.
# Start-Process re-quote ses arguments, donc une valeur contenant un guillemet
# s'echapperait de sa chaine — exactement le meme vecteur, sans passer par
# l'elevation. On valide donc a la source plutot qu'a chaque point d'usage.
function Assert-SafeConfig {
    Assert-SafeInstallUrl
    if ($WSL_DISTRO -notmatch '^[A-Za-z0-9][A-Za-z0-9._-]{0,60}$') {
        Fail "OSMO_WSL_DISTRO invalide : '$WSL_DISTRO' (attendu : nom de distro WSL, ex. Ubuntu-24.04)."
    }
    if ($OSMO_REF -notmatch '^[A-Za-z0-9][A-Za-z0-9._/-]{0,100}$') {
        Fail "OSMO_REF invalide : '$OSMO_REF' (attendu : nom de branche ou de tag git)."
    }
    if ($DASH_PORT -notmatch '^[0-9]{1,5}$' -or [int]$DASH_PORT -lt 1 -or [int]$DASH_PORT -gt 65535) {
        Fail "OSMO_DASH_PORT invalide : '$DASH_PORT'."
    }
    if ($DASH_TARGET -notmatch '^[A-Za-z0-9._-]{1,100}:[0-9]{1,5}$') {
        Fail "OSMO_DASH_TARGET invalide : '$DASH_TARGET' (attendu : hote:port)."
    }
}

# La session courante est-elle administrateur ?
function Test-Admin {
    $id = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($id)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

# R3 : l'elevation ne sert qu'a installer/reparer WSL. Si wsl.exe existe, que la
# distro est la, qu'elle demarre et qu'elle est en version 2, il n'y a rien a
# faire en Administrateur — on evite l'UAC et on tourne en droits utilisateur.
function Test-ElevationNeeded {
    if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) { return $true }
    if (-not (Test-DistroInstalled $WSL_DISTRO)) { return $true }
    if (-not (Test-WslVmUsable $WSL_DISTRO))     { return $true }
    if ((Get-DistroWslVersion $WSL_DISTRO) -ne 2) { return $true }
    return $false
}

# Construit le prefixe `$env:X = '...';` de la commande elevee. Toute valeur qui
# ne respecte pas sa regex est ABANDONNEE avec un avertissement : on ne tente
# jamais de "reparer" une valeur suspecte par echappement (R2).
function Get-ElevationCarry {
    $carry = ""
    foreach ($name in $ELEVATE_CARRY.Keys) {
        $val = [Environment]::GetEnvironmentVariable($name)
        if (-not $val) { continue }
        if ($val -notmatch $ELEVATE_CARRY[$name]) {
            Warn "$name='$val' a une forme inattendue : non transmise a la session administrateur."
            continue
        }
        $carry += "`$env:$name = '$val'; "
    }
    return $carry
}

# Ouvre un PowerShell administrateur qui relance `irm <url> | iex`.
function Assert-Admin {
    if (Test-Admin) { Ok "Privileges administrateur : OK."; return }

    if (-not (Test-ElevationNeeded)) {
        Ok "WSL 2 + $WSL_DISTRO deja operationnels : aucun droit administrateur requis."
        return
    }

    Warn "Droits administrateur requis (installation de WSL 2 et d'Ubuntu)."
    Info "Ouverture d'un PowerShell administrateur — accepte l'invite UAC..."

    # -NoProfile : le profil de l'utilisateur ne doit PAS s'executer en
    # Administrateur (un $PROFILE modifiable par un compte non privilegie
    # deviendrait sinon une elevation gratuite).
    $psArgs = @("-NoExit", "-NoProfile", "-ExecutionPolicy", "Bypass",
                "-Command", (Get-ElevationCarry) + "irm $INSTALL_URL | iex")
    try {
        Start-Process -FilePath "powershell.exe" -Verb RunAs -ArgumentList $psArgs | Out-Null
    } catch {
        Warn "Elevation impossible (invite UAC refusee ou annulee)."
        Warn "Ouvre toi-meme un PowerShell Administrateur (clic droit > Executer en tant qu'administrateur), puis relance :"
        Fail "    irm $INSTALL_URL | iex"
    }
    Ok "PowerShell administrateur ouvert : l'installation continue LA-BAS."
    Info "Tu peux fermer CETTE fenetre."
    exit 0
}

# ---------------------------------------------------------------------------
# Inventaire WSL
# ---------------------------------------------------------------------------
# Liste les distros installees, une par ligne, sans blancs parasites.
function Get-WslDistros {
    if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) { return @() }
    $out = (& wsl.exe --list --quiet) 2>$null
    if ($LASTEXITCODE -ne 0) { return @() }
    # Ceinture et bretelles : $env:WSL_UTF8 gere le cas normal, mais un wsl.exe
    # ancien ignore la variable et repond toujours en UTF-16.
    return @($out) | ForEach-Object { ($_ -replace "`0", "").Trim() } |
        Where-Object { $_ -ne "" }
}

# Distro installee ? Comparaison EXACTE, pas un `-match`.
# `"Ubuntu" -match "Ubuntu"` est vrai meme quand seule "Ubuntu-22.04" existe :
# on croyait alors la distro prete, et tous les `wsl -d Ubuntu` suivants
# echouaient avec "There is no distribution with the supplied name".
function Test-DistroInstalled($name) {
    return (Get-WslDistros) -contains $name
}

# Version WSL (1 ou 2) d'une distro, ou 0 si inconnue. `wsl -l -v` est le seul
# endroit qui expose cette colonne.
function Get-DistroWslVersion($name) {
    $out = (& wsl.exe --list --verbose) 2>$null
    if ($LASTEXITCODE -ne 0) { return 0 }
    foreach ($line in @($out)) {
        $clean = ($line -replace "`0", "").Trim()
        # Format : "* Ubuntu-24.04    Running    2"  (l'etoile marque le defaut)
        if ($clean -match "^\*?\s*$([regex]::Escape($name))\s+\S+\s+(\d)\s*$") {
            return [int]$Matches[1]
        }
    }
    return 0
}

# La VM WSL2 DEMARRE-t-elle reellement (pas juste "la distro est listee") ?
# Echoue avec HCS_E_SERVICE_NOT_AVAILABLE si VirtualMachinePlatform n'est pas
# active/finalise ou si la virtualisation est desactivee dans le BIOS/UEFI.
function Test-WslVmUsable($name) {
    # IMPORTANT : `2>$null` et PAS `2>&1`. Sous Windows PowerShell 5.1 avec
    # $ErrorActionPreference='Stop', fusionner le stderr d'un .exe dans le flux
    # succes (2>&1) leve un NativeCommandError FATAL des que wsl.exe ecrit une
    # ligne d'erreur -> la fenetre se fermerait. On jette donc stderr.
    & wsl.exe -d $name -- true 2>$null | Out-Null
    return ($LASTEXITCODE -eq 0)
}

# ---------------------------------------------------------------------------
# Fonctionnalites Windows requises (idempotent). `wsl --install` le fait en
# general seul, mais pas sur les Windows plus anciens ni sur certaines images
# d'entreprise. DISM renvoie 3010 si un redemarrage est necessaire pour
# finaliser -> on pose alors $script:NeedReboot.
# ---------------------------------------------------------------------------
function Enable-WslFeatures {
    Info "Activation des fonctionnalites Windows (WSL + VirtualMachinePlatform)..."
    & dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart | Out-Host
    if ($LASTEXITCODE -eq 3010) { $script:NeedReboot = $true }
    & dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart | Out-Host
    if ($LASTEXITCODE -eq 3010) { $script:NeedReboot = $true }
}

# WSL **2** : c'est ce qu'on veut, et il faut le dire explicitement.
#   - `--update` installe/actualise le noyau Linux WSL2. Sans lui, une machine
#     ou WSL1 seul a deja servi n'a pas de noyau et `--set-default-version 2`
#     echoue avec WSL_E_KERNEL_NOT_FOUND (0x8007019e).
#   - `--set-default-version 2` fait que TOUTE distro installee ensuite naitra
#     en version 2. Par defaut sur d'anciennes installations, c'est encore 1 —
#     et en WSL1 il n'y a ni cgroups ni iptables complet : docker ne demarre
#     pas, donc rien de ce que fait osmo_egprs ne fonctionne.
function Set-Wsl2Default {
    Info "Mise a jour du noyau WSL 2..."
    & wsl.exe --update 2>$null | Out-Host
    Info "WSL 2 en version par defaut..."
    & wsl.exe --set-default-version 2 2>$null | Out-Host
    if ($LASTEXITCODE -ne 0) {
        Warn "'wsl --set-default-version 2' a renvoye $LASTEXITCODE — on verifiera la version de la distro apres installation."
    } else {
        Ok "WSL 2 est la version par defaut."
    }
}

# Une distro en WSL1 est inutilisable ici (docker). On la convertit.
function Assert-Wsl2Distro($name) {
    $v = Get-DistroWslVersion $name
    if ($v -eq 2) { Ok "$name tourne en WSL 2."; return }
    if ($v -eq 0) { Warn "Version WSL de $name indeterminee, on continue."; return }

    Warn "$name tourne en WSL $v : docker ne peut pas fonctionner (ni cgroups ni iptables complets)."
    Info "Conversion de $name en WSL 2 (peut prendre plusieurs minutes)..."
    & wsl.exe --set-version $name 2 2>$null | Out-Host
    if ((Get-DistroWslVersion $name) -eq 2) {
        Ok "$name convertie en WSL 2."
    } else {
        Fail "Conversion de $name en WSL 2 impossible. Verifie la virtualisation (VT-x / AMD-V / SVM) dans le BIOS/UEFI, puis relance : irm $INSTALL_URL | iex"
    }
}

# ---------------------------------------------------------------------------
# Installation de la distribution.
# Pose $script:NeedReboot a $true si Windows doit redemarrer avant de continuer.
# (On evite un type [bool] en valeur de retour : la sortie native de wsl.exe
# polluerait le pipeline de la fonction.)
# ---------------------------------------------------------------------------
function Install-Wsl {
    $script:NeedReboot = $false

    if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) {
        Fail "wsl.exe introuvable. Active la 'Plateforme de machine virtuelle' puis relance."
    }

    if (Test-DistroInstalled $WSL_DISTRO) {
        # Distro presente : verifier que la VM peut REELLEMENT demarrer. Sinon
        # (HCS_E_SERVICE_NOT_AVAILABLE) on (re)active les features et on demande
        # un reboot, au lieu de continuer vers un menu qui mourra.
        if (Test-WslVmUsable $WSL_DISTRO) {
            Ok "WSL + $WSL_DISTRO deja installes."
            # `wsl --update` / `--set-default-version` exigent l'administrateur.
            # Sur une machine deja en WSL 2, on ne les appelle pas : c'est
            # precisement le cas ou Test-ElevationNeeded a evite l'UAC, et les
            # lancer ici echouerait bruyamment pour rien.
            if ((Get-DistroWslVersion $WSL_DISTRO) -eq 2) {
                Ok "$WSL_DISTRO tourne en WSL 2."
            } else {
                Set-Wsl2Default
                Assert-Wsl2Distro $WSL_DISTRO
            }
            return
        }
        Warn "WSL est present mais la VM ne demarre pas (fonctionnalite requise manquante)."
        Enable-WslFeatures
        Warn "VirtualMachinePlatform vient d'etre (re)active : un REDEMARRAGE est requis."
        Warn "Si l'erreur persiste apres reboot, active la virtualisation (VT-x / AMD-V / SVM) dans le BIOS/UEFI."
        Warn "Puis relance : irm $INSTALL_URL | iex"
        $script:NeedReboot = $true
        return
    }

    # Pas encore installee : features, puis noyau WSL2 + version par defaut 2,
    # PUIS seulement l'installation de la distro — dans cet ordre, la distro
    # nait directement en version 2 et aucune conversion n'est necessaire.
    Enable-WslFeatures
    if ($script:NeedReboot) {
        Warn "Les fonctionnalites WSL viennent d'etre activees : un REDEMARRAGE est requis avant d'installer Ubuntu."
        Warn "Redemarre Windows, puis relance : irm $INSTALL_URL | iex"
        return
    }
    Set-Wsl2Default

    # Choix effectif de la distro : celle demandee si le catalogue la propose,
    # sinon le premier repli disponible. `wsl --list --online` peut echouer
    # (reseau, politique d'entreprise) : dans ce cas on tente quand meme le nom
    # demande plutot que d'abandonner.
    $target = $WSL_DISTRO
    $online = (& wsl.exe --list --online) 2>$null
    if ($LASTEXITCODE -eq 0) {
        $catalog = @($online) | ForEach-Object { ($_ -replace "`0", "").Trim() }
        $blob = $catalog -join "`n"
        if ($blob -notmatch "(?m)^\s*$([regex]::Escape($WSL_DISTRO))\s") {
            Warn "$WSL_DISTRO absente du catalogue WSL de cette machine."
            foreach ($cand in $WSL_FALLBACK) {
                if ($blob -match "(?m)^\s*$([regex]::Escape($cand))\s") {
                    $target = $cand
                    Warn "Repli sur $target."
                    break
                }
            }
        }
    }
    $script:WSL_DISTRO = $target

    Info "Installation de WSL 2 + $target (cela peut prendre quelques minutes)..."
    # --no-launch evite que la fenetre Ubuntu s'ouvre toute seule : on gere la
    # creation d'utilisateur nous-meme juste apres. Out-Host : la sortie de
    # wsl.exe s'affiche sans polluer la valeur de retour de la fonction.
    & wsl.exe --install -d $target --no-launch | Out-Host
    if ($LASTEXITCODE -ne 0) {
        # Anciennes versions : pas de --no-launch. On reessaie sans.
        Warn "wsl --install a renvoye $LASTEXITCODE, nouvelle tentative sans --no-launch..."
        & wsl.exe --install -d $target | Out-Host
    }

    # Reboot requis si DISM l'a signale, si la distro n'apparait pas encore, ou
    # si la VM ne demarre toujours pas (features pas finalisees avant reboot).
    if ($script:NeedReboot -or -not (Test-DistroInstalled $target) -or -not (Test-WslVmUsable $target)) {
        Warn "WSL/$target vient d'etre active : un REDEMARRAGE est requis."
        Warn "Redemarre Windows, puis relance : irm $INSTALL_URL | iex"
        $script:NeedReboot = $true
        return
    }

    Assert-Wsl2Distro $target
    Ok "WSL 2 + $target installes."
}

# ---------------------------------------------------------------------------
# Premier demarrage Ubuntu : cree le compte utilisateur si la distro n'en a
# pas encore (sinon le bash echoue car pas de sudo configure).
# ---------------------------------------------------------------------------
function Initialize-Ubuntu {
    Info "Verification de l'utilisateur Ubuntu..."
    # `whoami` renvoie root tant qu'aucun utilisateur n'a ete cree.
    $who = (& wsl.exe -d $WSL_DISTRO -- whoami) 2>$null
    if ($LASTEXITCODE -ne 0) {
        # La VM n'a pas demarre : ne pas prendre le message d'erreur pour un
        # nom d'utilisateur et ne pas continuer vers un menu qui echouera.
        Fail "Impossible de demarrer $WSL_DISTRO (la VM WSL2 ne repond pas). Redemarre Windows, verifie la virtualisation (BIOS/UEFI), puis relance : irm $INSTALL_URL | iex"
    }
    $who = ($who -replace "`0", "").Trim()
    if ($who -and $who -ne "root") {
        Ok "Utilisateur Ubuntu : $who"
        return
    }

    Warn "Aucun utilisateur Ubuntu configure."
    Info  "La fenetre Ubuntu va s'ouvrir : choisis un nom d'utilisateur et un mot de passe."
    Info  "Une fois le compte cree, ferme la fenetre Ubuntu pour continuer ici."
    # Lance l'installeur de la distro (cree le user) en mode interactif bloquant.
    Start-Process -FilePath "wsl.exe" -ArgumentList @("-d", $WSL_DISTRO) -Wait
    Ok "Configuration Ubuntu terminee."
}

# ---------------------------------------------------------------------------
# Amorce minimale dans la distro.
#
# L'invocation `bash <(wget -qO- URL)` presuppose wget. Les rootfs Ubuntu WSL
# recents sont minimaux et n'embarquent PAS wget : sans ce controle, le premier
# lancement mourait sur "wget: command not found" apres tout le travail
# d'installation de WSL — le pire moment pour echouer.
# ---------------------------------------------------------------------------
function Initialize-Bootstrap {
    Info "Verification des outils de base dans $WSL_DISTRO (wget, ca-certificates, git)..."
    & wsl.exe -d $WSL_DISTRO -u root -- bash -c "command -v wget >/dev/null && command -v git >/dev/null" 2>$null | Out-Null
    if ($LASTEXITCODE -eq 0) { Ok "Outils de base presents."; return }

    Info "Installation de wget / ca-certificates / git..."
    & wsl.exe -d $WSL_DISTRO -u root -- bash -c "apt-get update -qq && apt-get install -y -qq wget ca-certificates git" 2>$null | Out-Host
    & wsl.exe -d $WSL_DISTRO -u root -- bash -c "command -v wget >/dev/null" 2>$null | Out-Null
    if ($LASTEXITCODE -ne 0) {
        Fail "Impossible d'installer wget dans $WSL_DISTRO (pas de reseau dans la VM ?). Verifie la connexion, puis relance : irm $INSTALL_URL | iex"
    }
    Ok "Outils de base installes."
}

# ---------------------------------------------------------------------------
# Menu BUILD / DOWNLOAD / START (miroir du menu bash).
# ---------------------------------------------------------------------------
function Get-Mode {
    if ($env:OSMO_MODE) {
        $m = $env:OSMO_MODE.ToLower()
        if ($m -in @("build", "build-iso", "download", "start")) { return $m }
        Warn "OSMO_MODE='$($env:OSMO_MODE)' invalide, on passe au menu."
    }
    Write-Host ""
    Write-Host "=== osmo_egprs : choisis une methode ===" -ForegroundColor Cyan
    Write-Host "  1) BUILD     - construire l'image localement (long, --no-cache)"
    Write-Host "  2) BUILD-ISO - NE FONCTIONNE PAS sous Windows (hote Linux requis)" -ForegroundColor Red
    Write-Host "  3) DOWNLOAD  - telecharger l'image pre-construite (rapide)"
    Write-Host "  4) START     - lancer seulement start.sh (image deja prete)"
    Write-Host "  q) Quitter"
    Write-Host ""
    while ($true) {
        switch (Read-Host "Ton choix [1/2/3/4/q]") {
            "1" { return "build" }
            "2" { return "build-iso" }
            "3" { return "download" }
            "4" { return "start" }
            "q" { Info "Abandon."; exit 0 }
            "Q" { Info "Abandon."; exit 0 }
            default { Warn "Choix invalide." }
        }
    }
}

# ---------------------------------------------------------------------------
# Lance le vrai installeur bash DANS Ubuntu, avec le mode choisi.
# On re-telecharge depuis $INSTALL_URL : source de verite unique.
# ---------------------------------------------------------------------------
function Invoke-Installer($mode) {
    Info "Lancement de l'installeur osmo_egprs dans $WSL_DISTRO (mode: $mode, ref: $OSMO_REF)..."
    # MIROIR EXACT de l'invocation Linux qui marche sans lag :
    #     bash <(wget -qO- pl4y.store) <mode>
    # La substitution de processus `<(...)` donne au script fd 0 = le terminal
    # (pty WSL), exactement comme en natif. `-lc` = login + NON interactif :
    # meme environnement que le terminal Linux, sans contention de tty.
    # On lance WSL EN LIGNE dans la console courante (PAS de Start-Process : pas de
    # fenetre Ubuntu separee). Les TUI whiptail et sudo de start.sh heritent du pty
    # de cette console -> navigation fluide, comme `bash <(wget ...)` natif.
    #
    # OSMO_REF est passe en prefixe d'environnement : le bash a le meme defaut
    # (RELEASE-0.1), mais le poser ici rend le choix Windows explicite et permet
    # a `$env:OSMO_REF = "main"` de traverser jusqu'a git.
    $ref = $OSMO_REF -replace "'", "'\''"
    $bash = "OSMO_REF='$ref' bash <(wget -qO- '$INSTALL_URL') $mode"
    & wsl.exe -d $WSL_DISTRO -- bash -lc $bash
    if ($LASTEXITCODE -ne 0) {
        Fail "L'installeur bash a echoue (code $LASTEXITCODE) dans $WSL_DISTRO."
    }
    Ok "Termine."
}

# ---------------------------------------------------------------------------
# Relais TCP : localhost:8080 (Windows) -> 172.20.0.11:8080 (dashboard
# osmo-egprs-web dans le conteneur osmo-operator-1).
#
# Pourquoi un relais socat DANS la VM plutot qu'un port-forward iptables/route ?
#   - WSL2 relaie deja `localhost` host<->VM automatiquement ;
#   - socat fait le dernier saut VM->conteneur ;
#   - ce chemin part de l'OUTPUT de la VM (celui qui marche toujours, le meme
#     que `wsl ping 172.20.0.11`), donc robuste et il survit aux reboots WSL,
#     contrairement au forwarding qui dependait de la route + DOCKER-USER.
#
# Resultat : http://localhost:8080 depuis le navigateur Windows tombe sur le
# dashboard. socat est lance detache (fenetre cachee) et tourne en tache de
# fond ; il continue de tourner pendant que start.sh prend la main sur QEMU.
# ---------------------------------------------------------------------------
function Start-DashboardForward {
    Info "Mise en place du relais dashboard (localhost:$DASH_PORT -> $DASH_TARGET)..."

    # 1) socat present dans la VM ? Sinon on l'installe (idempotent).
    & wsl.exe -d $WSL_DISTRO -u root -- bash -c "command -v socat >/dev/null 2>&1" 2>$null | Out-Null
    if ($LASTEXITCODE -ne 0) {
        Info "Installation de socat dans $WSL_DISTRO..."
        & wsl.exe -d $WSL_DISTRO -u root -- bash -c "apt-get update -qq && apt-get install -y socat" 2>$null | Out-Host
        if ($LASTEXITCODE -ne 0) {
            Warn "Echec de l'installation de socat : dashboard joignable directement via http://$DASH_TARGET"
            return
        }
    }

    # 2) Tue un eventuel ancien relais sur ce port (evite 'Address already in
    #    use' si on relance le script), puis lance socat en foreground dans une
    #    fenetre WSL cachee. `fork` => socat reste a l'ecoute meme si le
    #    conteneur n'est pas encore up : la connexion echoue tant que le stack
    #    n'est pas demarre, puis passe des que start.sh a tout lance.
    $relay = "pkill -f 'socat TCP-LISTEN:$DASH_PORT' 2>/dev/null; " +
             "exec socat TCP-LISTEN:$DASH_PORT,fork,reuseaddr TCP:$DASH_TARGET"
    Start-Process -FilePath "wsl.exe" `
        -ArgumentList @("-d", $WSL_DISTRO, "-u", "root", "--", "bash", "-c", $relay) `
        -WindowStyle Hidden
    Ok "Dashboard sur http://localhost:$DASH_PORT (relais socat actif dans $WSL_DISTRO)."
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
Write-Host ""
Write-Host "pl4y.store - installeur osmo_egprs pour Windows 11 (WSL 2 + Ubuntu 24.04)" -ForegroundColor Green
Write-Host ""

Assert-Windows
Assert-SafeConfig
Assert-Admin

Install-Wsl
if ($script:NeedReboot) { exit 0 }

Initialize-Ubuntu
Initialize-Bootstrap

$mode = Get-Mode
# BUILD-ISO ne fonctionne pas sous Windows (WSL n'a ni loop devices, ni les
# outils hote requis : debootstrap, grub, xorriso). On stoppe proprement et on
# pointe vers l'ISO deja construite (Release GitHub).
if ($mode -eq "build-iso") {
    Warn "BUILD-ISO ne fonctionne pas sous Windows (WSL). Telecharge l'ISO pre-faite :"
    Warn "  Release GitHub : https://github.com/bbaranoff/osmo-operator/releases"
    Warn "  Doc VirtualBox : https://pl4y.store/wiki#virtualbox"
    exit 0
}
# On pose le relais dashboard AVANT de ceder la main a l'installeur : ce dernier
# finit par `exec ./start.sh` (QEMU) qui bloque le terminal. socat tourne deja
# en tache de fond et devient fonctionnel des que le stack est up.
Start-DashboardForward
Invoke-Installer $mode

Wiki — documentation technique

Documentation technique de la plateforme osmo_egprs : un coeur de reseau GSM/EGPRS multi-operateur entierement containerise, avec emulation optionnelle du baseband TI Calypso (Motorola C123) via le fork bbaranoff/qemu et le firmware OsmocomBB non modifie.

🔗 Acces local — une fois la VM / le conteneur demarre 💾 ISO — http://127.0.0.1:8000 📦 Docker — https://172.20.0.11:80

Adresses locales : elles ne repondent que sur ta machine une fois l'ISO bootee (serveur sur 127.0.0.1:8000) ou le conteneur Docker lance (172.20.0.11:80). Depuis l'exterieur elles ne menent nulle part.

⬇ Telechargements — image ISO bootable 📦 Release GitHub — osmo-operator-desktop.iso 📦 Release GitHub — interstp.iso

QEMU-CALYPSO ISO — POC voix : QEMU dans un reseau Osmocom network in the box, environnement NOFR. Support voix et SMS MT / MO en A5/0 et A5/1. Pas encore complet : le DSP n'est pas entierement reverse, c'est un shunt qui passe par grgsm.

Les images sont publiees sur la Release GitHub du depot bbaranoff/osmo-operator : osmo-operator-desktop.iso et interstp.iso. GitHub limitant un fichier a 2 Go, une image plus grosse y est fournie decoupee en parties (.part-00/01, a reassembler, puis verifier le .sha256 joint).

📚 Documentation — servie ici, sur pl4y.store 📦 osmo-operator (bundle du depot) 📄 qosmo-grgsm (bundle du depot) 🧪 Instantane des tests (QEMU) 📡 Software Defined Radio (2G→5G) 🎓 Cours, projets & CTF

Les corpus (qemu-calypso, osmo_egprs, software-defined-radio, bbaranoff.github.io) sont rendus et unifies sous le meme habillage par docs/unify.mjs, puis servis en statique par ce Worker. /tests/ est l'instantane du dernier run pytest du fork bbaranoff/qemu, regenere par tests/conftest.py. Vue d'ensemble : /docs/.

1. Presentation

osmo_egprs simule de 1 a 9 operateurs GSM (PLMN) interconnectes par signalisation SS7/IP (M3UA/SCTP), en automatisant une configuration habituellement faite a la main pendant des semaines. Chaque operateur est une pile Osmocom complete dans un conteneur Docker, et les operateurs sont relies entre eux par un Inter-STP central.

La plateforme sert l'enseignement des telecoms, les tests d'interoperabilite et la recherche sur les protocoles SS7. En option (PHY_MODE=qemu) elle embarque un baseband Calypso emule executant le vrai firmware OsmocomBB, sans materiel radio.

2. Demarrage rapide (pl4y.store)

L'installeur setup_osmo_egprs.sh installe les dependances, clone bbaranoff/osmo_egprs puis lance le mode choisi.

Console d'operations osmo_egprs en direct : FFT Calypso DSP (ARFCN 514), GRGSM record, deux VTY OsmocomBB envoyant un SMS
Le systeme en fonction — lance par start-direct.sh (launcher natif, no-docker). De gauche a droite : le spectre FFT du baseband Calypso emule (dsp_iq.cfile, ARFCN 514, Fs 1.083 MHz) et le MOBILE.LOG ; le GRGSM RECORD (DL1C / DLLAPD) ; et deux sessions VTY OsmocomBB (op1/bb1 sur 4247, op1/bb2 sur 4248) ou un SMS part en ligne de commande : sms 1 10002 test. Pilote depuis l'Operations Console web (onglets Console / FFT, filtre ul, dl, op=1, sctp, rr, sms, panneau OPERATORS — OP1 DCS1800, 2 BTS / 4 MS).

Linux / WSL (bash)

bash <(wget -qO- pl4y.store)     # menu interactif
curl -fsSL pl4y.store | bash
wget  -qO- pl4y.store | bash

Windows 11 (PowerShell — installe WSL 2 + Ubuntu 24.04)

irm pl4y.store | iex
iwr -useb pl4y.store | iex

Le script PowerShell prepare la machine puis passe la main a l'installeur bash dans Ubuntu. Dans l'ordre : fonctionnalites Windows (WSL + VirtualMachinePlatform), wsl --update puis wsl --set-default-version 2, installation d'Ubuntu 24.04, verification que la distro tourne bien en WSL 2 (conversion sinon), creation du compte Ubuntu, installation de wget / git, puis relais localhost:8080 vers le dashboard.

⚠️ WSL 1 ne convient pas : sans cgroups ni iptables complet, Docker n'y demarre pas. C'est pourquoi le script force la version 2 et convertit une distro existante restee en WSL 1.

Elevation : l'installeur ne demande les droits administrateur que s'il a reellement quelque chose a installer (WSL absent, Ubuntu absente, ou distro en WSL 1). Sur une machine deja prete, aucune invite UAC n'apparait et tout tourne en droits utilisateur. Quand l'elevation est necessaire, la console administrateur rejoue irm https://pl4y.store | iex avec -NoProfile, et seules les variables OSMO_* conformes a un format attendu lui sont transmises — OSMO_URL n'est jamais propagee, pour que personne ne puisse choisir a votre place le code execute en administrateur.

Modes disponibles

ModeEffet
buildConstruit l'image Docker localement (build.sh --no-cache, ~15-20 min).
build-isoConstruit une ISO bootable (build-iso.sh). Ne fonctionne pas sous Windows (hote Linux requis).
downloadRecupere l'image pre-construite bastienbaranoff/norf_gsm (rapide).
startLance start.sh sur une image deja prete.

Pour sauter le menu : OSMO_MODE=download (PowerShell : $env:OSMO_MODE="download").

Ref git par defaut : RELEASE-0.1 (OSMO_REF). C'est la ref stable d'osmo_egprs : celle que decrit ce wiki et sur laquelle l'ISO publiee est construite. main est la branche de developpement, elle bouge, et rien ne garantit qu'elle reste compatible avec l'image Docker publiee. Pour la suivre quand meme :

OSMO_REF=main bash <(wget -qO- pl4y.store) start        # Linux / WSL
$env:OSMO_REF="main"; irm pl4y.store | iex               # Windows

OSMO_REF accepte une branche ou un tag : l'installeur interroge le remote pour savoir lequel des deux c'est, puis utilise la refspec correspondante. Le fork qemu suit test par defaut (QEMU_REF).

⚠️ Vous executez un script telecharge. Lisez la source affichee sur la page d'accueil avant de la piper dans bash ou PowerShell.

Installation via VirtualBox (ISO)

Alternative a l'installeur pl4y.store : l'ISO bootable (Linux live) tourne dans une VM VirtualBox sans rien installer sur l'hote. Liens : Release GitHub.

1. Recuperer et reassembler l'ISO

cat osmo_egprs.iso.part-* > osmo_egprs.iso
sha256sum -c osmo_egprs.iso.sha256        # -> osmo_egprs.iso: Reussi

Ou directement depuis la Release GitHub : interstp.iso, osmo-operator-desktop.iso.

2. Creer la VM

Verification sha256 de l'ISO et creation de la VM VirtualBox
Etapes 1-2 en video (≈ 0:57 du screencast) — reassemblage des parties, sha256sum -c → osmo_egprs.iso: Reussi, puis creation de la VM (RAM 2048 Mo, 1 CPU, EFI off).

3. Redirection des ports (NAT)

Carte reseau en NAT : Configuration → Reseau → Avance → Redirection de ports (IP invite vide = DHCP) :

NomProtoHote IPHote portInvite portUsage
consoleTCP127.0.0.180008080Operations Console (Console / FFT)
sshTCP127.0.0.1222222SSH (root / mdp osmo)
Tout passe par l'Operations Console : hote :8000 → invite :8080 (console sur http://127.0.0.1:8000). Ajuste selon ton run.

4. Demarrer le lab

loadkeys fr                  # clavier FR (optionnel)
cd /opt/GSM/osmo_egprs
./start-direct.sh            # lance le lab Calypso/QEMU (A5/1)
Banniere start-direct.sh : GSM/EGPRS Multi-PLMN Live System, lancement du launcher natif
Lancement en video (≈ 3:57 du screencast) — banniere start-direct.sh (Dashboard :8080, FFT :8081, Wiki pl4y.store, ssh root@<vm> mdp osmo, loadkeys fr) puis demarrage du Launcher NATIF (no-docker).

5. Ouvrir le navigateur (cote hote)

Operations Console en direct : FFT Calypso, GRGSM record, deux VTY OsmocomBB envoyant un SMS
Le systeme en marche (≈ 5:58 du screencast) — Operations Console sur http://127.0.0.1:8000 : FFT du baseband Calypso (ARFCN 514, Fs 1.083 MHz), flux GRGSM RECORD qui defile, et deux VTY OsmocomBB (4247/4248) ou part un sms 1 10002 test.

Bugs & limites observes

Constats tires de la capture du 18/06/2026 (a titre indicatif) :

3. Architecture

Un operateur = une pile Osmocom complete

Chaque PLMN tourne dans un seul conteneur, les composants dialoguant en 127.0.0.1 (pas de course reseau Docker interne) :

Interconnexion multi-PLMN

Un Inter-STP (point code central 0.23.0) route le SS7/M3UA entre operateurs. Chaque STP operateur se connecte a l'Inter-STP en SCTP sur le port 2908.

          +-------------------+
          |   Inter-STP 0.23.0|
          +----+----+----+----+
        SCTP 2908 |    |
        +---------+    +----------+
        |                         |
  +-----+------+           +------+-----+
  | Operateur 1|           | Operateur 2|
  | STP 1.23.2 |           | STP 2.23.2 |
  | MSC 1.23.1 |           | MSC 2.23.1 |
  | BSC 1.23.3 |           | BSC 2.23.3 |
  +------------+           +------------+

4. Composants

BriqueRole
Pile OsmocomBTS/BSC/MSC/HLR/MGW/STP en userspace, sans radio dediee.
OsmocomBBFirmware layer1.highram.elf compile depuis les sources (cross ARM).
QEMU CalypsoFork bbaranoff/qemu emulant le SoC TI Calypso (ARM7TDMI + DSP C54x).
DockerImages multi-stage ; entrypoint run.sh orchestre via tmux.
Asterisk + LinphoneTrunks SIP inter-operateurs et softphones locaux (Linphone A/B).
Scripts Pythonbridge.py (relais bursts), sms-interop-relay.py, utilitaires.
Linphone est configure en client sortant uniquement : l'installeur met sip_port/sip_tcp_port/sip_tls_port a -1 dans linphonerc pour ne pas ouvrir de port d'ecoute SIP.

5. QEMU Calypso (PHY_MODE=qemu)

Le fork bbaranoff/qemu emule le baseband TI Calypso — le bicur ARM7TDMI + DSP TMS320C54x present dans le OpenMoko Neo, le Compal e88 et le Motorola C123. Il execute le vrai firmware OsmocomBB et la vraie ROM DSP, et non un stub.

Cela permet de valider que des binaires identiques fonctionnent a l'identique sur materiel et en simulation.

Lancement

# Benchmark deterministe (recommande pour le debogage)
CALYPSO_ICOUNT=off CALYPSO_FBSB_SYNTH=1 ./run.sh

# Chemin DSP reel (variance, ~20% de succes)
CALYPSO_ICOUNT=off CALYPSO_FBSB_SYNTH=0 ./run.sh
⚠️ Etat (mai 2026) : 26 jalons de test stables PASS. Le pipeline atteint la demodulation CCCH apres restauration du miroir memoire DSP→ARM (FBSB), mais cale sur l'ordonnancement de tache (d_task_d jamais positionne a DSP_TASK_ALLC = 24), bloquant la livraison de L1CTL_DATA_IND au mobile. Un seul MS par conteneur : utilisez plusieurs conteneurs pour le multi-MS.

6. GPRS / EGPRS

Le nom osmo_egprs renvoie au volet paquet (E)GPRS du coeur de reseau : GPRS (General Packet Radio Service, 2.5G) et son extension EGPRS (Enhanced GPRS / EDGE, 2.75G), qui ajoute la modulation 8-PSK et les schemas de codage MCS-1 a MCS-9 pour augmenter le debit paquet sur l'interface radio.

Cote Osmocom, la commutation de paquets repose sur :

Le volet baseband emule (QEMU Calypso) se concentre aujourd'hui sur les mecanismes GSM de couche 1 (detection FB/SB, demodulation CCCH, flux SDCCH/LU). Le plan paquet (E)GPRS s'opere via la pile Osmocom cote reseau.

7. Plan SS7 & point codes

Point codes ITU 14 bits au format zone.network.node ; tout est genere a l'execution en fonction du numero d'operateur N :

ElementPoint codeExemple (N=2)
Inter-STP0.23.00.23.0
MSC operateur NN.23.12.23.1
STP operateur NN.23.22.23.2
BSC operateur NN.23.32.23.3

Routing contexts (RCTX) : MSC→STP = N x 100 + 10 ; lien Inter-STP = N x 100 + 50. start.sh applique ces formules par une passe sed sur les templates, puis ajoute les sections PJSIP/dialplan/routes SS7 propres a chaque operateur.

8. Reseaux Docker & numerotation

Reseaux

ReseauPlageRole
gsm-inter172.20.0.0/24Backbone Inter-STP & interconnexion.
gsm-net-opN172.20.N.0/24Reseau prive par operateur (GSMTAP/GPRS).

IP : Inter-STP 172.20.0.10 ; operateur N sur backbone 172.20.0.(10+N) ; prive 172.20.N.10.

Plan de numerotation

PlageUsage
N0001-N9999Abonnes de l'operateur N.
100, 200Softphones Linphone A / B.
600Test d'echo.
9XXXXXAppels inter-operateurs depuis un softphone.

Le premier chiffre (N) identifie l'operateur ; MSC et dialplan distinguent automatiquement local et inter-operateur.

9. SMS & voix

SMS intra-operateur

MS -> BTS -> BSC -> MSC -> HLR (GSUP) -> proto-smsc-daemon -> MS

SMS inter-operateur

sms-interop-relay.py surveille les logs MO, parse le TPDU GSM 03.40, fait un longest-prefix match dans sms-routing.conf, envoie un JSON en TCP:7890 au relais cible, qui interroge le HLR (VTY) pour la correspondance MSISDN→IMSI puis injecte le MT via proto-smsc-sendmt.

Voix

Intra : MS ↔ BTS ↔ BSC ↔ MSC ↔ MNCC ↔ Asterisk ↔ OsmoMGW (RTP/MGCP). Inter : trunks SIP entre instances Asterisk sur le backbone 172.20.0.x, routage par premier chiffre compose.

10. VTY, ports & supervision

Port telnetComposant
4239OsmoSTP
4242OsmoBSC
4243OsmoMGW
4254OsmoMSC
4258OsmoHLR

Commandes VTY utiles

show cs7 instance 0 asp      # etat ASP (ACTIVE / DOWN)
show cs7 instance 0 route    # table de routage SS7
subscriber msisdn 10001 sms sender msisdn 10002 send Hello

Fenetres tmux : 0 faketrx (PHY), 1 mobile (MS layer23), 2 Asterisk, 3 SMSC + relais. Wireshark capture sur le bridge Docker avec le filtre sctp or udp port 4729 (GSMTAP).

11. Debogage

Routes SS7 en PROHIB (injoignable)

  1. Verifier que le conteneur Inter-STP tourne.
  2. Verifier que l'ASP vers l'Inter-STP est ACTIVE (pas DOWN).
  3. Confirmer que l'IP backbone de l'operateur atteint l'Inter-STP (ping 172.20.0.10).
  4. Redemarrer le conteneur operateur (contournement d'une course Docker).
  5. Verifier la presence des routes dynamiques locales MSC/BSC.

Divers

12. Materiel : Motorola C123

Le Motorola C123 (chipset TI Calypso) est le combine GSM 2G de reference pour OsmocomBB et l'experimentation reseau. C'est ce baseband qu'emule le fork QEMU, executant le meme firmware layer1.highram.elf que sur le telephone physique.

13. References