commit 865fc61f06157597ce463e22b9b242574214da3d
parent ebb0b8205b01574ca66db7198c8a9a11f22c7b97
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date: Sat, 14 Feb 2026 18:38:48 +0100
Add dbinit
Diffstat:
4 files changed, 207 insertions(+), 80 deletions(-)
diff --git a/Makefile.in b/Makefile.in
@@ -11,6 +11,7 @@ server:
tools:
${GO} build -o taler-directory-cli -ldflags "-X main.version=${VERSION} -X main.taldirdatahome=${TALER_DIRECTORY_HOME} -X main.taldirconfdir=${TALER_DIRECTORY_CONFDIR}" ./cmd/taldir-cli
${GO} build -o taler-directory-config -ldflags "-X main.version=${VERSION} -X main.taldirdatahome=${TALER_DIRECTORY_HOME} -X main.taldirconfdir=${TALER_DIRECTORY_CONFDIR}" ./cmd/taldir-config
+ ${GO} build -o taler-directory-dbinit -ldflags "-X main.version=${VERSION} -X main.taldirdatahome=${TALER_DIRECTORY_HOME} -X main.taldirconfdir=${TALER_DIRECTORY_CONFDIR}" ./cmd/taldir-dbinit
install: server tools
@@ -19,8 +20,10 @@ install: server tools
install ./taler-directory ${DESTDIR}${bindir}
install ./taler-directory-cli ${DESTDIR}${bindir}
install ./taler-directory-config ${DESTDIR}${bindir}
+ install ./taler-directory-dbinit ${DESTDIR}${bindir}
cp -r ./web ${DESTDIR}${TALER_DIRECTORY_HOME}/
cp -r ./static ${DESTDIR}${TALER_DIRECTORY_HOME}/
+ cp -r ./sql ${DESTDIR}${TALER_DIRECTORY_HOME}/
chmod +x scripts/validators/*
cp scripts/validators/* ${DESTDIR}${bindir}
-mkdir -p ${DESTDIR}${mandir}/man1
@@ -35,6 +38,7 @@ format:
${GO} fmt ./cmd/taldir-server/*.go
${GO} fmt ./cmd/taldir-cli/*.go
${GO} fmt ./cmd/taldir-config/*.go
+ ${GO} fmt ./cmd/taldir-dbinit/*.go
${GO} fmt ./pkg/taldir/*.go
check:
diff --git a/cmd/taldir-dbinit/main.go b/cmd/taldir-dbinit/main.go
@@ -0,0 +1,122 @@
+// This file is part of taldir, the Taler Directory implementation.
+// Copyright (C) 2025 Martin Schanzenbach
+//
+// Taldir 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 of the License,
+// or (at your option) any later version.
+//
+// Taldir 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 this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package main
+
+import (
+ "database/sql"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "path"
+
+ "rsc.io/getopt"
+ "taler.net/taldir/pkg/taldir"
+
+ "gopkg.in/ini.v1"
+)
+
+var (
+ taldirdatahome string
+ taldirconfdir string
+)
+
+func printHelp() {
+ fmt.Print("taler-directory-dbinit\n\n")
+ getopt.PrintDefaults()
+ fmt.Print("\nReport bugs to gnunet-developers@gnu.org.\n",
+ "Home page: https://taler.net\n",
+ "General help using GNU software: http://www.gnu.org/gethelp/\n")
+}
+
+func printKey(key *ini.Key, onlyValue bool) {
+ if onlyValue {
+ fmt.Printf("%s\n", key.String())
+ return
+ }
+ fmt.Printf("%s = %s\n", key.Name(), key.String())
+}
+
+func printCfgOptions(sec *ini.Section, option *string, onlyValue *bool) {
+ if len(*option) == 0 {
+ for _, key := range sec.Keys() {
+ printKey(key, *onlyValue)
+ }
+ return
+ }
+ if !sec.HasKey(*option) {
+ fmt.Printf("Section `%s' does not have option `%s'!\n", sec.Name(), *option)
+ os.Exit(1)
+ }
+ key := sec.Key(*option)
+ printKey(key, *onlyValue)
+}
+
+func printCfgSections(f *ini.File) {
+ for _, sec := range f.Sections() {
+ fmt.Println(sec.Name())
+ }
+}
+
+func main() {
+ var cfg *ini.File
+ var err error
+ var cfgFlag = flag.String("c", "", "Configuration file to use")
+ getopt.Alias("c", "config")
+ var helpFlag = flag.Bool("h", false, "Print help")
+ getopt.Alias("h", "help")
+
+ getopt.Parse()
+ if *helpFlag {
+ printHelp()
+ return
+ }
+ cfgfile := path.Join(taldirconfdir, "taldir.conf")
+ if len(*cfgFlag) != 0 {
+ cfg, err = ini.Load(*cfgFlag)
+ if err != nil {
+ fmt.Printf("Failed to read config: %v\n", err)
+ os.Exit(1)
+ }
+ } else {
+ // FIXME also try in datahome
+ cfg, err = ini.LooseLoad(cfgfile)
+ if err != nil {
+ fmt.Printf("Failed to read config: %v\n", err)
+ os.Exit(1)
+ }
+ }
+ dbName := cfg.Section("taldir-pq").Key("db_name").MustString("taldir")
+ psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
+ cfg.Section("taldir-pq").Key("host").MustString("localhost"),
+ cfg.Section("taldir-pq").Key("port").MustInt64(5432),
+ cfg.Section("taldir-pq").Key("user").MustString("taldir"),
+ cfg.Section("taldir-pq").Key("password").MustString("secret"),
+ dbName)
+
+ db, err := sql.Open("postgres", psqlconn)
+ if err != nil {
+ log.Panic(err)
+ }
+ defer db.Close()
+ err = taldir.DBInit(db, taldirdatahome, dbName)
+ if err != nil {
+ log.Fatalf("%v\n", err)
+ }
+}
diff --git a/cmd/taldir-server/main_test.go b/cmd/taldir-server/main_test.go
@@ -27,7 +27,6 @@ import (
"net/http"
"net/http/httptest"
"os"
- "os/exec"
"strings"
"testing"
@@ -98,84 +97,6 @@ const merchantConfigResponse = `{
"version": "18:0:15"
}`
-func CheckVersioning(db *sql.DB) (bool, error) {
- rows, err := db.Query(`SELECT schema_name FROM information_schema.schemata WHERE schema_name='_v';`)
- if err != nil {
- return false, err
- }
- defer rows.Close()
- if rows.Next() {
- fmt.Println("Versioning applied")
- return true, nil
- }
- fmt.Printf("Versioning not applied: %v", rows.Err())
- return false, nil
-}
-
-func CheckPatch(db *sql.DB, patchName string) (bool, error) {
- rows, err := db.Query(`SELECT applied_by FROM _v.patches WHERE patch_name=$1 LIMIT 1;`, patchName)
- if err != nil {
- return false, err
- }
- defer rows.Close()
- if rows.Next() {
- return true, nil
- }
- return false, nil
-}
-
-func RunSQL(db *sql.DB, patchName string) error {
- path, err := exec.LookPath("psql")
- if err != nil {
- return err
- }
- out, err := exec.Command(path, "taler-directory", "-f", patchName, "-q", "--set", "ON_ERROR_STOP=1").Output()
- // FIXME logger
- fmt.Printf("Executing `%s %s %s %s %s %s %s`\n", path, "taler-directory", "-f", patchName, "-q", "--set", "ON_ERROR_STOP=1")
- if err != nil {
- fmt.Printf("%s, %v\n", out, err)
- return err
- }
- return nil
-
-}
-
-func DBInit(db *sql.DB) error {
- applied, err := CheckVersioning(db)
- loadSuffix := "../../sql/"
- if err != nil {
- fmt.Printf("%v\n", err)
- }
- if !applied {
- err := RunSQL(db, fmt.Sprintf("%s%s", loadSuffix, "versioning.sql"))
- if err != nil {
- return err
- }
- }
- for i := range 10000 {
- patchName := fmt.Sprintf("taler-directory-%04d", i+1)
- applied, err := CheckPatch(db, patchName)
- if err != nil {
- return err
- }
- if applied {
- fmt.Printf("Patch %s already applied\n", patchName)
- continue
- }
- patchFile := fmt.Sprintf("%s%s.sql", loadSuffix, patchName)
- if _, err := os.Stat(patchFile); err != nil {
- fmt.Printf("Patch %s not found, up-to-date.\n", patchFile)
- break
- }
- fmt.Printf("Applying patch %s\n", patchName)
- err = RunSQL(db, patchFile)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
func TestMain(m *testing.M) {
cfg, err := ini.LooseLoad("testdata/taldir-test.conf")
if err != nil {
@@ -191,7 +112,7 @@ func TestMain(m *testing.M) {
log.Panic(err)
}
defer db.Close()
- err = DBInit(db)
+ err = taldir.DBInit(db, "../..", "taler-directory")
if err != nil {
log.Fatalf("Failed to apply versioning or patches: %v", err)
}
diff --git a/pkg/taldir/db.go b/pkg/taldir/db.go
@@ -23,6 +23,9 @@ import (
"database/sql"
"errors"
"fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
"time"
)
@@ -494,3 +497,80 @@ func InsertEntryIntoDatabase(db *sql.DB, e *Entry) error {
defer rows.Close()
return nil
}
+
+func CheckVersioning(db *sql.DB) (bool, error) {
+ rows, err := db.Query(`SELECT schema_name FROM information_schema.schemata WHERE schema_name='_v';`)
+ if err != nil {
+ return false, err
+ }
+ defer rows.Close()
+ if rows.Next() {
+ fmt.Println("Versioning applied")
+ return true, nil
+ }
+ return false, nil
+}
+
+func CheckPatch(db *sql.DB, patchName string) (bool, error) {
+ rows, err := db.Query(`SELECT applied_by FROM _v.patches WHERE patch_name=$1 LIMIT 1;`, patchName)
+ if err != nil {
+ return false, err
+ }
+ defer rows.Close()
+ if rows.Next() {
+ return true, nil
+ }
+ return false, nil
+}
+
+func RunSQL(db *sql.DB, patchName string, dbName string) error {
+ path, err := exec.LookPath("psql")
+ if err != nil {
+ return err
+ }
+ _, err = exec.Command(path, dbName, "-f", patchName, "-q", "--set", "ON_ERROR_STOP=1").Output()
+ fmt.Printf("Running: %s %s %s %s %s %s %s\n", path, dbName, "-f", patchName, "-q", "--set", "ON_ERROR_STOP=1")
+ if err != nil {
+ return err
+ }
+ return nil
+
+}
+
+func DBInit(db *sql.DB, datahome string, dbName string) error {
+ applied, err := CheckVersioning(db)
+ loadSuffix := filepath.Join(datahome, "sql")
+ if err != nil {
+ fmt.Printf("%v\n", err)
+ }
+ if !applied {
+ err := RunSQL(db, filepath.Join(loadSuffix, "versioning.sql"), dbName)
+ if err != nil {
+ return err
+ }
+ }
+ for i := range 10000 {
+ patchName := fmt.Sprintf("taler-directory-%04d", i+1)
+ applied, err := CheckPatch(db, patchName)
+ if err != nil {
+ return err
+ }
+ if applied {
+ fmt.Printf("Patch %s already applied\n", patchName)
+ continue
+ }
+ patchFile := fmt.Sprintf("%s.sql", filepath.Join(loadSuffix, patchName))
+ if _, err := os.Stat(patchFile); err != nil {
+ fmt.Printf("Patch %s not found, up-to-date.\n", patchFile)
+ break
+ }
+ fmt.Printf("Applying patch %s\n", patchName)
+ err = RunSQL(db, patchFile, dbName)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+