2021-04-20 12:26:42 +01:00
require 'msfdb_helpers/db_interface'
2021-04-16 13:52:55 +01:00
2021-04-30 14:24:56 +01:00
module MsfdbHelpers
class PgCtl < DbInterface
def initialize ( db_path : , options : , localconf : , db_conf : )
@db = db_path
@options = options
@localconf = localconf
@db_conf = db_conf
2022-06-23 16:50:23 +05:30
@socket_directory = db_path
2021-04-30 14:24:56 +01:00
super ( options )
2021-04-26 02:24:11 +01:00
end
2021-04-30 14:24:56 +01:00
def init ( msf_pass , msftest_pass )
puts " Creating database at #{ @db } "
Dir . mkdir ( @db )
2021-05-13 16:44:17 +01:00
run_cmd ( " initdb --auth-host=trust --auth-local=trust -E UTF8 #{ @db . shellescape } " )
2022-01-25 20:24:14 +05:30
2022-01-25 20:11:57 +05:30
File . open ( " #{ @db } /postgresql.conf " , 'a' ) do | f |
f . puts " port = #{ @options [ :db_port ] } "
2022-06-23 16:50:23 +05:30
end
2022-06-23 20:51:28 +05:30
# Try creating a test file at {Dir.tmpdir},
# Else fallback to creation at @{db}
# Else fail with error.
if test_executable_file ( " #{ Dir . tmpdir } " )
@socket_directory = Dir . tmpdir
elsif test_executable_file ( " #{ @db } " )
@socket_directory = @db
else
2022-07-27 11:47:31 -05:00
print_error ( " Attempt to create DB socket file at Temporary Directory and `~/.msf4/db` failed. Possibly because they are mounted with NOEXEC flags. Database initialization failed. " )
2021-04-30 14:24:56 +01:00
end
2023-10-10 13:47:46 +01:00
2021-04-30 14:24:56 +01:00
start
2021-04-26 02:24:11 +01:00
2021-04-30 14:24:56 +01:00
create_db_users ( msf_pass , msftest_pass )
2021-04-16 13:52:55 +01:00
2021-04-30 14:24:56 +01:00
write_db_client_auth_config
restart
end
2021-04-16 13:52:55 +01:00
2022-06-23 16:50:23 +05:30
# Creates and attempts to execute a testfile in the specified directory,
# to determine if it is mounted with NOEXEC flags.
def test_executable_file ( path )
2022-06-23 20:51:28 +05:30
begin
file_name = File . join ( path , 'msfdb_testfile' )
File . open ( file_name , 'w' ) do | f |
f . puts " # !/bin/bash \n echo exec "
end
File . chmod ( 0744 , file_name )
2023-10-10 13:47:46 +01:00
2022-06-23 20:51:28 +05:30
if run_cmd ( file_name )
File . open ( " #{ @db } /postgresql.conf " , 'a' ) do | f |
f . puts " unix_socket_directories = \' #{ path } \' "
end
puts " Creating db socket file at #{ path } "
end
return true
rescue = > e
return false
ensure
begin
File . delete ( file_name )
rescue
print_error ( " Unable to delete test file #{ file_name } " )
2022-06-23 16:50:23 +05:30
end
end
2022-06-23 20:51:28 +05:30
2022-06-23 16:50:23 +05:30
end
2021-04-30 14:24:56 +01:00
def delete
2021-06-17 21:27:33 +01:00
if exists?
2021-04-30 14:24:56 +01:00
stop
2021-04-16 13:52:55 +01:00
2021-04-30 14:24:56 +01:00
if @options [ :delete_existing_data ]
puts " Deleting all data at #{ @db } "
FileUtils . rm_rf ( @db )
end
if @options [ :delete_existing_data ]
2021-06-17 21:27:33 +01:00
FileUtils . rm_r ( @db_conf , force : true )
2021-04-30 14:24:56 +01:00
end
else
puts " No data at #{ @db } , doing nothing "
2021-04-16 13:52:55 +01:00
end
end
2021-04-30 14:24:56 +01:00
def start
2021-06-17 21:27:33 +01:00
if status == DatabaseStatus :: RUNNING
2021-04-30 14:24:56 +01:00
puts " Database already started at #{ @db } "
return true
end
print " Starting database at #{ @db } ... "
2023-10-10 13:47:46 +01:00
pg_ctl_spawn_cmd = " pg_ctl -o \" -p #{ @options [ :db_port ] } \" -D #{ @db . shellescape } -l #{ @db . shellescape } /log start & "
puts " spawn_cmd: #{ pg_ctl_spawn_cmd } " if @options [ :debug ]
pg_ctl_pid = Process . spawn ( pg_ctl_spawn_cmd )
Process . detach ( pg_ctl_pid )
is_database_running = retry_until_truthy ( timeout : 60 ) do
status == DatabaseStatus :: RUNNING
end
if is_database_running
2021-04-30 14:24:56 +01:00
puts 'success' . green . bold . to_s
true
2023-10-10 13:47:46 +01:00
else
begin
Process . kill ( :KILL , pg_ctl_pid )
rescue = > e
puts " Failed to kill pg_ctl_pid= #{ pg_ctl_pid } - #{ e . class } #{ e . message } " if @options [ :debug ]
end
puts 'failed' . red . bold . to_s
false
2021-04-30 14:24:56 +01:00
end
2021-04-16 13:52:55 +01:00
end
2021-04-30 14:24:56 +01:00
def stop
2021-06-17 21:27:33 +01:00
if status == DatabaseStatus :: RUNNING
2021-04-30 14:24:56 +01:00
puts " Stopping database at #{ @db } "
2021-05-13 16:44:17 +01:00
run_cmd ( " pg_ctl -o \" -p #{ @options [ :db_port ] } \" -D #{ @db . shellescape } stop " )
2021-04-30 14:24:56 +01:00
else
puts " Database is no longer running at #{ @db } "
end
2021-04-16 13:52:55 +01:00
end
2021-04-30 14:24:56 +01:00
def restart
stop
start
end
2021-04-16 13:52:55 +01:00
2021-06-17 21:27:33 +01:00
def exists?
Dir . exist? ( @db )
end
2021-04-30 14:24:56 +01:00
def status
2021-06-17 21:27:33 +01:00
if exists?
2021-05-13 16:44:17 +01:00
if run_cmd ( " pg_ctl -o \" -p #{ @options [ :db_port ] } \" -D #{ @db . shellescape } status " ) == 0
2021-06-17 21:27:33 +01:00
DatabaseStatus :: RUNNING
2021-04-30 14:24:56 +01:00
else
2021-06-17 21:27:33 +01:00
DatabaseStatus :: INACTIVE
2021-04-30 14:24:56 +01:00
end
2021-04-16 13:52:55 +01:00
else
2021-06-17 21:27:33 +01:00
DatabaseStatus :: NOT_FOUND
2021-04-16 13:52:55 +01:00
end
end
2021-04-30 14:24:56 +01:00
def create_db_users ( msf_pass , msftest_pass )
puts 'Creating database users'
2022-06-23 16:50:23 +05:30
run_psql ( " create user #{ @options [ :msf_db_user ] . shellescape } with password ' #{ msf_pass } ' " , @socket_directory )
run_psql ( " create user #{ @options [ :msftest_db_user ] . shellescape } with password ' #{ msftest_pass } ' " , @socket_directory )
run_psql ( " alter role #{ @options [ :msf_db_user ] . shellescape } createdb " , @socket_directory )
run_psql ( " alter role #{ @options [ :msftest_db_user ] . shellescape } createdb " , @socket_directory )
run_psql ( " alter role #{ @options [ :msf_db_user ] . shellescape } with password ' #{ msf_pass } ' " , @socket_directory )
run_psql ( " alter role #{ @options [ :msftest_db_user ] . shellescape } with password ' #{ msftest_pass } ' " , @socket_directory )
2021-04-30 14:24:56 +01:00
conn = PG . connect ( host : @options [ :db_host ] , dbname : 'postgres' , port : @options [ :db_port ] , user : @options [ :msf_db_user ] , password : msf_pass )
conn . exec ( " CREATE DATABASE #{ @options [ :msf_db_name ] } " )
conn . exec ( " CREATE DATABASE #{ @options [ :msftest_db_name ] } " )
conn . finish
end
2021-04-26 02:24:11 +01:00
2021-04-30 14:24:56 +01:00
def write_db_client_auth_config
client_auth_config = " #{ @db } /pg_hba.conf "
super ( client_auth_config )
end
2021-04-20 13:47:47 +01:00
2021-04-30 14:24:56 +01:00
def self . requirements
%w[ psql pg_ctl initdb createdb ]
end
2023-10-10 13:47:46 +01:00
protected
def retry_until_truthy ( timeout : )
start_time = Process . clock_gettime ( Process :: CLOCK_MONOTONIC , :second )
ending_time = start_time + timeout
retry_count = 0
while Process . clock_gettime ( Process :: CLOCK_MONOTONIC , :second ) < ending_time
result = yield
return result if result
retry_count += 1
remaining_time_budget = ending_time - Process . clock_gettime ( Process :: CLOCK_MONOTONIC , :second )
break if remaining_time_budget < = 0
delay = 2 ** retry_count
if delay > = remaining_time_budget
delay = remaining_time_budget
puts ( " Final attempt. Sleeping for the remaining #{ delay } seconds out of total timeout #{ timeout } " ) if @options [ :debug ]
else
puts ( " Sleeping for #{ delay } seconds before attempting again " ) if @options [ :debug ]
end
sleep delay
end
nil
end
2021-04-20 13:47:47 +01:00
end
2021-04-16 13:52:55 +01:00
end