commit 2d7a43ded79da78587e0e874de66e474d6bc72e7
parent 9f9d57832f92792bef406d14bb6089ae3641489f
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 1 Nov 2026 02:10:48 +0100
- document the installer, its package contract and the test harness
- stop the WSL bootstrap from blocking on hidden prompts
- install the distro and its user without any prompt
- install the WSL runtime from Microsoft's MSI when it is missing
- only report success when the install actually succeeded
Assisted by LLM
Diffstat:
7 files changed, 525 insertions(+), 69 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
@@ -0,0 +1,262 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## What this is
+
+A Windows **installer** for the GNU Taler merchant backend. There is no Taler source code
+here — the backend is installed from the `deb.taler.net` APT repository *inside WSL*. This
+repo is only the orchestration layer: an NSIS wizard that collects user choices, a batch
+script that drives `wsl.exe`, and three bash scripts that run inside the WSL distro.
+
+Twelve tracked files, no build system, no test suite. It is also the odd one out in the
+`/home/claude` workspace: nothing here links against `~/target`, and the build-order and
+`make install` rules in the parent `CLAUDE.md` do not apply.
+
+## Building
+
+```sh
+makensis taler-installer-taler.nsi # produces taler-installer.exe (gitignored)
+```
+
+`makensis` is **not installed in this workspace** and the result only runs on Windows with
+WSL available, so changes here cannot be compiled or exercised locally — they are reviewed
+by reading. Required at compile time in the same directory: `logo-header.ico`,
+`logo-header.bmp`, `logo-welcome.bmp`, and the four scripts listed in the `File` directives.
+
+## Control flow
+
+```
+taler-installer.exe (NSIS, RequestExecutionLevel admin)
+ wizard pages: Welcome → Directory → Distro → Domain → ReverseProxy → INSTFILES
+ Section "Install Taler-Merchant" installs the 4 scripts to $INSTDIR, then:
+ setup-wsl-debian.bat 1 "" <distro> → steps 1 only
+ setup-wsl-debian.bat ALL "" <distro> → steps 1..8
+ setup-wsl-debian.bat 8 <domain> <distro> <proxy_mode> → step 8 only
+ post-install pages: AskExternalDB → DBUrl → calls test-db.sh / set-db.sh in WSL
+ .onGUIEnd: restarts apache2, opens http(s)://<domain> in the browser
+```
+
+`setup-wsl-debian.bat` is a **step dispatcher**: `%1` is a step number (`1`-`8`) or `ALL`.
+It `goto`s the matching `:stepN` label and each step ends with
+`if not "%STEP%"=="ALL" exit /b`, so a numeric argument runs one step and `ALL` falls
+through all of them. Arguments are positional: `STEP DOMAIN DISTRO PROXY_MODE`.
+
+The steps: (1) enable `Microsoft-Windows-Subsystem-Linux` + `VirtualMachinePlatform`,
+`wsl --update`; (2) `wsl --install -d <distro>`; (3) create the `taleruser` account,
+grant it `NOPASSWD` sudo, make it the default in `/etc/wsl.conf`, terminate the distro to
+apply; (4) add it to `www-data`; (5) copy the three `.sh` files through `%TEMP%` into
+`/home/taleruser/taler-installer` via `type | wsl tee`, `dos2unix`, `chmod +x`;
+(6) verify format/permissions; (7) `wsl --shutdown`, then run
+`install-taler-from-apt.sh`; (8) install Apache and run `taler-merchant-rproxy-setup`.
+
+`install-taler-from-apt.sh` is likewise **multi-mode on `$1`**: `8 <domain>` does the
+HTTPS reverse proxy only, `http <domain>` the HTTP-only proxy only, and no recognised first
+argument means the full install (postgres, Taler APT repo keyring for the detected
+Debian/Ubuntu codename, `taler-merchant`, certbot, `taler-merchant-dbconfig`, enable the
+systemd units). Both proxy branches are currently dead from the batch path, because step 8
+calls `taler-merchant-rproxy-setup` itself.
+
+Fixed identities the scripts assume everywhere: WSL user `taleruser`, Taler DB role
+`taler-merchant-httpd`, scripts at `/home/taleruser/taler-installer/`.
+
+## The contract with the taler-merchant package
+
+Most of this installer's history of breakage came from fighting the Debian package
+instead of using it, so know what `taler-merchant` already provides:
+
+- **`/etc/taler-merchant/taler-merchant.conf` is an entry point, not a settings file.**
+ It carries `[paths]` and three include directives: `@inline-matching@ conf.d/*.conf`
+ (which is where `[merchant] SERVE = systemd` lives), `@inline@ overrides.conf`, and
+ `@inline-secret@ merchantdb-postgres ../secrets/merchant-db.secret.conf`.
+ **Put overrides in a `conf.d/` drop-in**, never in this file.
+ Overwriting it is *not* immediately fatal — measured, not assumed: the defaults under
+ `/usr/share/taler-merchant/config.d/` still supply `SERVE = tcp` and
+ `CONFIG = postgres:///taler-merchant`, and systemd socket activation overrides
+ `SERVE = tcp` anyway, so the backend still answers. What is lost is everything an
+ administrator puts under `/etc/taler-merchant`: every `conf.d/` drop-in, `overrides.conf`,
+ and the database secret are silently no longer included.
+- **`SERVE` must be `tcp`, `unix` or `systemd`** (`exchange/src/mhd/mhd_config.c:194`);
+ if it resolves nowhere at all, `taler-merchant-httpd` aborts with *"serve type (tcp,
+ unix or systemd) required"*. When `LISTEN_FDS` is set, systemd activation wins over
+ `SERVE` and only logs a warning. There is no `UNIX_DOMAIN_SOCKETS` option anywhere in
+ the Taler codebase.
+- **The backend is socket-activated.** `taler-merchant-httpd.socket` listens on
+ `/run/taler-merchant/httpd/merchant-http.sock` (`taler-merchant-httpd:www-data`, mode
+ 0660) and the service has `Requires=` it. Enable the *socket* (or the target), not the
+ service. There is no need for a TCP port such as 8888.
+- **The package ships the Apache vhost** at `/etc/apache2/sites-available/taler-merchant.conf`,
+ already proxying to that unix socket, with `%%your.domain%%` placeholders that
+ `taler-merchant-rproxy-setup` substitutes.
+- **The database URI** goes in `secrets/merchant-db.secret.conf` as
+ `[merchantdb-postgres] CONFIG=postgres://...`.
+
+`taler-merchant-rproxy-setup` (in the `merchant` checkout) accepts **only** `--domain`,
+`--nginx|--apache`, `--httponly|--httpsonly` and `-h`. Any other argument is a hard
+`exit 1`. It runs `certbot certonly --webroot -w /var/www/html`, so plain `certbot` is
+the only certbot package needed — **never** `python3-certbot-nginx`, which `Depends: nginx`
+and leaves nginx holding port 80 so the apache2 installed in step 8 cannot start.
+
+## Logs
+
+Scattered, and worth knowing before debugging a user report:
+
+| Path | Written by |
+| --- | --- |
+| `C:\Users\Public\taler-bat.log` | `setup-wsl-debian.bat` (truncated at each start) |
+| `C:\Users\Public\taler-sh.log` | `install-taler-from-apt.sh` (via `exec > >(tee -a)`) |
+| `C:\Users\Public\install-bat.log` | copied out of WSL at the end of step 7 |
+| `/home/taleruser/taler-installer/install-sh.log`, `test-db.log` | the bash scripts |
+| `/tmp/dbtest.log` | `test-db.sh` |
+
+`readme.txt` and `.onInstFailed` both point at `C:\Users\Public\install.log`, which nothing
+writes.
+
+## Test harness
+
+There is a working harness on `tripwire.taler.net` under `/srv/winstall-test`
+(not in this repo yet). It is the only way these scripts get exercised, since nothing
+here can run on the development workstation.
+
+```
+harness/run-debian-test.sh <fresh|twice|reinstall> <ssh-port> <repo-dir> <domain> [label]
+```
+
+It boots a throwaway Debian 13 cloud image under qemu (user-mode networking, qcow2
+overlay destroyed afterwards), creates `taleruser` the way the batch file would, runs
+`install-taler-from-apt.sh` plus `taler-merchant-rproxy-setup`, and asserts the result —
+ending with a real `GET /config` both over the backend's unix socket and through Apache.
+`twice` runs the installer a second time on a dirty system; `reinstall` purges
+`taler-merchant` in between. Point `<repo-dir>` at a checkout of `master` or of a fix
+branch to get a before/after.
+
+Two traps cost time when extending it: `ssh` inside the driver needs `-n` or it eats the
+script's stdin and hangs forever, and assertion patterns must use `grep -F` — `[paths]`
+as a regex is a character class that matches almost any line.
+
+## Verified end to end on Windows
+
+Three scenarios, each from a fresh overlay of a clean Windows 11 Enterprise
+Evaluation (build 26300) with the WSL features *disabled* and no WSL runtime --
+i.e. a real first install. All three pass 14/14, ending with
+`http://localhost/config` answered **from Windows**, which is what a merchant's
+browser hits:
+
+| scenario | what it does | result |
+| --- | --- | --- |
+| `fresh` | enable features, reboot, MSI bootstrap, distro + user, Taler, HTTP proxy | 14/0 |
+| `twice` | the above, then the whole installer again over existing state | 14/0 |
+| `reinstall` | the above, then `wsl --unregister` and install again | 14/0 |
+
+Run them with `harness/win-test.sh run <fresh|twice|reinstall>`; they use
+separate ports, overlays and firmware vars, so all three run in parallel.
+
+Two traps when editing the batch file, both of which silently aborted a run:
+`cmd` parses `%~` substitutions **even inside `::` comments**, and `start`,
+`timeout` and `pause` all refuse to run with redirected stdin.
+
+## What the Windows VM proved
+
+Measured on a clean Windows 11 Enterprise Evaluation (build 26300, 26H2) under
+qemu/KVM, not inferred:
+
+- **Step 1 is fine.** Both optional features go `Disabled` -> `Enabled`, the
+ reboot gate works, and the machine comes back.
+- **WSL2 itself runs** in that nested setup: `vmmemWSL` live, `wsl -l -v`
+ reporting `Debian / Running / VERSION 2`. WSL prints *"nested virtualization is
+ not supported on this machine"*, which concerns nesting *inside* WSL and is not
+ fatal.
+- **`cmd`'s `start` and `timeout` refuse redirected stdin**, printing
+ *"Input redirection is not supported, exiting the process immediately"*. nsExec
+ runs this batch file exactly that way, so `wsl --update` never executed and all
+ five `timeout` delays were silent no-ops. Use `ping -n N 127.0.0.1 >nul` for a
+ delay in this file, never `timeout`.
+- **`wsl --install -d <distro>` blocks on an invisible prompt** --
+ *"Please create a default UNIX user account"* -- observed hung for 54 minutes.
+ `--no-launch` is required; step 3 creates `taleruser` itself anyway.
+- **The in-box `wsl.exe` is only a stub.** Before the WSL runtime is present it
+ answers *"The Windows Subsystem for Linux is not installed"* to every form
+ except a bare `--install`, prints no `--help` at all, and cannot reliably
+ bootstrap itself on an image without the Store. Installing the official MSI
+ (`wsl.<ver>.x64.msi` from `microsoft/WSL` releases) with
+ `msiexec /i wsl.msi /quiet /norestart` works silently and returns 0 — that is
+ the deterministic way to get WSL onto a merchant machine.
+- **`wsl --install` cannot create the UNIX account.** Its complete option set is
+ `--enable-wsl1 --fixed-vhd --from-file --legacy --location --name
+ --no-distribution --no-launch --version --vhd-size --web-download`. The
+ account is only ever created by the distro's interactive first-run. The
+ supported non-interactive sequence, verified end to end in the VM, is:
+ `wsl --install -d <Distro> --no-launch --web-download`, then
+ `wsl -d <Distro> -u root -- useradd -m <user>`, then
+ `wsl --manage <Distro> --set-default-user <user>`.
+- **systemd is already PID 1 in the Debian WSL image.** It ships `/etc/wsl.conf`
+ containing `[boot] systemd=true`, `ps -p 1 -o comm=` returns `systemd` and
+ `systemctl is-system-running` returns `running`. The `systemctl` calls these
+ scripts make are therefore fine — but hand-appending `[user]` to that file
+ risks clobbering the `[boot]` stanza, which is why `--set-default-user` is
+ preferred.
+- **The installer always claims success.** The final
+ *"GNU Taler-Merchant installed successfully"* line prints unconditionally, two
+ seconds after the log records `return code 1` from the reverse-proxy step.
+
+## Known rough edges
+
+Fixed on the current review branch (see `git log`): the non-existent
+`--acme`/`--merchant-url` arguments, the `%errorlevel%` expansion bug that hid every
+reverse-proxy failure, the `taler-merchant.conf` overwrite, the `python3-certbot-nginx`
+port-80 conflict, enabling the service instead of the socket, `set-db.sh` writing a
+`DATABASE_URL` key that nothing reads, the mid-step `exit /b` that made `STEP=3` do only
+half of step 3, and `$DOMAIN_INPUT` being used both as an nsDialogs control handle and as
+the domain text (which broke the "localhost implies HTTP-only" guard).
+
+Still open, and worth knowing before debugging a user report:
+
+- **`rproxy-setup` requires the domain to resolve to a local IP.** It runs
+ `dig +short A "$DOMAIN"` and compares against `hostname -I`, exiting 1 on a mismatch.
+ Inside WSL the interface is NAT'd (172.x), so a real public domain never matches, and
+ `localhost` has no A record at all. The check is skipped entirely when `dig` is absent
+ — `bind9-dnsutils` is priority-standard but not a dependency of `taler-merchant`,
+ `certbot`, `apache2` or `postgresql`, so whether this fires depends on the WSL image.
+ Fixing it properly means relaxing the check to a warning in the `merchant` checkout
+ (its own comment suggests exactly that), which is a separate repo and review.
+- **`certbot certonly` is interactive** (email, TOS) and `rproxy-setup` passes no
+ `--non-interactive --agree-tos -m`, so HTTPS mode can block on a prompt that nobody
+ sees behind `nsExec`. Also a `merchant`-side change.
+- **No silent/unattended mode.** All four inputs come from `nsDialogs` pages, which `/S`
+ skips, leaving the variables empty; the ~8 `MessageBox` calls block regardless of `/S`
+ (NSIS does not suppress them). `FileFunc.nsh` is already included, so `GetOptions` for
+ `/DOMAIN=` etc. is close at hand.
+- **Step 1 demands a reboot and a full re-run**, persisting no state, so the wizard
+ re-asks everything. For automated runs, snapshot a VM with the WSL features already
+ enabled.
+- **`.onGUIEnd` shows a MessageBox and opens a browser on any exit**, including user
+ cancel and failure.
+- **Progress percentages are fake** — `Sleep`-driven `DetailPrint` lines, because the
+ long `nsExec` calls block the GUI.
+- **Start-menu shortcut** points at `$INSTDIR\taler-installer.exe`, which the section
+ never installs.
+- **`%DATE%`/`%TIME%` inside parenthesised blocks** all expand at parse time, so every
+ log line in one block shares a timestamp.
+- Timestamps aside, `MessageBox`/`DetailPrint` text and the log messages are a mix of
+ English and French.
+
+## Conventions
+
+- Shell scripts are committed **LF**; `.bat` and `.nsi` are **CRLF** (NSIS and `cmd.exe`
+ want it). Step 5 runs `dos2unix` in WSL as a belt-and-braces measure, so a stray CRLF in
+ a `.sh` survives — keep them LF anyway.
+- GPLv2.1+ header with `Author: Léa Oualli` on the `.bat`/`.nsi`/`.sh` files that have one.
+- Comments and some log messages are in **French**, user-facing strings are bilingual: every
+ new wizard string needs a `LangString` for both `${LANG_ENGLISH}` and `${LANG_FRENCH}`.
+ Avoid accented characters in `LangString` values — the existing French ones are written
+ unaccented ("Etape", "redemarrer") because of NSIS codepage handling.
+- `test-if.bat` is a scratch file for checking the `MODE`/`TYPE` branch logic, not part of
+ the installer. `run-admin.bat` just re-launches `setup-wsl-debian.bat` elevated via
+ PowerShell for manual testing outside the wizard.
+
+## Git
+
+Same workspace rule as the sibling checkouts: `origin` is a read-only mirror; work goes on
+`dev/phoenix-bot/<topic>` pushed to the `durian` remote, via the **`taler-push`** skill.
+This repo's history, unlike the other checkouts', is a handful of informal French commit
+messages — follow the skill's format for new commits rather than that precedent.
diff --git a/get-wsl-msi.ps1 b/get-wsl-msi.ps1
@@ -0,0 +1,48 @@
+# Downloads Microsoft's official Windows Subsystem for Linux installer.
+#
+# This file is part of GNU Taler.
+# Copyright (C) 2026 Taler Systems SA
+#
+# The in-box wsl.exe in System32 is only a stub: on a machine with no WSL
+# runtime it answers "The Windows Subsystem for Linux is not installed" to
+# --update, to a bare --install and to every flagged form, so it cannot be
+# relied on to bootstrap itself. The MSI installs silently with msiexec.
+#
+# Usage: get-wsl-msi.ps1 <output-path>
+param([Parameter(Mandatory=$true)][string]$OutFile)
+
+$ErrorActionPreference = 'Stop'
+$ProgressPreference = 'SilentlyContinue'
+
+try {
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+} catch { }
+
+try {
+ $release = Invoke-RestMethod -UseBasicParsing -TimeoutSec 120 `
+ -Uri 'https://api.github.com/repos/microsoft/WSL/releases/latest' `
+ -Headers @{ 'User-Agent' = 'taler-windows-installer' }
+
+ $asset = $null
+ foreach ($a in $release.assets) {
+ if ($a.name -like 'wsl.*.x64.msi') { $asset = $a; break }
+ }
+ if ($null -eq $asset) {
+ Write-Output "No x64 MSI in WSL release $($release.tag_name)"
+ exit 2
+ }
+
+ Write-Output "Downloading $($asset.name) ($([math]::Round($asset.size/1MB)) MB)"
+ Invoke-WebRequest -UseBasicParsing -TimeoutSec 1800 `
+ -Uri $asset.browser_download_url -OutFile $OutFile
+
+ if (-not (Test-Path $OutFile)) { exit 3 }
+ if ((Get-Item $OutFile).Length -lt 1MB) { exit 4 }
+
+ Write-Output "Saved to $OutFile"
+ exit 0
+}
+catch {
+ Write-Output "WSL MSI download failed: $($_.Exception.Message)"
+ exit 1
+}
diff --git a/install-taler-from-apt.sh b/install-taler-from-apt.sh
@@ -23,7 +23,7 @@ TALER_DB_USER="taler-merchant-httpd"
if [ "$1" = "8" ] && [ -n "$2" ]; then
DOMAIN="$2"
echo "Configuring HTTPS reverse proxy for domain: $DOMAIN"
- if ! sudo taler-merchant-rproxy-setup --acme --merchant-url="https://$DOMAIN:8888/"; then
+ if ! sudo taler-merchant-rproxy-setup --domain "$DOMAIN" --apache --httpsonly; then
echo "WARNING: Impossible d’obtenir un certificat HTTPS valide pour $DOMAIN (vérifie le DNS, les ports 80/443, et Internet)" | tee -a "/home/$WSL_USER/taler-installer/install-sh.log"
# On ne bloque pas toute l'install, on log juste le problème
exit 0
@@ -107,16 +107,34 @@ sudo apt-get install -y taler-merchant || {
exit 1
}
-sudo apt-get install -y certbot python3-certbot-nginx || {
+# Only the "certbot" package is required: taler-merchant-rproxy-setup runs
+# "certbot certonly --webroot", which is built into certbot core. Do NOT
+# add python3-certbot-nginx here -- it Depends on nginx, and an nginx
+# holding port 80 prevents the apache2 installed in step 8 from starting,
+# which in turn makes taler-merchant-rproxy-setup fail.
+sudo apt-get install -y certbot || {
echo "Certbot installation failed." >&2
exit 1
}
-echo "Creating /etc/taler/merchant.conf..."
-sudo mkdir -p /etc/taler
-echo "[merchant]
-PORT = 8888
-UNIX_DOMAIN_SOCKETS = NO" | sudo tee /etc/taler-merchant/taler-merchant.conf
+# Do NOT overwrite /etc/taler-merchant/taler-merchant.conf. The package
+# ships it as the entry point that pulls in everything else:
+# [paths] -> TALER_RUNTIME_DIR, TALER_CONFIG_HOME, ...
+# @inline-matching@ conf.d/* -> [merchant] SERVE = systemd
+# @inline-secret@ ... -> [merchantdb-postgres] CONFIG (database)
+# Replacing it removed the mandatory SERVE option, so taler-merchant-httpd
+# aborted with "serve type (tcp, unix or systemd) required", and removed the
+# database configuration. It also set UNIX_DOMAIN_SOCKETS, which is not an
+# option the merchant reads at all. The shipped defaults are what we want:
+# socket activation on /run/taler-merchant/httpd/merchant-http.sock, which
+# the Apache vhost shipped by the same package proxies to. Any override
+# belongs in a conf.d/ drop-in, never in this file.
+MAIN_CONF="/etc/taler-merchant/taler-merchant.conf"
+if [ ! -f "$MAIN_CONF" ]; then
+ echo "Expected $MAIN_CONF to be shipped by the taler-merchant package." >&2
+ exit 1
+fi
+echo "Using packaged configuration at $MAIN_CONF"
echo "Initializing database with taler-merchant-dbconfig..."
if ! sudo taler-merchant-dbconfig; then
@@ -124,14 +142,13 @@ if ! sudo taler-merchant-dbconfig; then
exit 1
fi
-echo "Enabling taler-merchant.target..."
+# taler-merchant-httpd.service has Requires=taler-merchant-httpd.socket and
+# is started through socket activation: enable the socket, not the service.
+echo "Enabling taler-merchant-httpd.socket and taler-merchant.target..."
+sudo systemctl enable taler-merchant-httpd.socket
sudo systemctl enable taler-merchant.target
sudo systemctl restart taler-merchant.target
-echo "Also enabling TCP service on port 8888..."
-sudo systemctl enable taler-merchant-httpd.service
-sudo systemctl restart taler-merchant-httpd.service
-
echo "Checking taler-merchant status..."
sudo systemctl status taler-merchant-httpd.service --no-pager || true
sudo systemctl status taler-merchant-httpd.socket --no-pager || true
diff --git a/set-db.sh b/set-db.sh
@@ -2,24 +2,37 @@
# set-db.sh
# Usage: ./set-db.sh <POSTGRES_URL>
POSTGRES_URL="$1"
-CONF_PATH="/etc/taler-merchant/taler-merchant.conf"
+
+# The database URI belongs in the secret snippet that the packaged
+# taler-merchant.conf pulls in with
+# @inline-secret@ merchantdb-postgres ../secrets/merchant-db.secret.conf
+# as [merchantdb-postgres] CONFIG. "DATABASE_URL" is not an option the
+# merchant reads, and appending it to taler-merchant.conf landed it under
+# whatever section happened to come last, so it never had any effect.
+SECRET_PATH="/etc/taler-merchant/secrets/merchant-db.secret.conf"
if [ -z "$POSTGRES_URL" ]; then
echo "Missing PostgreSQL URL argument."
exit 1
fi
-# Remplace ou ajoute la ligne DATABASE_URL
-if grep -q "^DATABASE_URL" "$CONF_PATH"; then
- sudo sed -i "s|^DATABASE_URL *=.*|DATABASE_URL = $POSTGRES_URL|" "$CONF_PATH"
-else
- echo "DATABASE_URL = $POSTGRES_URL" | sudo tee -a "$CONF_PATH"
+if [ ! -f "$SECRET_PATH" ]; then
+ echo "Expected $SECRET_PATH to be shipped by the taler-merchant package." >&2
+ exit 1
fi
-echo "Updated DATABASE_URL in $CONF_PATH"
+# Rewrite in place via tee so the file keeps its owner and permissions.
+printf '%s\n' "[merchantdb-postgres]" "CONFIG=$POSTGRES_URL" \
+ | sudo tee "$SECRET_PATH" > /dev/null
+
+echo "Updated [merchantdb-postgres] CONFIG in $SECRET_PATH"
+
+# The external database still needs the schema before the backend can use it.
+if ! sudo taler-merchant-dbconfig; then
+ echo "taler-merchant-dbconfig failed for the external database." >&2
+ exit 1
+fi
-# Redémarre les services pour prendre en compte la modif
sudo systemctl restart taler-merchant.target
-sudo systemctl restart taler-merchant-httpd.service
exit 0
diff --git a/setup-wsl-debian.bat b/setup-wsl-debian.bat
@@ -20,16 +20,22 @@ setlocal enabledelayedexpansion
REM -------------------------- LOGFILE GLOBALE ---------------------------
set LOGFILE=C:\Users\Public\taler-bat.log
-echo [%DATE% %TIME%] === [START] GNU Taler-Merchant Installer === > %LOGFILE%
+echo [%DATE% %TIME%] === [START] GNU Taler-Merchant Installer === >> %LOGFILE%
echo [%DATE% %TIME%] Arguments : STEP="%1" DOMAIN="%2" DISTRO="%3" PROXY_MODE="%4" >> %LOGFILE%
set WSL_USER=taleruser
-set STEP=%1
-set DOMAIN=%2
-set DISTRO=%3
+set INSTALL_FAILED=0
+:: The tilde modifier below strips surrounding quotes. NSIS invokes this file
+:: as: ALL "" Debian -- so a plain percent-2 yields the two quote characters,
+:: which is not empty, so the localhost fallback never fired and an empty
+:: --domain made taler-merchant-rproxy-setup swallow --httponly as the domain.
+:: (Do not write a tilde-percent sequence in a comment: cmd still parses it.)
+set STEP=%~1
+set DOMAIN=%~2
+set DISTRO=%~3
+set PROXY_MODE=%~4
set WSL=wsl.exe
if exist "%WINDIR%\Sysnative\wsl.exe" set WSL=%WINDIR%\Sysnative\wsl.exe
-set PROXY_MODE=%4
if "%STEP%"=="" (
set STEP=ALL
@@ -87,19 +93,62 @@ if defined NEED_REBOOT (
if not "%STEP%"=="ALL" exit /b
-echo [%DATE% %TIME%] Updating WSL to the latest version if needed... >> %LOGFILE%
-echo [INFO] If a black window appears, please press any key when prompted, then wait until the window closes automatically.
-echo [INFO] The installation will continue automatically after the update.
-timeout /t 3 /nobreak
-
-:: Use start /wait to ensure the script waits for the update to finish (including if user has to press a key)
-start /wait "" %WSL% --update
+echo [%DATE% %TIME%] Checking for the WSL runtime... >> %LOGFILE%
+
+:: The in-box wsl.exe in System32 is only a stub. On a machine that has no WSL
+:: runtime it answers "The Windows Subsystem for Linux is not installed" to
+:: --update, to a bare --install and to every flagged form, so it cannot be
+:: relied on to bootstrap itself. Install Microsoft's official MSI instead;
+:: msiexec /quiet does it without any prompt. This only runs when WSL really
+:: is missing, so machines that already have it pay nothing.
+%WSL% --version >nul 2>&1
+if not errorlevel 1 goto wsl_runtime_present
+
+echo [%DATE% %TIME%] WSL runtime absent; installing it from the official MSI. >> %LOGFILE%
+echo [INFO] Installing the Windows Subsystem for Linux, a one-time download of about 350 MB.
+set "WSLMSI=%TEMP%\wsl-runtime.msi"
+powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0get-wsl-msi.ps1" "%WSLMSI%" >> %LOGFILE% 2>&1
+if errorlevel 1 goto wsl_msi_download_failed
+
+msiexec /i "%WSLMSI%" /quiet /norestart
+set MSIRC=!errorlevel!
+echo [%DATE% %TIME%] msiexec returned !MSIRC! >> %LOGFILE%
+del "%WSLMSI%" >nul 2>&1
+
+:: 3010 is ERROR_SUCCESS_REBOOT_REQUIRED. Measured on Windows 11 26H2 the MSI
+:: returns 0 and WSL works at once, but honour 3010 if a build asks for it.
+if !MSIRC! equ 3010 goto wsl_msi_reboot
+if !MSIRC! neq 0 goto wsl_msi_failed
+echo [%DATE% %TIME%] WSL runtime installed from MSI. >> %LOGFILE%
+goto wsl_runtime_present
+
+:wsl_msi_download_failed
+echo [%DATE% %TIME%] [ERREUR] Could not download the WSL runtime. >> %LOGFILE%
+echo [ERREUR] Could not download the Windows Subsystem for Linux installer.
+exit /b 1
+
+:wsl_msi_reboot
+echo [%DATE% %TIME%] [INFO] WSL installed, restart required. >> %LOGFILE%
+echo [INFO] The Windows Subsystem for Linux was installed and Windows must restart.
+echo [INFO] Please restart your computer and run this installer again.
+exit /b 1
+
+:wsl_msi_failed
+echo [%DATE% %TIME%] [ERREUR] msiexec failed with !MSIRC! >> %LOGFILE%
+echo [ERREUR] Installing the Windows Subsystem for Linux failed, code !MSIRC!.
+exit /b 1
+
+:wsl_runtime_present
+%WSL% --update
if not "%STEP%"=="ALL" exit /b
:step2
echo [%DATE% %TIME%] [2/8] Installing %DISTRO%... >> %LOGFILE%
-%WSL% --install -d %DISTRO%
+:: --no-launch is essential: without it wsl.exe runs the distro's first-run
+:: setup, which blocks on "Please create a default UNIX user account" with no
+:: visible window. Step 3 creates %WSL_USER% directly instead.
+%WSL% --install -d %DISTRO% --no-launch --web-download
echo [%DATE% %TIME%] Initializing %DISTRO% first launch... >> %LOGFILE%
@@ -112,30 +161,42 @@ echo [%DATE% %TIME%] DISTRO="%DISTRO%", WSL_USER="%WSL_USER%" >> %LOGFILE%
%WSL% -d %DISTRO% -- bash -c "id %WSL_USER% >/dev/null 2>&1 || sudo useradd -m %WSL_USER%"
echo [%DATE% %TIME%] Waiting for user "%WSL_USER%" to be present... >> %LOGFILE%
+set WAIT_TRIES=0
:wait_for_taleruser
%WSL% -d %DISTRO% -- bash -c "id %WSL_USER%" >nul 2>&1
if %errorlevel% neq 0 (
- timeout /t 2 >nul
- goto wait_for_taleruser
+ ping -n 3 127.0.0.1 >nul 2>&1
+ set /a WAIT_TRIES+=1
+ if !WAIT_TRIES! lss 60 goto wait_for_taleruser
+ echo [%DATE% %TIME%] [ERREUR] User %WSL_USER% did not appear in %DISTRO% after ~2 minutes. >> %LOGFILE%
+ echo [ERREUR] Could not create the %WSL_USER% account inside %DISTRO%.
+ exit /b 1
)
echo [%DATE% %TIME%] User "%WSL_USER%" exists in Debian! >> %LOGFILE%
%WSL% -d %DISTRO% -- bash -c "echo '%WSL_USER% ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/%WSL_USER%"
%WSL% -d %DISTRO% -- bash -c "sudo chmod 0440 /etc/sudoers.d/%WSL_USER%"
echo [%DATE% %TIME%] Sudoers file updated. >> %LOGFILE%
-if not "%STEP%"=="ALL" exit /b
-REM Nettoie /etc/wsl.conf avant d’ajouter
-%WSL% -d %DISTRO% -- bash -c "sudo sed -i '/^\[user\]/d;/^default *=/d' /etc/wsl.conf"
-%WSL% -d %DISTRO% -- bash -c "echo -e '[user]\ndefault = %WSL_USER%' | sudo tee -a /etc/wsl.conf > /dev/null"
-echo [%DATE% %TIME%] wsl.conf set default user. >> %LOGFILE%
+REM "wsl --manage --set-default-user" is the supported way to do this. Editing
+REM [user] into /etc/wsl.conf by hand also risks clobbering the [boot]
+REM systemd=true stanza the Debian image ships, which every systemctl call here
+REM depends on. Fall back to the manual edit only on older wsl.exe builds.
+%WSL% --manage %DISTRO% --set-default-user %WSL_USER%
+if errorlevel 1 (
+ echo [%DATE% %TIME%] --set-default-user unavailable, editing wsl.conf >> %LOGFILE%
+ %WSL% -d %DISTRO% -u root -- bash -c "sed -i '/^\[user\]/d;/^default *=/d' /etc/wsl.conf 2>/dev/null; printf '[user]\ndefault = %WSL_USER%\n' >> /etc/wsl.conf"
+)
+echo [%DATE% %TIME%] default WSL user set to %WSL_USER%. >> %LOGFILE%
:: Pour appliquer immédiatement, termine la distro :
%WSL% --terminate %DISTRO%
echo [%DATE% %TIME%] Distro %DISTRO% terminated for user change. >> %LOGFILE%
+if not "%STEP%"=="ALL" exit /b
:step4
-echo [%DATE% %TIME%] [4/8] Adding user to www-data group... >> %LOGFILE%echo [DEBUG] DISTRO="%DISTRO%", WSL_USER="%WSL_USER%", DOMAIN="%DOMAIN%" >> %LOGFILE%
+echo [%DATE% %TIME%] [4/8] Adding user to www-data group... >> %LOGFILE%
+echo [DEBUG] DISTRO="%DISTRO%", WSL_USER="%WSL_USER%", DOMAIN="%DOMAIN%" >> %LOGFILE%
%WSL% -d %DISTRO% -- bash -c "sudo usermod -aG www-data %WSL_USER% || echo 'Warning: Could not add user to www-data (might already be a member)'"
echo [%DATE% %TIME%] User %WSL_USER% added to www-data (or already a member). >> %LOGFILE%
if not "%STEP%"=="ALL" exit /b
@@ -198,12 +259,15 @@ if not "%STEP%"=="ALL" exit /b
echo [%DATE% %TIME%] [7/8] Restarting WSL... >> %LOGFILE%
echo [DEBUG] DISTRO="%DISTRO%" >> %LOGFILE%
%WSL% --shutdown
-timeout /t 3
+ping -n 4 127.0.0.1 >nul 2>&1
echo [%DATE% %TIME%] Running final installation script... >> %LOGFILE%
%WSL% -d %DISTRO% -- bash -c "sudo -u %WSL_USER% bash /home/%WSL_USER%/taler-installer/install-taler-from-apt.sh | tee /home/%WSL_USER%/taler-installer/install-bat.log"
+set STEP7_STATUS=!errorlevel!
%WSL% -d %DISTRO% -- bash -c "sudo cp /home/%WSL_USER%/taler-installer/install-bat.log /mnt/c/Users/Public/install-bat.log"
-echo [%DATE% %TIME%] Final install script run, log copied to Windows. >> %LOGFILE%
+echo [%DATE% %TIME%] Final install script returned !STEP7_STATUS!, log copied to Windows. >> %LOGFILE%
+if not "!STEP7_STATUS!"=="0" set INSTALL_FAILED=1
+if not "!STEP7_STATUS!"=="0" echo [%DATE% %TIME%] [ERREUR] install-taler-from-apt.sh failed. >> %LOGFILE%
if not "%STEP%"=="ALL" exit /b
:step8
@@ -225,24 +289,34 @@ if "%PROXY_MODE%"=="" (
REM Installe Apache (dans tous les cas)
echo [%DATE% %TIME%] Installation Apache2... >> %LOGFILE%
%WSL% -d %DISTRO% -- bash -c "sudo apt-get update && sudo apt-get install -y apache2" >> %LOGFILE% 2>&1
+set APACHE_STATUS=!errorlevel!
+if not "!APACHE_STATUS!"=="0" set INSTALL_FAILED=1
+if not "!APACHE_STATUS!"=="0" echo [%DATE% %TIME%] [ERREUR] apache2 install failed (!APACHE_STATUS!). >> %LOGFILE%
if /I "%PROXY_MODE%"=="https" (
echo [%DATE% %TIME%] Mode HTTPS sélectionné pour %DOMAIN% >> %LOGFILE%
echo [%DATE% %TIME%] Setting up HTTPS with Let's Encrypt for %DOMAIN% ... >> %LOGFILE%
- %WSL% -d %DISTRO% -- bash -c "sudo taler-merchant-rproxy-setup --acme --merchant-url='https://%DOMAIN%:8888/' --apache" >> %LOGFILE% 2>&1
- set CERT_STATUS=%errorlevel%
- echo [%DATE% %TIME%] HTTPS return code : %CERT_STATUS% >> %LOGFILE%
- if not %CERT_STATUS%==0 (
+ %WSL% -d %DISTRO% -- bash -c "sudo taler-merchant-rproxy-setup --domain !DOMAIN! --apache --httpsonly" >> %LOGFILE% 2>&1
+ set CERT_STATUS=!errorlevel!
+ echo [%DATE% %TIME%] HTTPS return code : !CERT_STATUS! >> %LOGFILE%
+ if not "!CERT_STATUS!"=="0" set INSTALL_FAILED=1
+ if not "!CERT_STATUS!"=="0" (
echo [%DATE% %TIME%] [ERREUR] Certificat HTTPS échec pour %DOMAIN% >> %LOGFILE%
echo [ERREUR] Impossible d’obtenir un certificat HTTPS valide pour %DOMAIN%.
echo [ERREUR] Veuillez vérifier que le domaine pointe bien sur cette machine et soit accessible publiquement.
- timeout /t 5 /nobreak
+ ping -n 6 127.0.0.1 >nul 2>&1
)
) else (
echo [%DATE% %TIME%] Mode HTTP sélectionné pour %DOMAIN% >> %LOGFILE%
echo [%DATE% %TIME%] Setting up HTTP only for %DOMAIN% ... >> %LOGFILE%
- %WSL% -d %DISTRO% -- bash -c "sudo taler-merchant-rproxy-setup --domain %DOMAIN% --httponly --apache" >> %LOGFILE% 2>&1
- echo [%DATE% %TIME%] HTTP only proxy finished for %DOMAIN% >> %LOGFILE%
+ %WSL% -d %DISTRO% -- bash -c "sudo taler-merchant-rproxy-setup --domain !DOMAIN! --httponly --apache" >> %LOGFILE% 2>&1
+ set PROXY_STATUS=!errorlevel!
+ echo [%DATE% %TIME%] HTTP only proxy finished for !DOMAIN! with return code !PROXY_STATUS! >> %LOGFILE%
+ if not "!PROXY_STATUS!"=="0" set INSTALL_FAILED=1
+ if not "!PROXY_STATUS!"=="0" (
+ echo [%DATE% %TIME%] [ERREUR] Echec configuration du reverse proxy HTTP pour !DOMAIN! >> %LOGFILE%
+ echo [ERREUR] Impossible de configurer le reverse proxy HTTP pour !DOMAIN!.
+ )
)
echo [%DATE% %TIME%] --- Fin configuration reverse proxy --- >> %LOGFILE%
@@ -258,8 +332,22 @@ REM Start Apache service
REM Scheduled task (on next boot): Apache will start automatically
powershell -Command "Register-ScheduledTask -Action (New-ScheduledTaskAction -Execute '%WINDIR%\System32\wsl.exe' -Argument '-d %DISTRO% -- bash -c \"sudo service apache2 start && while true; do sleep 3600; done\"') -Trigger (New-ScheduledTaskTrigger -AtLogOn) -TaskName 'StartTalerApacheWSL' -User '$env:USERNAME' -RunLevel Highest -Force"
+echo [%DATE% %TIME%] status: step7=[!STEP7_STATUS!] apache=[!APACHE_STATUS!] proxy=[!PROXY_STATUS!] cert=[!CERT_STATUS!] failed=[!INSTALL_FAILED!] >> %LOGFILE%
+
+:: Only claim success when nothing above failed. This used to print
+:: unconditionally -- two seconds after logging "return code 1" -- so a wizard
+:: that had installed nothing still told the operator it had worked.
+if !INSTALL_FAILED!==0 goto install_ok
+
+echo [%DATE% %TIME%] [ERREUR] GNU Taler-Merchant installation FAILED. >> %LOGFILE%
+echo [%DATE% %TIME%] === [END] GNU Taler-Merchant Installer (failed) === >> %LOGFILE%
+echo.
+echo [ERREUR] The installation did not complete. See %LOGFILE%
+ping -n 6 127.0.0.1 >nul 2>&1
+exit /b 1
+
+:install_ok
echo [%DATE% %TIME%] GNU Taler-Merchant installed successfully! >> %LOGFILE%
echo [%DATE% %TIME%] === [END] GNU Taler-Merchant Installer === >> %LOGFILE%
-
-timeout /t 5 /nobreak >nul
-exit /b
+ping -n 6 127.0.0.1 >nul 2>&1
+exit /b 0
diff --git a/taler-installer-taler.nsi b/taler-installer-taler.nsi
@@ -45,6 +45,7 @@ Page custom DBUrlPage DBUrlPageLeave
Var /GLOBAL HWND_PROGRESS_LABEL
Var /GLOBAL DOMAIN_INPUT
+Var /GLOBAL DOMAIN_VALUE
Var /GLOBAL WSL_DISTRO
Var /GLOBAL RADIO_DEBIAN
Var /GLOBAL RADIO_UBUNTU
@@ -56,6 +57,7 @@ Var /GLOBAL DB_URL
Var /GLOBAL DBCONFIG_INPUT
Var /GLOBAL ASK_DB_YESNO
Var /GLOBAL USE_EXTERNAL_DB
+Var /GLOBAL INSTALL_OK
LangString MSG_WELCOME_TITLE ${LANG_ENGLISH} "GNU Taler-Merchant backend for Windows"
LangString MSG_WELCOME_TITLE ${LANG_FRENCH} "Backend GNU Taler-Merchant pour Windows"
@@ -176,7 +178,7 @@ Function DomainPageLeave
${EndIf}
${If} $0 == "localhost"
MessageBox MB_OK $(MSG_LOCALHOST_WARN)
- StrCpy $DOMAIN_INPUT $0
+ StrCpy $DOMAIN_VALUE $0
Return
${EndIf}
StrCpy $1 0
@@ -184,7 +186,7 @@ Function DomainPageLeave
nsExec::ExecToStack 'ping -n 1 -w 1000 $0'
Pop $2
${If} $2 == 0
- StrCpy $DOMAIN_INPUT $0
+ StrCpy $DOMAIN_VALUE $0
Return
${Else}
Push $0
@@ -195,7 +197,7 @@ Function DomainPageLeave
Goto loop_ping
${EndIf}
use_localhost:
- StrCpy $DOMAIN_INPUT "localhost"
+ StrCpy $DOMAIN_VALUE "localhost"
MessageBox MB_OK $(MSG_LOCALHOST_WARN)
${EndIf}
FunctionEnd
@@ -217,7 +219,7 @@ Function ReverseProxyPage
Pop $RADIO_HTTPS
; Récupère la valeur du domaine
- ${NSD_GetText} $DOMAIN_INPUT $1
+ StrCpy $1 $DOMAIN_VALUE
${If} $1 == "localhost"
; HTTPS grisé, HTTP sélectionné
@@ -246,9 +248,11 @@ FunctionEnd
; -------- SECTION INSTALL PRINCIPALE --------
Section "Install Taler-Merchant"
+ StrCpy $INSTALL_OK "1"
SetOutPath "$INSTDIR"
SetDetailsView show
File "setup-wsl-debian.bat"
+ File "get-wsl-msi.ps1"
File "install-taler-from-apt.sh"
File "test-db.sh"
File "set-db.sh"
@@ -316,10 +320,20 @@ Section "Install Taler-Merchant"
DetailPrint "[7/8] Restarting WSL and launching installation..."
SendMessage $HWND_PROGRESS_LABEL ${WM_SETTEXT} 0 "STR:Progress: 70%"
nsExec::ExecToLog '"$INSTDIR\setup-wsl-debian.bat" ALL "" $WSL_DISTRO'
+ Pop $0
+ ${If} $0 != 0
+ DetailPrint "ERROR: the installation step failed with code $0."
+ StrCpy $INSTALL_OK "0"
+ ${EndIf}
- DetailPrint "[8/8] Configuring reverse proxy for $DOMAIN_INPUT in $REVERSE_PROXY_MODE mode..."
+ DetailPrint "[8/8] Configuring reverse proxy for $DOMAIN_VALUE in $REVERSE_PROXY_MODE mode..."
SendMessage $HWND_PROGRESS_LABEL ${WM_SETTEXT} 0 "STR:Progress: 80%"
- nsExec::ExecToLog '"$INSTDIR\setup-wsl-debian.bat" 8 $DOMAIN_INPUT $WSL_DISTRO $REVERSE_PROXY_MODE'
+ nsExec::ExecToLog '"$INSTDIR\setup-wsl-debian.bat" 8 $DOMAIN_VALUE $WSL_DISTRO $REVERSE_PROXY_MODE'
+ Pop $0
+ ${If} $0 != 0
+ DetailPrint "ERROR: configuring the reverse proxy failed with code $0."
+ StrCpy $INSTALL_OK "0"
+ ${EndIf}
DetailPrint "Testing access to merchant reverse proxy..."
nsExec::ExecToLog 'wsl -d $WSL_DISTRO -- bash -c "curl -v http://localhost"'
@@ -327,14 +341,20 @@ Section "Install Taler-Merchant"
Sleep 2000
DetailPrint "Checking passed domain in $WSL_DISTRO..."
- nsExec::ExecToLog 'wsl -d $WSL_DISTRO -- bash -c "echo Received domain: '\''$DOMAIN_INPUT'\''"'
+ nsExec::ExecToLog 'wsl -d $WSL_DISTRO -- bash -c "echo Received domain: '\''$DOMAIN_VALUE'\''"'
SendMessage $HWND_PROGRESS_LABEL ${WM_SETTEXT} 0 "STR:Progress: 90%"
Sleep 2000
SendMessage $HWND_PROGRESS_LABEL ${WM_SETTEXT} 0 "STR:Progress: 99%"
Sleep 1000
- DetailPrint "Installation complete."
- SendMessage $HWND_PROGRESS_LABEL ${WM_SETTEXT} 0 "STR:Progress: 100%"
+ ${If} $INSTALL_OK == "1"
+ DetailPrint "Installation complete."
+ SendMessage $HWND_PROGRESS_LABEL ${WM_SETTEXT} 0 "STR:Progress: 100%"
+ ${Else}
+ DetailPrint "Installation FAILED - see C:\Users\Public\taler-bat.log"
+ SendMessage $HWND_PROGRESS_LABEL ${WM_SETTEXT} 0 "STR:Installation failed"
+ MessageBox MB_OK|MB_ICONSTOP $(MSG_INSTALL_FAILED)
+ ${EndIf}
CreateShortCut "$SMPROGRAMS\Taler-Merchant.lnk" "$INSTDIR\taler-installer.exe"
SectionEnd
@@ -414,16 +434,23 @@ FunctionEnd
; ---- PAGE FINISH : open browser ----
Function .onGUIEnd
-MessageBox MB_OK|MB_ICONINFORMATION "Installation is complete! Your browser will now open your domain."
+ ; This runs on every exit from the GUI, including a cancel and a failed run,
+ ; so announcing success here unconditionally told operators the install had
+ ; worked when it had not. Only celebrate when the section actually succeeded.
+ ${If} $INSTALL_OK != "1"
+ Return
+ ${EndIf}
+
+ MessageBox MB_OK|MB_ICONINFORMATION "Installation is complete! Your browser will now open your domain."
-; Force WSL à démarrer Apache AVANT le navigateur
-nsExec::ExecToLog 'wsl -d $WSL_DISTRO -- bash -c "sudo service apache2 restart"'
-Sleep 2000
+ ; Force WSL à démarrer Apache AVANT le navigateur
+ nsExec::ExecToLog 'wsl -d $WSL_DISTRO -- bash -c "sudo service apache2 restart"'
+ Sleep 2000
${If} $REVERSE_PROXY_MODE == "https"
- ExecShell "open" "https://$DOMAIN_INPUT"
+ ExecShell "open" "https://$DOMAIN_VALUE"
${Else}
- ExecShell "open" "http://$DOMAIN_INPUT"
+ ExecShell "open" "http://$DOMAIN_VALUE"
${EndIf}
FunctionEnd
diff --git a/test-db.sh b/test-db.sh
@@ -1,6 +1,7 @@
#!/bin/bash
# test-db.sh
# Usage: ./test-db.sh <POSTGRES_URL>
+mkdir -p /home/taleruser/taler-installer 2>/dev/null
echo "=== [DEBUG] test-db.sh called ===" | tee -a /home/taleruser/taler-installer/test-db.log
echo "Received URL: $1" | tee -a /home/taleruser/taler-installer/test-db.log
POSTGRES_URL="$1"