mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-06-02 08:50:08 +00:00
Allow arbitrary characters in passwords
This converts some shell scripted commands to small golang tools
("aio-container-tools") in order to ensure proper string handling.
In effect database passwords now can contain all characters, even emojis
and quotes.
AI-assistant: Copilot v1.0.7 (Claude Sonnet 4.6)
Signed-off-by: Pablo Zmdl <pablo@nextcloud.com>
This commit is contained in:
92
aio-container-tools/cmd/aio-pg-healthcheck/main.go
Normal file
92
aio-container-tools/cmd/aio-pg-healthcheck/main.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/nextcloud/aio-container-tools/internal/util"
|
||||
)
|
||||
|
||||
// tryConnect opens a TCP connection to the given database host:port and runs SELECT 1.
|
||||
// Returns nil on success, an error otherwise.
|
||||
func tryConnect(ctx context.Context, host string, port uint16, user, password, database string) error {
|
||||
util.Debugf("attempting connection: host=%s port=%d user=%s database=%s", host, port, user, database)
|
||||
|
||||
cfg, err := pgx.ParseConfig("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.Host = host
|
||||
cfg.Port = port
|
||||
cfg.User = user
|
||||
cfg.Password = password
|
||||
cfg.Database = database
|
||||
|
||||
conn, err := pgx.ConnectConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
util.Debugf("connection failed: %v", err)
|
||||
return err
|
||||
}
|
||||
defer conn.Close(ctx)
|
||||
|
||||
util.Debugf("connection established, running SELECT 1")
|
||||
var result string
|
||||
if err := conn.QueryRow(ctx, "SELECT 1").Scan(&result); err != nil {
|
||||
util.Debugf("SELECT 1 failed: %v", err)
|
||||
return err
|
||||
}
|
||||
util.Debugf("SELECT 1 returned %q", result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// envOrDefault returns the value of the named environment variable,
|
||||
// or the provided default if the variable is unset or empty.
|
||||
func envOrDefault(key, defaultVal string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
util.Debugf("env %s = %q", key, v)
|
||||
return v
|
||||
}
|
||||
util.Debugf("env %s not set, using default %q", key, defaultVal)
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func main() {
|
||||
debug := flag.Bool("debug", false, "enable debug output")
|
||||
flag.Parse()
|
||||
util.SetDebug(*debug)
|
||||
|
||||
util.Debugf("reading required environment variables")
|
||||
pgUser := util.RequireEnv("POSTGRES_USER")
|
||||
pgPassword := util.RequireEnv("POSTGRES_PASSWORD")
|
||||
pgDB := util.RequireEnv("POSTGRES_DB")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
pgHost := envOrDefault("POSTGRES_HOST", "127.0.0.1")
|
||||
|
||||
var pgPort uint16 = 5432
|
||||
if portStr := os.Getenv("POSTGRES_PORT"); portStr != "" {
|
||||
util.Debugf("env POSTGRES_PORT = %q", portStr)
|
||||
p, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "invalid POSTGRES_PORT %q: %v\n", portStr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pgPort = uint16(p)
|
||||
} else {
|
||||
util.Debugf("env POSTGRES_PORT not set, using default port %d", pgPort)
|
||||
}
|
||||
|
||||
util.Debugf("connecting to: host=%s port=%d user=%s", pgHost, pgPort, pgUser)
|
||||
if err := tryConnect(ctx, pgHost, pgPort, pgUser, pgPassword, pgDB); err == nil {
|
||||
util.Debugf("connection succeeded, exiting 0")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
util.Debugf("connection failed, exiting 1")
|
||||
os.Exit(1)
|
||||
}
|
||||
78
aio-container-tools/cmd/aio-pg-init/main.go
Normal file
78
aio-container-tools/cmd/aio-pg-init/main.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/nextcloud/aio-container-tools/internal/util"
|
||||
)
|
||||
|
||||
// quoteLiteral safely quotes a string as a PostgreSQL string literal.
|
||||
// Single quotes are escaped by doubling them. This is safe with
|
||||
// standard_conforming_strings=on (default since PostgreSQL 9.1).
|
||||
func quoteLiteral(s string) string {
|
||||
return "'" + strings.ReplaceAll(s, "'", "''") + "'"
|
||||
}
|
||||
|
||||
// main reimplements init-user-db.sh:
|
||||
// - Creates $POSTGRES_DB_OWNER (falling back to $POSTGRES_USER) with $POSTGRES_PASSWORD and CREATEDB
|
||||
// - Transfers ownership of $POSTGRES_DB to that user
|
||||
// - Grants all privileges on the database and public schema
|
||||
// - Connects using $POSTGRES_USER in all cases
|
||||
func main() {
|
||||
debug := flag.Bool("debug", false, "enable debug output")
|
||||
flag.Parse()
|
||||
util.SetDebug(*debug)
|
||||
|
||||
util.Debugf("reading required environment variables")
|
||||
pgUser := util.RequireEnv("POSTGRES_USER")
|
||||
pgPassword := util.RequireEnv("POSTGRES_PASSWORD")
|
||||
pgDB := util.RequireEnv("POSTGRES_DB")
|
||||
pgDBOwner := util.OptionalEnv("POSTGRES_DB_OWNER", pgUser)
|
||||
|
||||
util.Debugf("building connection config: host=/var/run/postgresql port=5432 user=%s database=%s", pgUser, pgDB)
|
||||
cfg, err := pgx.ParseConfig("")
|
||||
if err != nil {
|
||||
util.ErrorOut(fmt.Errorf("building connection config: %w", err))
|
||||
}
|
||||
cfg.Host = "/var/run/postgresql"
|
||||
cfg.Port = 5432
|
||||
cfg.User = pgUser
|
||||
cfg.Password = pgPassword
|
||||
cfg.Database = pgDB
|
||||
|
||||
ctx := context.Background()
|
||||
util.Debugf("connecting to postgres via unix socket")
|
||||
conn, err := pgx.ConnectConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
util.ErrorOut(fmt.Errorf("connecting to postgres: %w", err))
|
||||
}
|
||||
defer conn.Close(ctx)
|
||||
util.Debugf("connected successfully")
|
||||
|
||||
dbOwner := pgDBOwner
|
||||
util.Debugf("dbOwner = %q (from POSTGRES_DB_OWNER=%q, POSTGRES_USER=%q)", dbOwner, pgDBOwner, pgUser)
|
||||
// pgx.Identifier.Sanitize() double-quotes and escapes the identifier safely.
|
||||
dbOwnerIdent := pgx.Identifier{dbOwner}.Sanitize()
|
||||
dbIdent := pgx.Identifier{pgDB}.Sanitize()
|
||||
util.Debugf("quoted dbOwnerIdent = %s, dbIdent = %s", dbOwnerIdent, dbIdent)
|
||||
|
||||
statements := []string{
|
||||
fmt.Sprintf("CREATE USER %s WITH PASSWORD %s CREATEDB", dbOwnerIdent, quoteLiteral(pgPassword)),
|
||||
fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", dbIdent, dbOwnerIdent),
|
||||
fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s", dbIdent, dbOwnerIdent),
|
||||
fmt.Sprintf("GRANT ALL PRIVILEGES ON SCHEMA public TO %s", dbOwnerIdent),
|
||||
}
|
||||
|
||||
for i, stmt := range statements {
|
||||
util.Debugf("executing statement %d/%d: %s", i+1, len(statements), stmt)
|
||||
if _, err := conn.Exec(ctx, stmt); err != nil {
|
||||
util.ErrorOut(fmt.Errorf("executing statement: %w", err))
|
||||
}
|
||||
util.Debugf("statement %d/%d succeeded", i+1, len(statements))
|
||||
}
|
||||
util.Debugf("all statements executed successfully")
|
||||
}
|
||||
Reference in New Issue
Block a user