"fmt"
"os"
"os/exec"
+ "os/user"
"path/filepath"
+ "strconv"
"strings"
"time"
"github.com/lib/pq"
)
+// Run a postgresql server in a private data directory. Set up a db
+// user, database, and TCP listener that match the supervisor's
+// configured database connection info.
type runPostgreSQL struct{}
func (runPostgreSQL) String() string {
return "postgresql"
}
-func (runPostgreSQL) Run(ctx context.Context, fail func(error), boot *Booter) error {
- err := boot.wait(ctx, createCertificates{})
+func (runPostgreSQL) Run(ctx context.Context, fail func(error), super *Supervisor) error {
+ err := super.wait(ctx, createCertificates{})
if err != nil {
return err
}
+ if super.ClusterType == "production" {
+ return nil
+ }
+
+ iamroot := false
+ if u, err := user.Current(); err != nil {
+ return fmt.Errorf("user.Current(): %w", err)
+ } else if u.Uid == "0" {
+ iamroot = true
+ }
+
buf := bytes.NewBuffer(nil)
- err = boot.RunProgram(ctx, boot.tempdir, buf, nil, "pg_config", "--bindir")
+ err = super.RunProgram(ctx, super.tempdir, runOptions{output: buf}, "pg_config", "--bindir")
if err != nil {
return err
}
bindir := strings.TrimSpace(buf.String())
- datadir := filepath.Join(boot.tempdir, "pgdata")
- err = os.Mkdir(datadir, 0755)
+ datadir := filepath.Join(super.tempdir, "pgdata")
+ err = os.Mkdir(datadir, 0700)
if err != nil {
return err
}
- err = boot.RunProgram(ctx, boot.tempdir, nil, nil, filepath.Join(bindir, "initdb"), "-D", datadir)
+ prog, args := filepath.Join(bindir, "initdb"), []string{"-D", datadir, "-E", "utf8"}
+ opts := runOptions{}
+ if iamroot {
+ postgresUser, err := user.Lookup("postgres")
+ if err != nil {
+ return fmt.Errorf("user.Lookup(\"postgres\"): %s", err)
+ }
+ postgresUID, err := strconv.Atoi(postgresUser.Uid)
+ if err != nil {
+ return fmt.Errorf("user.Lookup(\"postgres\"): non-numeric uid?: %q", postgresUser.Uid)
+ }
+ postgresGid, err := strconv.Atoi(postgresUser.Gid)
+ if err != nil {
+ return fmt.Errorf("user.Lookup(\"postgres\"): non-numeric gid?: %q", postgresUser.Gid)
+ }
+ err = os.Chown(super.tempdir, 0, postgresGid)
+ if err != nil {
+ return err
+ }
+ err = os.Chmod(super.tempdir, 0710)
+ if err != nil {
+ return err
+ }
+ err = os.Chown(datadir, postgresUID, 0)
+ if err != nil {
+ return err
+ }
+ opts.user = "postgres"
+ }
+ err = super.RunProgram(ctx, super.tempdir, opts, prog, args...)
if err != nil {
return err
}
- err = boot.RunProgram(ctx, boot.tempdir, nil, nil, "cp", "server.crt", "server.key", datadir)
+ err = super.RunProgram(ctx, super.tempdir, runOptions{}, "cp", "server.crt", "server.key", datadir)
if err != nil {
return err
}
+ if iamroot {
+ err = super.RunProgram(ctx, super.tempdir, runOptions{}, "chown", "postgres", datadir+"/server.crt", datadir+"/server.key")
+ if err != nil {
+ return err
+ }
+ }
- port := boot.cluster.PostgreSQL.Connection["port"]
+ port := super.cluster.PostgreSQL.Connection["port"]
- boot.waitShutdown.Add(1)
+ super.waitShutdown.Add(1)
go func() {
- defer boot.waitShutdown.Done()
- fail(boot.RunProgram(ctx, boot.tempdir, nil, nil, filepath.Join(bindir, "postgres"),
+ defer super.waitShutdown.Done()
+ prog, args := filepath.Join(bindir, "postgres"), []string{
"-l", // enable ssl
"-D", datadir, // data dir
"-k", datadir, // socket dir
- "-p", boot.cluster.PostgreSQL.Connection["port"],
- ))
+ "-h", super.cluster.PostgreSQL.Connection["host"],
+ "-p", super.cluster.PostgreSQL.Connection["port"],
+ }
+ opts := runOptions{}
+ if iamroot {
+ opts.user = "postgres"
+ }
+ fail(super.RunProgram(ctx, super.tempdir, opts, prog, args...))
}()
for {
if ctx.Err() != nil {
return ctx.Err()
}
- if exec.CommandContext(ctx, "pg_isready", "--timeout=10", "--host="+boot.cluster.PostgreSQL.Connection["host"], "--port="+port).Run() == nil {
+ if exec.CommandContext(ctx, "pg_isready", "--timeout=10", "--host="+super.cluster.PostgreSQL.Connection["host"], "--port="+port).Run() == nil {
break
}
time.Sleep(time.Second / 2)
}
- db, err := sql.Open("postgres", arvados.PostgreSQLConnection{
+ pgconn := arvados.PostgreSQLConnection{
"host": datadir,
"port": port,
"dbname": "postgres",
- }.String())
+ }
+ if iamroot {
+ pgconn["user"] = "postgres"
+ }
+ db, err := sql.Open("postgres", pgconn.String())
if err != nil {
return fmt.Errorf("db open failed: %s", err)
}
return fmt.Errorf("db conn failed: %s", err)
}
defer conn.Close()
- _, err = conn.ExecContext(ctx, `CREATE USER `+pq.QuoteIdentifier(boot.cluster.PostgreSQL.Connection["user"])+` WITH SUPERUSER ENCRYPTED PASSWORD `+pq.QuoteLiteral(boot.cluster.PostgreSQL.Connection["password"]))
+ _, err = conn.ExecContext(ctx, `CREATE USER `+pq.QuoteIdentifier(super.cluster.PostgreSQL.Connection["user"])+` WITH SUPERUSER ENCRYPTED PASSWORD `+pq.QuoteLiteral(super.cluster.PostgreSQL.Connection["password"]))
if err != nil {
return fmt.Errorf("createuser failed: %s", err)
}
- _, err = conn.ExecContext(ctx, `CREATE DATABASE `+pq.QuoteIdentifier(boot.cluster.PostgreSQL.Connection["dbname"]))
+ _, err = conn.ExecContext(ctx, `CREATE DATABASE `+pq.QuoteIdentifier(super.cluster.PostgreSQL.Connection["dbname"])+` WITH TEMPLATE template0 ENCODING 'utf8'`)
if err != nil {
return fmt.Errorf("createdb failed: %s", err)
}