
Should be tested on Windows, as that's where this code will be used most often. As of now, untested.
136 lines
4.2 KiB
Go
136 lines
4.2 KiB
Go
package persistence
|
|
|
|
/* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* Original Code Copyright (C) 2022 Blender Foundation.
|
|
*
|
|
* This file is part of Flamenco.
|
|
*
|
|
* Flamenco is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, either version 3 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* Flamenco 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* ***** END GPL LICENSE BLOCK ***** */
|
|
|
|
import (
|
|
"bufio"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"golang.org/x/term"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var errInputTooLong = errors.New("input is too long")
|
|
|
|
const adminDSN = "host=localhost user=postgres password=%s dbname=%s TimeZone=Europe/Amsterdam"
|
|
|
|
// InitialSetup uses the `postgres` admin user to set up the database.
|
|
// TODO: distinguish between production and development setups.
|
|
func InitialSetup() error {
|
|
// Get the password of the 'postgres' user.
|
|
adminPass, err := readPassword()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to read password: %w", err)
|
|
}
|
|
|
|
// Connect to the 'postgres' database so we can create other databases.
|
|
db, err := connectDBAsAdmin(adminPass, "postgres")
|
|
if err != nil {
|
|
return fmt.Errorf("unable to connect to the database: %w", err)
|
|
}
|
|
|
|
// TODO: get username / password / database name from some config file, user input, CLI args, whatevah.
|
|
// Has to be used by the regular Flamenco Manager runs as well, though.
|
|
username := "flamenco"
|
|
userPass := "flamenco"
|
|
|
|
tx := db.Exec("CREATE USER @user PASSWORD @pass NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN",
|
|
sql.Named("user", username),
|
|
sql.Named("pass", userPass))
|
|
if tx.Error != nil {
|
|
return fmt.Errorf("unable to create database user '%s': %w", username, tx.Error)
|
|
}
|
|
|
|
// Create the databases.
|
|
tx = db.Debug().Exec("CREATE DATABASE flamenco OWNER ? ENCODING 'utf8'", username)
|
|
if tx.Error != nil {
|
|
return fmt.Errorf("unable to create database 'flamenco': %w", tx.Error)
|
|
}
|
|
tx = db.Exec("CREATE DATABASE flamenco-test OWNER ? ENCODING 'utf8'", username)
|
|
if tx.Error != nil {
|
|
return fmt.Errorf("unable to create database 'flamenco': %w", tx.Error)
|
|
}
|
|
|
|
// Close the connection so we can reconnect.
|
|
sqlDB, err := db.DB()
|
|
if err != nil {
|
|
fmt.Printf("error closing the database connection, please report this issue: %v", err)
|
|
} else {
|
|
sqlDB.Close()
|
|
}
|
|
|
|
// Allow 'flamenco' user to completely nuke and recreate the flamenco-test database, without needing 'CREATEDB' permission.
|
|
db, err = connectDBAsAdmin(adminPass, "flamenco-test")
|
|
if err != nil {
|
|
return fmt.Errorf("unable to reconnect to the database: %w", err)
|
|
}
|
|
tx = db.Exec("ALTER SCHEMA public OWNER TO ?", username)
|
|
if tx.Error != nil {
|
|
fmt.Printf("Unable to allow database user '%s' to reset the test database: %v\n", username, tx.Error)
|
|
fmt.Println("This is not an issue, unless you want to develop Flamenco yourself.")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func readPassword() (string, error) {
|
|
if pwFromEnv := os.Getenv("PSQL_ADMIN"); pwFromEnv != "" {
|
|
log.Info().Msg("getting password from PSQL_ADMIN environment variable")
|
|
return pwFromEnv, nil
|
|
}
|
|
|
|
fmt.Print("PostgreSQL admin password: ")
|
|
|
|
var (
|
|
line []byte
|
|
err error
|
|
)
|
|
|
|
if runtime.GOOS == "windows" {
|
|
// term.ReadPassword() doesn't work reliably on Windows, especially when you
|
|
// use a MingW terminal (like Git Bash). See
|
|
// https://github.com/golang/go/issues/11914#issuecomment-613715787 for more
|
|
// info.
|
|
//
|
|
// The downside is that this echoes the password to the terminal.
|
|
buf := bufio.NewReader(os.Stdin)
|
|
line, _, err = buf.ReadLine()
|
|
} else {
|
|
fd := int(os.Stdin.Fd())
|
|
line, err = term.ReadPassword(fd)
|
|
}
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(line), nil
|
|
}
|
|
|
|
func connectDBAsAdmin(password, database string) (*gorm.DB, error) {
|
|
dsn := fmt.Sprintf(adminDSN, password, database)
|
|
return gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
|
}
|