Installeur osmo_egprs — coeur de reseau GSM/EGPRS multi-operateur, containerise. — 📖 Wiki QEMU / osmo_egprs / EGPRS · 📚 Documentation (Calypso, SDR, cours & CTF)
Motorola C123 — combine GSM 2G a chipset TI Calypso, la reference historique pour OsmocomBB et l'experimentation reseau. Ici en plein scan de spectre.
bash <(wget -qO- pl4y.store)curl -fsSL pl4y.store | bashwget -qO- pl4y.store | bashirm pl4y.store | iexiwr -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
#!/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 "$@"
# 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
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.
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.
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).
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/.
Bundle complet du depot osmo-operator : le coeur de reseau GSM/EGPRS multi-PLMN (Docker, configs Osmocom, reseau, helpers, scripts de lancement, tests) — chaque fichier dans son bloc de code, avec filtre et recherche plein-texte.
Bundle complet du fork qosmo-grgsm (gr-gsm re-outille pour qemu-calypso) : baseband TI Calypso emule (DSP C54x / osmocom-bb), sources, scripts et documentation — chaque fichier dans son bloc de code.
La documentation software-defined-radio.com : 2G / 3G / 4G / 5G et SDR, en francais et en anglais (Sphinx + MyST).
Le contenu de bbaranoff.github.io : cours (Agile, UML, Git), projets radio (IMSI catcher, chiffrement, LoRa, ADS-B), CTF et jeux.
Le rapport de sante du banc osmo_egprs pour un operateur (mode natif) : STP, HLR, MSC, BSC, BTS, PCU, SGSN, GGSN, MGW, SIP et services — statut par composant et resume global.
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.
L'installeur setup_osmo_egprs.sh installe les dependances, clone
bbaranoff/osmo_egprs puis lance le mode choisi.
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).
bash <(wget -qO- pl4y.store) # menu interactif
curl -fsSL pl4y.store | bash
wget -qO- pl4y.store | bash
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.
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.
| Mode | Effet |
|---|---|
build | Construit l'image Docker localement (build.sh --no-cache, ~15-20 min). |
build-iso | Construit une ISO bootable (build-iso.sh). Ne fonctionne pas sous Windows (hote Linux requis). |
download | Recupere l'image pre-construite bastienbaranoff/norf_gsm (rapide). |
start | Lance 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).
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.
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.
Linux / Ubuntu (64-bit).osmo_egprs.iso, booter dessus (live).
sha256sum -c → osmo_egprs.iso: Reussi,
puis creation de la VM (RAM 2048 Mo, 1 CPU, EFI off).Carte reseau en NAT : Configuration → Reseau → Avance → Redirection de ports (IP invite vide = DHCP) :
| Nom | Proto | Hote IP | Hote port | Invite port | Usage |
|---|---|---|---|---|---|
| console | TCP | 127.0.0.1 | 8000 | 8080 | Operations Console (Console / FFT) |
| ssh | TCP | 127.0.0.1 | 2222 | 22 | SSH (root / mdp osmo) |
:8000 → invite :8080
(console sur http://127.0.0.1:8000). Ajuste selon ton run.loadkeys fr # clavier FR (optionnel)
cd /opt/GSM/osmo_egprs
./start-direct.sh # lance le lab Calypso/QEMU (A5/1)
start-direct.sh (Dashboard :8080, FFT :8081, Wiki pl4y.store,
ssh root@<vm> mdp osmo, loadkeys fr) puis demarrage du
Launcher NATIF (no-docker).http://127.0.0.1:8000ssh -p 2222 root@127.0.0.1 (mot de passe osmo)
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.Constats tires de la capture du 18/06/2026 (a titre indicatif) :
.part-00/01 (reassemblage cat + sha256sum -c obligatoires). D'ou le miroir MEGA.GRGSM RECORD est sature de
DL1C NOTICE … We missed N timers (scheduler_trx.c:427) et
We were N FN faster/slower than TRX, compensating (scheduler_trx.c:595/608).
Le scheduler osmo-bts-trx peine a tenir l'horloge TDMA temps reel dans une VM (pas de clock RT)
→ desyncs possibles, bursts perdus, SMS/LU intermittents.DLLAPD ERROR apparait (erreur L2 transitoire sur l'air emule).2048 Mo pour Osmocom + QEMU Calypso + grgsm + Wireshark + navigateur. Prevoir 3-4 Go.Chaque PLMN tourne dans un seul conteneur, les composants dialoguant en
127.0.0.1 (pas de course reseau Docker interne) :
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 |
+------------+ +------------+
| Brique | Role |
|---|---|
| Pile Osmocom | BTS/BSC/MSC/HLR/MGW/STP en userspace, sans radio dediee. |
| OsmocomBB | Firmware layer1.highram.elf compile depuis les sources (cross ARM). |
| QEMU Calypso | Fork bbaranoff/qemu emulant le SoC TI Calypso (ARM7TDMI + DSP C54x). |
| Docker | Images multi-stage ; entrypoint run.sh orchestre via tmux. |
| Asterisk + Linphone | Trunks SIP inter-operateurs et softphones locaux (Linphone A/B). |
| Scripts Python | bridge.py (relais bursts), sms-interop-relay.py, utilitaires. |
sip_port/sip_tcp_port/sip_tls_port
a -1 dans linphonerc pour ne pas ouvrir de port d'ecoute SIP.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.
layer1.highram.elf non modifie, identique au binaire flashe sur materiel reel.bridge.py relaie les bursts UDP (ports 5700-5702) entre osmo-bts-trx et le baseband simule ; QEMU fournit l'horloge maitre TDMA sur UDP 6700./tmp/osmocom_l2 permet a mobile de piloter la couche radio.Cela permet de valider que des binaires identiques fonctionnent a l'identique sur materiel et en simulation.
# 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
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.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 :
Point codes ITU 14 bits au format zone.network.node ; tout est
genere a l'execution en fonction du numero d'operateur N :
| Element | Point code | Exemple (N=2) |
|---|---|---|
| Inter-STP | 0.23.0 | 0.23.0 |
| MSC operateur N | N.23.1 | 2.23.1 |
| STP operateur N | N.23.2 | 2.23.2 |
| BSC operateur N | N.23.3 | 2.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.
| Reseau | Plage | Role |
|---|---|---|
| gsm-inter | 172.20.0.0/24 | Backbone Inter-STP & interconnexion. |
| gsm-net-opN | 172.20.N.0/24 | Reseau 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.
| Plage | Usage |
|---|---|
N0001-N9999 | Abonnes de l'operateur N. |
100, 200 | Softphones Linphone A / B. |
600 | Test d'echo. |
9XXXXX | Appels inter-operateurs depuis un softphone. |
Le premier chiffre (N) identifie l'operateur ; MSC et dialplan distinguent automatiquement local et inter-operateur.
MS -> BTS -> BSC -> MSC -> HLR (GSUP) -> proto-smsc-daemon -> MS
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.
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.
| Port telnet | Composant |
|---|---|
4239 | OsmoSTP |
4242 | OsmoBSC |
4243 | OsmoMGW |
4254 | OsmoMSC |
4258 | OsmoHLR |
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).
ACTIVE (pas DOWN).ping 172.20.0.10).pcspkr/snd_pcsp, bell-style none).sudo systemctl status docker.CALYPSO_FBSB_SYNTH=1.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.