commit 743b40b1062b0c62d5f1475acd7c121378baccaa
parent 3a107bb8b15e04c50f9b5a9f41ab64ec6dc24d48
Author: Florian Dold <dold@taler.net>
Date: Mon, 24 Aug 2026 01:52:03 +0200
libeufin-bank: load WebUI from separate package
Diffstat:
16 files changed, 153 insertions(+), 79 deletions(-)
diff --git a/.gitmodules b/.gitmodules
@@ -1,10 +1,6 @@
[submodule "build-system/taler-build-scripts"]
path = build-system/taler-build-scripts
url = ../build-common.git
-[submodule "contrib/wallet-core"]
- path = contrib/wallet-core
- url = ../taler-typescript-core.git
- branch = prebuilt
[submodule "doc/prebuilt"]
path = doc/prebuilt
url = ../taler-docs.git
diff --git a/Makefile b/Makefile
@@ -57,7 +57,7 @@ install-nobuild-files:
install -D -t $(bin_dir) contrib/libeufin-ebisync-dbconfig
install -D -t $(bin_dir) contrib/libeufin-tan-*.sh
install -d $(share_dir)/libeufin/spa
- cp contrib/wallet-core/bank/* $(share_dir)/libeufin/spa/
+ cp contrib/spa/* $(share_dir)/libeufin/spa/
install -d $(share_dir)/libeufin-ebisync/spa
cp libeufin-ebisync/src/spa/* $(share_dir)/libeufin-ebisync/spa/
@@ -137,4 +137,3 @@ bank-bench-db: install-nobuild-files
.PHONY: nexus-bench-db
nexus-bench-db: install-nobuild-files
./gradlew cleanTest :libeufin-nexus:test --tests Bench.benchDb -i --no-build-cache
-
diff --git a/README b/README
@@ -72,13 +72,14 @@ executable found under the "bin/" folder.
User interface
==============
-This repository does not ship any UI, rather it downloads
-one from the following project along the "make deb" target:
-https://git.taler.net/wallet-core.git/tree/packages/bank-ui
+The LibEuFin Bank WebUI is built separately from the
+`libeufin-bank-webui` package in taler-typescript-core:
+https://git.taler.net/taler-typescript-core.git/tree/packages/libeufin-bank-webui
-This way, the libeufin-bank Debian package provides one
-self-contained solution including Nginx, LibEuFin Bank,
-and the UI.
+The `libeufin-bank` Debian package depends on `libeufin-bank-webui` and
+serves its installed files from `$PREFIX/share/libeufin-bank-webui/`.
+When those files are unavailable, the bank serves a small fallback page
+with installation guidance.
-Note: the UI an independent Web app that could even be served
+Note: the UI is an independent Web app that could even be served
from a different host than the one running the backend.
diff --git a/bootstrap b/bootstrap
@@ -21,6 +21,5 @@ git config --local submodule.recurse true
git submodule sync
git submodule update --init
-./contrib/check-prebuilt
rm -f ./configure
cp build-system/taler-build-scripts/configure ./configure
diff --git a/contrib/bank-spa.lock b/contrib/bank-spa.lock
@@ -1 +0,0 @@
-1.5.14
-\ No newline at end of file
diff --git a/contrib/bank.conf b/contrib/bank.conf
@@ -76,8 +76,8 @@ BIND_TO = 0.0.0.0
# Which unix domain path should we bind to? Only used if SERVE is unix.
# UNIXPATH = libeufin-bank.sock
-# Path to spa files
-SPA = $DATADIR/spa/
+# Path to the separately installed WebUI files
+SPA = $PREFIX/share/libeufin-bank-webui/
# Exchange that is suggested to wallets when withdrawing.
# SUGGESTED_WITHDRAWAL_EXCHANGE = https://exchange.demo.taler.net/
diff --git a/contrib/check-prebuilt b/contrib/check-prebuilt
@@ -1,15 +0,0 @@
-#!/usr/bin/env python3
-
-import os
-import sys
-
-contrib = os.path.abspath(os.path.dirname(__file__))
-
-bank_ver_lock = open(contrib + "/" + "bank-spa.lock").read().strip()
-bank_ver_prebuilt = open(contrib + "/" + "wallet-core/bank/version.txt").read().strip()
-
-if bank_ver_lock != bank_ver_prebuilt:
- print("bank SPA version mismatch: bank-spa.lock")
- print("lockfile has version", bank_ver_lock)
- print("prebuilt has version", bank_ver_prebuilt)
- sys.exit(1)
diff --git a/contrib/ci/jobs/0-codespell/job.sh b/contrib/ci/jobs/0-codespell/job.sh
@@ -14,7 +14,6 @@ configure~
*/.git/*
*/.gradle/*
*/contrib/ci/*
-*/contrib/wallet-core/*
*/frontend/*
*/build/*
*/*.xsd
diff --git a/contrib/spa/index.html b/contrib/spa/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <h1>LibEuFin Bank Web UI not installed</h1>
+ <p>This is a fallback page shown when the LibEuFin Bank Web UI is not
+ available.</p>
+ <p>Please make sure the package is installed and the
+ <code>SPA</code> option in the <code>[libeufin-bank]</code> section of
+ the LibEuFin configuration is set correctly.</p>
+</body>
+</html>
diff --git a/contrib/wallet-core b/contrib/wallet-core
@@ -1 +0,0 @@
-Subproject commit 45c7768f9153846198dac99b6a9015477b9d7d2a
diff --git a/debian/control b/debian/control
@@ -20,6 +20,7 @@ Architecture: all
Depends:
default-jre-headless | java-runtime-headless (>= 17),
libeufin-common (= ${binary:Version}),
+ libeufin-bank-webui,
${misc:Depends}
Recommends:
nginx | apache2 | httpd,
diff --git a/debian/etc/libeufin/settings.json b/debian/etc/libeufin/settings.json
@@ -1,34 +0,0 @@
-// This file is an example of configuration of Bank SPA
-// Remove all the comments to make the file a valid
-// JSON file, otherwise no value here will make any
-// effect.
-// All the settings are optionals.
-{
- // Where libeufin backend is localted
- // default: window.origin without "webui/"
- "backendBaseURL": "http://bank.taler.test:1180/",
- // Shows a button "create random account" in the registration form
- // Useful for testing
- // default: false
- "allowRandomAccountCreation": false,
- // Create all random accounts with password "123"
- // Useful for testing
- // default: false
- "simplePasswordForRandomAccounts": false,
- // Bank name shown in the header
- // default: "Taler Bank"
- "bankName": "Taler TESTING Bank",
- // URL where the user is going to be redirected after
- // clicking in Taler Logo
- // default: home page
- "iconLinkURL": "#",
- // Mapping for every link shown in the top navitation bar
- // - key: link label, what the user will read
- // - value: link target, where the user is going to be redirected
- // default: empty list
- "topNavSites": {
- "Exchange": "http://exchange.taler.test:1180/",
- "Bank": "http://bank-ui.taler.test:1180/",
- "Merchant": "http://merchant.taler.test:1180/"
- }
-}
diff --git a/debian/libeufin-bank.install b/debian/libeufin-bank.install
@@ -1,15 +1,13 @@
debian/etc/libeufin/libeufin-bank.conf etc/libeufin/
debian/etc/nginx/sites-available/libeufin-bank etc/nginx/sites-available/
debian/etc/apache2/sites-available/libeufin-bank.conf etc/apache2/sites-available/
-debian/etc/libeufin/settings.json etc/libeufin/
-
libeufin-bank/build/install/libeufin-bank-shadow/bin/libeufin-bank usr/bin/
contrib/libeufin-bank-dbinit usr/bin/
contrib/libeufin-tan-*.sh usr/bin/
database-versioning/libeufin-bank*.sql usr/share/libeufin/sql/
-contrib/wallet-core/bank/* usr/share/libeufin/spa
+contrib/spa/* usr/share/libeufin/spa
contrib/bank.conf usr/share/libeufin/config.d/
@@ -19,4 +17,4 @@ libeufin-bank/build/install/libeufin-bank-shadow/lib/libeufin-bank-all.jar usr/l
doc/prebuilt/man/libeufin-bank.1 usr/share/man/man1
doc/prebuilt/man/libeufin-bank.conf.5 usr/share/man/man5
-debian/libeufin-bank.conf /usr/lib/sysusers.d/
-\ No newline at end of file
+debian/libeufin-bank.conf /usr/lib/sysusers.d/
diff --git a/libeufin-bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/libeufin-bank/src/main/kotlin/tech/libeufin/bank/Config.kt
@@ -48,6 +48,7 @@ data class BankConfig(
val fiatCurrency: String?,
val fiatCurrencySpec: CurrencySpecification?,
val spaPath: Path?,
+ val fallbackSpaPath: Path,
val tanChannels: Map<TanChannel, Pair<Path, Map<String, String>>>,
val payto: BankPaytoCtx,
val wireMethod: WireMethod,
@@ -179,6 +180,7 @@ private fun TalerConfig.loadBankConfig(): BankConfig = section("libeufin-bank").
maxAmount = amount("max_wire_transfer_amount", regionalCurrency).default(MAX),
suggestedWithdrawalExchange = string("suggested_withdrawal_exchange").orNull(),
spaPath = path("spa").orNull(),
+ fallbackSpaPath = this@loadBankConfig.section("paths").path("datadir").require().resolve("spa"),
baseUrl = baseUrl,
fiatCurrency = fiatCurrency,
fiatCurrencySpec = fiatCurrencySpec,
diff --git a/libeufin-bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/libeufin-bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -32,6 +32,7 @@ import tech.libeufin.common.api.OpenApiInfo
import tech.libeufin.common.api.talerApi
import tech.libeufin.common.VERSION
import com.github.ajalt.clikt.core.main
+import kotlin.io.path.isDirectory
val logger: Logger = LoggerFactory.getLogger("libeufin-bank")
@@ -65,15 +66,21 @@ fun Application.corebankWebApp(db: Database, cfg: BankConfig, serveSpec: Boolean
preparedTransferApi(db, cfg)
revenueApi(db, cfg)
observabilityApi(db, cfg)
- cfg.spaPath?.let {
- get("/") {
- call.respondRedirect("/webui/")
+ val spaPath = cfg.spaPath?.takeIf { it.isDirectory() } ?: run {
+ cfg.spaPath?.let {
+ logger.warn("SPA not found at '{}', using fallback", it)
}
- staticFiles("/webui/", it.toFile())
+ check(cfg.fallbackSpaPath.isDirectory()) {
+ "SPA fallback directory not found at '${cfg.fallbackSpaPath}'"
+ }
+ cfg.fallbackSpaPath
+ }
+ get("/") {
+ call.respondRedirect("/webui/")
}
+ staticFiles("/webui/", spaPath.toFile())
}
fun main(args: Array<String>) {
LibeufinBank().main(args)
}
-
diff --git a/libeufin-bank/src/test/kotlin/WebUiTest.kt b/libeufin-bank/src/test/kotlin/WebUiTest.kt
@@ -0,0 +1,114 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2026 Taler Systems S.A.
+
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+
+ * LibEuFin is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+import io.ktor.client.request.get
+import io.ktor.client.statement.bodyAsText
+import io.ktor.http.HttpStatusCode
+import io.ktor.server.testing.testApplication
+import org.junit.Test
+import sun.misc.Unsafe
+import tech.libeufin.bank.BankConfig
+import tech.libeufin.bank.bankConfig
+import tech.libeufin.bank.corebankWebApp
+import tech.libeufin.bank.db.Database
+import java.nio.file.Path
+import kotlin.io.path.createDirectories
+import kotlin.io.path.createTempDirectory
+import kotlin.io.path.createTempFile
+import kotlin.io.path.readText
+import kotlin.io.path.writeText
+import kotlin.test.assertContains
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+class WebUiTest {
+ private fun fakeDatabase(): Database {
+ val field = Unsafe::class.java.getDeclaredField("theUnsafe")
+ field.isAccessible = true
+ val unsafe = field.get(null) as Unsafe
+ return unsafe.allocateInstance(Database::class.java) as Database
+ }
+
+ private fun config(dataDir: Path, spaPath: Path?): BankConfig {
+ val file = createTempFile(suffix = ".conf")
+ file.writeText(buildString {
+ appendLine(Path.of("conf/test_no_conversion.conf").readText())
+ appendLine("[paths]")
+ appendLine("DATADIR = $dataDir")
+ if (spaPath != null) {
+ appendLine("[libeufin-bank]")
+ appendLine("SPA = $spaPath")
+ }
+ })
+ return bankConfig(file)
+ }
+
+ @Test
+ fun configuredSpa() {
+ val root = createTempDirectory()
+ val spa = root.resolve("webui").createDirectories()
+ spa.resolve("index.html").writeText("configured WebUI")
+ val cfg = config(root.resolve("data"), spa)
+
+ testApplication {
+ application { corebankWebApp(fakeDatabase(), cfg) }
+
+ val noRedirectClient = createClient { followRedirects = false }
+ val redirect = noRedirectClient.get("/")
+ assertEquals(HttpStatusCode.Found, redirect.status)
+ assertEquals("/webui/", redirect.headers["Location"])
+
+ val response = client.get("/webui/")
+ assertEquals(HttpStatusCode.OK, response.status)
+ assertEquals("configured WebUI", response.bodyAsText())
+ }
+ }
+
+ @Test
+ fun fallbackSpa() {
+ val root = createTempDirectory()
+ val fallback = root.resolve("spa").createDirectories()
+ fallback.resolve("index.html").writeText("fallback WebUI")
+ val configuredFile = root.resolve("not-a-directory")
+ configuredFile.writeText("not a WebUI")
+ val cfg = config(root, configuredFile)
+
+ testApplication {
+ application { corebankWebApp(fakeDatabase(), cfg) }
+
+ val response = client.get("/webui/")
+ assertEquals(HttpStatusCode.OK, response.status)
+ assertEquals("fallback WebUI", response.bodyAsText())
+ }
+ }
+
+ @Test
+ fun missingFallbackFails() {
+ val root = createTempDirectory()
+ val cfg = config(root.resolve("missing-data"), root.resolve("missing-webui"))
+
+ val error = assertFailsWith<IllegalStateException> {
+ testApplication {
+ application { corebankWebApp(fakeDatabase(), cfg) }
+ client.get("/")
+ }
+ }
+ assertContains(error.message.orEmpty(), "SPA fallback directory not found")
+ }
+}