@@ -347,8 +347,9 @@ func (p *Process) assumePrimary(downstream *discoverd.Instance) (err error) {
347347 if p .running () && p .config ().Role == state .RoleSync {
348348 log .Info ("promoting to primary" )
349349
350- if err := os .WriteFile (p .triggerPath (), nil , 0655 ); err != nil {
351- log .Error ("error creating trigger file" , "path" , p .triggerPath (), "err" , err )
350+ // PG 16 removed promote_trigger_file; use pg_ctl promote instead
351+ if err := p .runCmd (exec .Command (p .binPath ("pg_ctl" ), "promote" , "-D" , p .dataDir )); err != nil {
352+ log .Error ("error promoting standby" , "err" , err )
352353 return err
353354 }
354355
@@ -369,6 +370,10 @@ func (p *Process) assumePrimary(downstream *discoverd.Instance) (err error) {
369370
370371 if err := os .Remove (p .recoveryConfPath ()); err != nil && ! os .IsNotExist (err ) {
371372 log .Error ("error removing recovery.conf" , "path" , p .recoveryConfPath (), "err" , err )
373+ // non-fatal, PG 16 doesn't use recovery.conf
374+ }
375+ if err := os .Remove (p .standbySignalPath ()); err != nil && ! os .IsNotExist (err ) {
376+ log .Error ("error removing standby.signal" , "path" , p .standbySignalPath (), "err" , err )
372377 return err
373378 }
374379
@@ -483,6 +488,14 @@ func (p *Process) installExtensionsInTemplate() error {
483488 return fmt .Errorf ("creating extension %s in template1: %s" , ext , err )
484489 }
485490 }
491+
492+ // In PostgreSQL 15+, the default CREATE privilege on the public schema was
493+ // revoked for non-owner roles. Restore the PG14 behavior so that application
494+ // users can create tables in the public schema of their databases.
495+ if _ , err := templateDB .Exec ("GRANT ALL ON SCHEMA public TO PUBLIC" ); err != nil {
496+ return fmt .Errorf ("granting public schema privileges in template1: %s" , err )
497+ }
498+
486499 return nil
487500}
488501
@@ -536,12 +549,8 @@ func (p *Process) assumeStandby(upstream, downstream *discoverd.Instance) error
536549 os .Remove (p .triggerPath ())
537550 }
538551
539- if err := p .writeConfig (configData {ReadOnly : true }); err != nil {
540- log .Error ("error writing postgres.conf" , "path" , p .configPath (), "err" , err )
541- return err
542- }
543- if err := p .writeRecoveryConf (upstream ); err != nil {
544- log .Error ("error writing recovery.conf" , "path" , p .recoveryConfPath (), "err" , err )
552+ if err := p .writeRecoveryConf (upstream , configData {ReadOnly : true }); err != nil {
553+ log .Error ("error writing recovery config" , "path" , p .configPath (), "err" , err )
545554 return err
546555 }
547556
@@ -897,21 +906,21 @@ func (p *Process) writeConfig(d configData) error {
897906 return configTemplate .Execute (f , d )
898907}
899908
900- func (p * Process ) writeRecoveryConf (upstream * discoverd.Instance ) error {
901- data := recoveryData {
902- TriggerFile : p .triggerPath (),
903- PrimaryInfo : fmt .Sprintf (
904- "host=%s port=%s user=flynn password=%s application_name=%s" ,
905- upstream .Host (), upstream .Port (), p .password , p .id ,
906- ),
907- }
909+ func (p * Process ) recoveryConfigData (upstream * discoverd.Instance , d configData ) configData {
910+ d .PrimaryConnInfo = fmt .Sprintf (
911+ "host=%s port=%s user=flynn password=%s application_name=%s" ,
912+ upstream .Host (), upstream .Port (), p .password , p .id ,
913+ )
914+ return d
915+ }
908916
909- f , err := os .Create (p .recoveryConfPath ())
910- if err != nil {
917+ func (p * Process ) writeRecoveryConf (upstream * discoverd.Instance , d configData ) error {
918+ // PG 12+ uses postgresql.conf for recovery settings + standby.signal file
919+ if err := p .writeConfig (p .recoveryConfigData (upstream , d )); err != nil {
911920 return err
912921 }
913- defer f . Close ()
914- return recoveryConfTemplate . Execute ( f , data )
922+ // Create standby.signal to indicate this is a standby
923+ return os . WriteFile ( p . standbySignalPath (), nil , 0644 )
915924}
916925
917926func (p * Process ) writeHBAConf () error {
@@ -923,9 +932,14 @@ func (p *Process) configPath() string {
923932}
924933
925934func (p * Process ) recoveryConfPath () string {
935+ // PG 16 no longer uses recovery.conf; keep this for cleanup of old files
926936 return p .dataPath ("recovery.conf" )
927937}
928938
939+ func (p * Process ) standbySignalPath () string {
940+ return p .dataPath ("standby.signal" )
941+ }
942+
929943func (p * Process ) hbaConfPath () string {
930944 return p .dataPath ("pg_hba.conf" )
931945}
@@ -951,6 +965,9 @@ type configData struct {
951965 TimescaleDB bool
952966 ExtWhitelist bool
953967 SHMType string
968+
969+ // Recovery settings (PG 12+ uses postgresql.conf instead of recovery.conf)
970+ PrimaryConnInfo string
954971}
955972
956973var configTemplate = template .Must (template .New ("postgresql.conf" ).Parse (`
@@ -960,10 +977,10 @@ port = {{.Port}}
960977ssl = off
961978max_connections = 400
962979shared_buffers = 32MB
963- wal_level = hot_standby
980+ wal_level = replica
964981fsync = on
965982max_wal_senders = 15
966- wal_keep_segments = 128
983+ wal_keep_size = 2048
967984synchronous_commit = remote_write
968985synchronous_standby_names = '{{.Sync}}'
969986{{if .ReadOnly}}
@@ -985,6 +1002,7 @@ datestyle = 'iso, mdy'
9851002timezone = 'UTC'
9861003client_encoding = 'UTF8'
9871004default_text_search_config = 'pg_catalog.english'
1005+ password_encryption = md5
9881006
9891007{{if .TimescaleDB}}
9901008shared_preload_libraries = 'timescaledb'
@@ -998,18 +1016,12 @@ dynamic_shared_memory_type = '{{.SHMType}}'
9981016local_preload_libraries = 'pgextwlist'
9991017extwlist.extensions = 'btree_gin,btree_gist,chkpass,citext,cube,dblink,dict_int,earthdistance,fuzzystrmatch,hstore,intarray,isn,ltree,pg_prewarm,pg_stat_statements,pg_trgm,pgcrypto,pgrouting,pgrowlocks,pgstattuple,plpgsql,plv8,postgis,postgis_topology,postgres_fdw,tablefunc,timescaledb,unaccent,uuid-ossp'
10001018{{end}}
1001- ` [1 :]))
1002-
1003- type recoveryData struct {
1004- PrimaryInfo string
1005- TriggerFile string
1006- }
10071019
1008- var recoveryConfTemplate = template .Must (template .New ("recovery.conf" ).Parse (`
1009- standby_mode = on
1010- primary_conninfo = '{{.PrimaryInfo}}'
1011- trigger_file = '{{.TriggerFile}}'
1020+ {{if .PrimaryConnInfo}}
1021+ # Recovery settings (managed by flynn-postgres, PG 12+)
1022+ primary_conninfo = '{{.PrimaryConnInfo}}'
10121023recovery_target_timeline = 'latest'
1024+ {{end}}
10131025` [1 :]))
10141026
10151027var hbaConf = []byte (`
0 commit comments