| 1 | #!/usr/bin/env -S v run |
| 2 | |
| 3 | import os |
| 4 | |
| 5 | const default_db_name = 'gitly' |
| 6 | const default_ci_db_name = 'gitly_ci' |
| 7 | const default_role_name = 'gitly' |
| 8 | const default_role_password = 'gitly' |
| 9 | const default_admin_db = 'postgres' |
| 10 | |
| 11 | struct Options { |
| 12 | mut: |
| 13 | db_name string = default_db_name |
| 14 | role_name string = default_role_name |
| 15 | role_password string = default_role_password |
| 16 | admin_db string = default_admin_db |
| 17 | with_ci bool |
| 18 | } |
| 19 | |
| 20 | fn main() { |
| 21 | mut opts := Options{ |
| 22 | db_name: env_or('GITLY_DB_NAME', default_db_name) |
| 23 | role_name: env_or('GITLY_DB_USER', default_role_name) |
| 24 | role_password: env_or('GITLY_DB_PASSWORD', default_role_password) |
| 25 | admin_db: env_or('GITLY_SETUP_ADMIN_DB', default_admin_db) |
| 26 | with_ci: env_bool('GITLY_SETUP_WITH_CI') |
| 27 | } |
| 28 | args := os.args[1..] |
| 29 | if '--help' in args || '-h' in args { |
| 30 | print_help() |
| 31 | return |
| 32 | } |
| 33 | parse_args(mut opts, args) |
| 34 | |
| 35 | psql := os.find_abs_path_of_executable('psql') or { |
| 36 | fail('`psql` was not found in PATH. Install PostgreSQL client tools first.') |
| 37 | return |
| 38 | } |
| 39 | |
| 40 | check_admin_connection(psql, opts.admin_db) or { |
| 41 | fail('Could not connect to PostgreSQL admin database `${opts.admin_db}`.\n${err.msg()}\nUse PGHOST/PGPORT/PGUSER/PGPASSWORD to point the script at an admin connection.') |
| 42 | return |
| 43 | } |
| 44 | |
| 45 | println('Using admin database `${opts.admin_db}`.') |
| 46 | println('Ensuring role `${opts.role_name}` and database `${opts.db_name}` exist.') |
| 47 | ensure_role(psql, opts.admin_db, opts.role_name, opts.role_password) or { |
| 48 | fail(err.msg()) |
| 49 | return |
| 50 | } |
| 51 | ensure_database(psql, opts.admin_db, opts.db_name, opts.role_name) or { |
| 52 | fail(err.msg()) |
| 53 | return |
| 54 | } |
| 55 | if opts.with_ci { |
| 56 | println('Ensuring CI database `${default_ci_db_name}` exists.') |
| 57 | ensure_database(psql, opts.admin_db, default_ci_db_name, opts.role_name) or { |
| 58 | fail(err.msg()) |
| 59 | return |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | println('') |
| 64 | println('PostgreSQL setup complete.') |
| 65 | println('Next step: ./gitly') |
| 66 | println('gitly will create its tables automatically on first start.') |
| 67 | if opts.with_ci { |
| 68 | println('Optional CI service: v run gitly_ci') |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | fn print_help() { |
| 73 | println('Usage: v run setup_db.vsh [options]') |
| 74 | println('') |
| 75 | println('Creates the PostgreSQL role/database that gitly expects on first run.') |
| 76 | println('Defaults:') |
| 77 | println(' database: ${default_db_name}') |
| 78 | println(' role: ${default_role_name}') |
| 79 | println(' password: ${default_role_password}') |
| 80 | println(' admin db: ${default_admin_db}') |
| 81 | println('') |
| 82 | println('Options:') |
| 83 | println(' --db-name=<name> Database name to create. Default: ${default_db_name}') |
| 84 | println(' --role=<name> Role name to create/update. Default: ${default_role_name}') |
| 85 | println(' --password=<value> Role password to set. Default: ${default_role_password}') |
| 86 | println(' --admin-db=<name> Admin database to connect to. Default: ${default_admin_db}') |
| 87 | println(' --with-ci Also create the `${default_ci_db_name}` database for gitly_ci') |
| 88 | println('') |
| 89 | println('Connection settings are taken from the normal PostgreSQL env vars:') |
| 90 | println(' PGHOST PGPORT PGUSER PGPASSWORD') |
| 91 | println('') |
| 92 | println('Optional env overrides:') |
| 93 | println(' GITLY_DB_NAME GITLY_DB_USER GITLY_DB_PASSWORD GITLY_SETUP_ADMIN_DB GITLY_SETUP_WITH_CI') |
| 94 | } |
| 95 | |
| 96 | fn parse_args(mut opts Options, args []string) { |
| 97 | for arg in args { |
| 98 | if arg == '--with-ci' { |
| 99 | opts.with_ci = true |
| 100 | continue |
| 101 | } |
| 102 | if arg.starts_with('--db-name=') { |
| 103 | opts.db_name = arg.all_after('--db-name=') |
| 104 | continue |
| 105 | } |
| 106 | if arg.starts_with('--role=') { |
| 107 | opts.role_name = arg.all_after('--role=') |
| 108 | continue |
| 109 | } |
| 110 | if arg.starts_with('--password=') { |
| 111 | opts.role_password = arg.all_after('--password=') |
| 112 | continue |
| 113 | } |
| 114 | if arg.starts_with('--admin-db=') { |
| 115 | opts.admin_db = arg.all_after('--admin-db=') |
| 116 | continue |
| 117 | } |
| 118 | fail('Unknown argument: ${arg}\nRun `v run setup_db.vsh --help` for usage.') |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | fn env_or(key string, fallback string) string { |
| 123 | if value := os.getenv_opt(key) { |
| 124 | if value != '' { |
| 125 | return value |
| 126 | } |
| 127 | } |
| 128 | return fallback |
| 129 | } |
| 130 | |
| 131 | fn env_bool(key string) bool { |
| 132 | value := os.getenv(key).trim_space().to_lower() |
| 133 | return value in ['1', 'true', 'yes', 'on'] |
| 134 | } |
| 135 | |
| 136 | fn check_admin_connection(psql string, admin_db string) ! { |
| 137 | _ = psql_query(psql, admin_db, 'select 1;')! |
| 138 | } |
| 139 | |
| 140 | fn ensure_role(psql string, admin_db string, role_name string, password string) ! { |
| 141 | if role_exists(psql, admin_db, role_name)! { |
| 142 | psql_exec(psql, admin_db, |
| 143 | 'alter role ${sql_ident(role_name)} with login password ${sql_literal(password)};')! |
| 144 | println('Updated role `${role_name}`.') |
| 145 | return |
| 146 | } |
| 147 | psql_exec(psql, admin_db, |
| 148 | 'create role ${sql_ident(role_name)} with login password ${sql_literal(password)};')! |
| 149 | println('Created role `${role_name}`.') |
| 150 | } |
| 151 | |
| 152 | fn ensure_database(psql string, admin_db string, db_name string, role_name string) ! { |
| 153 | if database_exists(psql, admin_db, db_name)! { |
| 154 | println('Database `${db_name}` already exists.') |
| 155 | } else { |
| 156 | psql_exec(psql, admin_db, |
| 157 | 'create database ${sql_ident(db_name)} owner ${sql_ident(role_name)};')! |
| 158 | println('Created database `${db_name}`.') |
| 159 | } |
| 160 | psql_exec(psql, admin_db, |
| 161 | 'alter database ${sql_ident(db_name)} owner to ${sql_ident(role_name)};')! |
| 162 | psql_exec(psql, admin_db, |
| 163 | 'grant all privileges on database ${sql_ident(db_name)} to ${sql_ident(role_name)};')! |
| 164 | psql_exec(psql, db_name, 'alter schema public owner to ${sql_ident(role_name)};')! |
| 165 | psql_exec(psql, db_name, 'grant all on schema public to ${sql_ident(role_name)};')! |
| 166 | } |
| 167 | |
| 168 | fn role_exists(psql string, admin_db string, role_name string) !bool { |
| 169 | result := psql_query(psql, admin_db, |
| 170 | 'select 1 from pg_roles where rolname = ${sql_literal(role_name)};')! |
| 171 | return result == '1' |
| 172 | } |
| 173 | |
| 174 | fn database_exists(psql string, admin_db string, db_name string) !bool { |
| 175 | result := psql_query(psql, admin_db, |
| 176 | 'select 1 from pg_database where datname = ${sql_literal(db_name)};')! |
| 177 | return result == '1' |
| 178 | } |
| 179 | |
| 180 | fn psql_query(psql string, database string, query string) !string { |
| 181 | cmd := '${os.quoted_path(psql)} -X -v ON_ERROR_STOP=1 -d ${os.quoted_path(database)} -tAc ${os.quoted_path(query)}' |
| 182 | res := os.execute(cmd) |
| 183 | if res.exit_code != 0 { |
| 184 | return error(res.output.trim_space()) |
| 185 | } |
| 186 | return res.output.trim_space() |
| 187 | } |
| 188 | |
| 189 | fn psql_exec(psql string, database string, query string) ! { |
| 190 | cmd := '${os.quoted_path(psql)} -X -v ON_ERROR_STOP=1 -d ${os.quoted_path(database)} -c ${os.quoted_path(query)}' |
| 191 | res := os.execute(cmd) |
| 192 | if res.exit_code != 0 { |
| 193 | return error(res.output.trim_space()) |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | fn sql_literal(value string) string { |
| 198 | return "'" + value.replace("'", "''") + "'" |
| 199 | } |
| 200 | |
| 201 | fn sql_ident(value string) string { |
| 202 | return '"' + value.replace('"', '""') + '"' |
| 203 | } |
| 204 | |
| 205 | fn fail(message string) { |
| 206 | eprintln(message) |
| 207 | exit(1) |
| 208 | } |
| 209 | |