Files
photoprism/internal/service/cluster/provisioner/naming.go
2025-09-25 17:52:44 +02:00

73 lines
2.5 KiB
Go

package provisioner
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base32"
"fmt"
"strings"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/rnd"
)
const (
// Name prefix for generated DB objects.
// Final pattern without slugs (UUID-based):
// database: photoprism_d<hmac11>
// username: photoprism_u<hmac11>
prefix = "photoprism_"
dbSuffix = 11
userSuffix = 11
// Budgets: keep user conservative for MySQL compatibility; MariaDB allows more.
userMax = 32
dbMax = 64
)
// GenerateCredentials computes deterministic database name and user for a node under the given portal
// plus a random password. Naming is stable for a given (clusterUUID, nodeUUID) pair and changes
// if the cluster UUID or node UUID changes.
func GenerateCredentials(conf *config.Config, nodeUUID, nodeName string) (dbName, dbUser, dbPass string) {
clusterUUID := conf.ClusterUUID()
// Compute base32 (no padding) HMAC suffixes scoped by cluster UUID and node UUID.
sName := hmacBase32("db-name:"+clusterUUID, nodeUUID)
sUser := hmacBase32("db-user:"+clusterUUID, nodeUUID)
// Budgets: user ≤32, db ≤64. Suffixes are fixed length and derived from UUID.
dbName = fmt.Sprintf("%sd%s", prefix, sName[:dbSuffix])
dbUser = fmt.Sprintf("%su%s", prefix, sUser[:userSuffix])
dbPass = rnd.Base62(32)
return
}
// BuildDSN returns a DSN suitable for PhotoPrism nodes given a database driver.
// Currently, "mysql"/"mariadb" are supported; other drivers log a warning and fall back to MySQL format.
func BuildDSN(driver, host string, port int, user, pass, name string) string {
d := strings.ToLower(driver)
switch d {
case config.MySQL, config.MariaDB:
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=true",
user, pass, host, port, name,
)
default:
log.Warnf("provisioner: unsupported driver %q, falling back to mysql DSN format", driver)
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=true",
user, pass, host, port, name,
)
}
}
// hmacBase32 returns a lowercase Base32 (no padding) encoded HMAC-SHA256 digest
// derived from the provided key and data. It is used to generate deterministic
// suffixes for database identifiers while keeping the resulting string URL/identifier safe.
func hmacBase32(key, data string) string {
mac := hmac.New(sha256.New, []byte(key))
_, _ = mac.Write([]byte(data))
sum := mac.Sum(nil)
enc := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(sum)
return strings.ToLower(enc)
}