Skip to content

Commit 92d1e5e

Browse files
committed
setup_db.vsh
1 parent b9dbdea commit 92d1e5e

1 file changed

Lines changed: 208 additions & 0 deletions

File tree

setup_db.vsh

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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+
}

0 commit comments

Comments
 (0)