ggdgsdbsdbbb / setup_db.vsh
208 lines · 186 sloc · 6.47 KB · 92d1e5eb1ce6bb37d155d164fbc0b7e8a0cbe869
Raw
1#!/usr/bin/env -S v run
2
3import os
4
5const default_db_name = 'gitly'
6const default_ci_db_name = 'gitly_ci'
7const default_role_name = 'gitly'
8const default_role_password = 'gitly'
9const default_admin_db = 'postgres'
10
11struct Options {
12mut:
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
20fn 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
72fn 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
96fn 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
122fn 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
131fn env_bool(key string) bool {
132 value := os.getenv(key).trim_space().to_lower()
133 return value in ['1', 'true', 'yes', 'on']
134}
135
136fn check_admin_connection(psql string, admin_db string) ! {
137 _ = psql_query(psql, admin_db, 'select 1;')!
138}
139
140fn 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
152fn 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
168fn 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
174fn 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
180fn 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
189fn 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
197fn sql_literal(value string) string {
198 return "'" + value.replace("'", "''") + "'"
199}
200
201fn sql_ident(value string) string {
202 return '"' + value.replace('"', '""') + '"'
203}
204
205fn fail(message string) {
206 eprintln(message)
207 exit(1)
208}
209