Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

18.1 bugfix #41

Merged
merged 3 commits into from
Aug 17, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
turnkey-gitlab-18.1 (1) turnkey; urgency=low

* Update GitLab to latest GitLab-CE v17.3.0- via upstream apt repo.

* Confconsole: Bugfix, improve and update GitLab specific Let's Encrypt
plugin - closes #1975.

* Inithooks: Improve and update GitLab inithook

-- Jeremy Davis <jeremy@turnkeylinux.org> Fri, 16 Aug 2024 12:44:58 +0000

turnkey-gitlab-18.0 (1) turnkey; urgency=low

* Install latest GitLab-CE v16.7.7 - from third party GitLab apt repo.
110 changes: 62 additions & 48 deletions overlay/usr/lib/confconsole/plugins.d/Lets_Encrypt/get_certificate.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@

import requests
import subprocess
from os import path, remove

# import inithooks_cache (from absolute path) for managing domain caching
import sys
@@ -22,45 +21,52 @@
support for Let's Encrypt.

For more details, please see:
https://docs.gitlab.com/omnibus/settings/ssl.html
https://docs.gitlab.com/omnibus/settings/ssl/
"""


example_domain = 'www.example.com'

# XXX Debug paths

def load_domain():

def load_domain() -> str:
''' Loads domain from inithooks cache '''
return inithooks_cache.read('APP_DOMAIN')
return str(inithooks_cache.read('APP_DOMAIN'))


def save_domain(domain):
''' Saves domain configuration '''
inithooks_cache.write('APP_DOMAIN', domain)


def strip_schema(url: str) -> str:
'''Return domain with http/https schema stripped'''
if url.startswith('http://'):
return url[7:]
elif url.startswith("https://"):
return url[8:]
return url


def invalid_domain(domain):
''' Validates well known limitations of domain-name specifications
doesn't enforce when or if special characters are valid. Returns a
string if domain is invalid explaining why otherwise returns False'''
if domain == '':
return ('Error: A domain must be provided in {} (with no'
' preceeding space)'.format(domain_path))
return 'Error: A domain must be provided'
if len(domain) != 0:
if len(domain) > 254:
return ('Error in {}: Domain name must not exceed 254'
' characters'.format(domain))
return 'Error: Domain must not exceed 254 characters'
for part in domain.split('.'):
if not 0 < len(part) < 64:
return ('Error in {}: Domain segments may not be larger'
' than 63 characters or less than 1'.format(domain))
return ('Error: Domain segments may not be larger than 63'
' characters or less than 1')
return False

def uncomment(file_name, search_term):
'''Dirty function that leverages sed.'''
subprocess.run(["sed", "-i", "/{}/ s|^# *||".format(search_term), filename])

def run():
field_width = 60
field_name = 'domain'

canceled = False

@@ -69,7 +75,7 @@ def run():
response = requests.get(LE_INFO_URL)
tos_url = response.json()['meta']['termsOfService']
except requests.exceptions.RequestException as e:
msg = "Failed to connect get data from '{}': '{}'".format(LE_INFO_URL, e)
msg = f"Failed to connect get data from '{LE_INFO_URL}': '{e}'"
if not tos_url:
console.msgbox('Error', msg, autosize=True)
return
@@ -85,34 +91,26 @@ def run():
return

ret = console.yesno(
"Before getting a Let's Encrypt certificate, you must agree "
'to the current Terms of Service.\n\n'
'You can find the current Terms of Service here:\n\n'
+tos_url+'\n\n'
"Do you agree to the Let's Encrypt Terms of Service?",
"Before getting a Let's Encrypt certificate, you must agree to the"
" current Terms of Service."
f"\n\nYou can find the current Terms of Service here: \n\n{tos_url}"
"\n\nDo you agree to the Let's Encrypt Terms of Service?",
autosize=True
)
if ret != 'ok':
return

domain = load_domain()
m = invalid_domain(domain)

if m:
ret = console.yesno(
(str(m) + '\n\nWould you like to ignore and overwrite data?'))
if ret == 'ok':
remove(domain_path)
domain = load_domain()
else:
return

value = domain
# should have a cached valid domain from firstboot
domain = strip_schema(load_domain())
# but double check and use example if not
if invalid_domain(domain):
domain = example_domain
domain = f"https://{domain}"

while True:
while True:
field = [
('Domain', 1, 0, value, 1, 10, field_width, 255),
('Domain', 1, 0, domain, 1, 10, field_width, 255),
]
ret, value = console.form(TITLE, DESC, field, autosize=True)
if len(value) >= 1:
@@ -127,7 +125,8 @@ def run():
continue

if ret == 'ok':
ret2 = console.yesno('This will overwrite previous settings and check for certificate, continue?')
ret2 = console.yesno("This will overwrite previous settings"
" and check for certificate, continue?")
if ret2 == 'ok':
save_domain(value)
break
@@ -136,20 +135,35 @@ def run():
break

config = "/etc/gitlab/gitlab.rb"
domain = "https://{}".format(domain)
subprocess.run(["sed", "-i", "/^external_url/ s|'.*|'{}'|".format(domain), config])
subprocess.run(["sed", "-i", "/letsencrypt\['enable'\]/ s|^# *||", config])
subprocess.run(["sed", "-i", "/^letsencrypt\['enable'\]/ s|=.*|= true|", config])
subprocess.run(["sed", "-i", "/letsencrypt\['auto_renew'\]/ s|^# *||", config])
subprocess.run(["sed", "-i", "/^letsencrypt\['auto_renew'\]/ s|=.*|= true|", config])
# should be https already - but ensure it
domain = f"https://{strip_schema(domain)}"

subprocess.run(["sed", "-i",
f"/^external_url/ s|'.*|'{domain}'|", config])
subprocess.run(["sed", "-i",
r"/letsencrypt\['enable'\]/ s|^# *||", config])
subprocess.run(["sed", "-i",
r"/^letsencrypt\['enable'\]/ s|=.*|= true|", config])
subprocess.run(["sed", "-i",
r"/letsencrypt\['auto_renew'\]/ s|^# *||", config])
subprocess.run(["sed", "-i",
r"/^letsencrypt\['auto_renew'\]/ s|=.*|= true|",
config])
print('Running gitlab-ctl reconfigure. This might take a while...')
exit_code = subprocess.run(['gitlab-ctl', 'reconfigure']).returncode

if exit_code != 0:
console.msgbox('GitLab Error!', 'Something went wrong!\nPlease check that '
'the domain {} resolves to a publicly accessable IP address for this '
'server and that ports 80 and 443 are publicly accessible.\n\n'
'It is also possible that there is some other issue with your config '
'file ({}).\n\nFor full details, please try running \'gitlab-ctl '
'reconfigure\' from the commandline.\n\nAlso see:\n'
'https://docs.gitlab.com/omnibus/settings/ssl.html'.format(domain, config))
console.msgbox(
"GitLab Error!",
"Something went wrong! :("
f"\n\nPlease check that the domain {domain} resolves to a"
" publicly accessable IP address for this server and that"
" ports 80 and 443 are publicly accessible."
"\n\nIt is also possible that there is some other issue with"
f" your config file ({config})."
"\n\nFor full details, please try running 'gitlab-ctl"
" reconfigure' from the commandline."
"\n\nAlso see:\n"
"\nhttps://docs.gitlab.com/omnibus/settings/ssl/")
else:
save_domain(domain)
40 changes: 27 additions & 13 deletions overlay/usr/lib/inithooks/bin/gitlab.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
#!/usr/bin/python3
"""Set GitLab root (admin) password, email and domain to serve
"""Set GitLab root user password, email and domain to serve

Option:
--pass= unless provided, will ask interactively
--email= unless provided, will ask interactively
--domain= unless provided, will ask interactively
(can include schema)
- can include schema
DEFAULT=www.example.com
--schema= unless provided will default to 'http'
- ignored if domain includes schema

"""

import sys
import getopt
from libinithooks import inithooks_cache
import os
import pwd
from subprocess import run, Popen, PIPE

from libinithooks.dialog_wrapper import Dialog

DEFAULT_DOMAIN = "www.example.com"


def usage(s=None):
if s:
@@ -26,7 +29,6 @@ def usage(s=None):
print(__doc__, file=sys.stderr)
sys.exit(1)

DEFAULT_DOMAIN = "www.example.com"

def main():
try:
@@ -35,9 +37,10 @@ def main():
except getopt.GetoptError as e:
usage(e)

password = ""
email = ""
domain = ""
password = ""
schema = ""
for opt, val in opts:
if opt in ('-h', '--help'):
usage()
@@ -55,7 +58,7 @@ def main():
password = d.get_password(
"GitLab Password",
"Enter new password for the GitLab 'root' account.",
pass_req = 8)
pass_req=8)

if not email:
if 'd' not in locals():
@@ -81,21 +84,32 @@ def main():
domain = DEFAULT_DOMAIN

inithooks_cache.write('APP_DOMAIN', domain)

print("Reconfiguring GitLab. This might take a while.")
config = "/etc/gitlab/gitlab.rb"
domain = "http://%s" % domain
run(["sed", "-i", "/^external_url/ s|'.*|'%s'|" % domain, config])
run(["sed", "-i", "/^gitlab_rails\['gitlab_email_from'\]/ s|=.*|= '%s'|" % email, config])

if not domain.startswith('http'):
if schema:
if not schema.endswith("://"):
schema = f"{schema}://"
domain = f"{schema}{domain}"
else:
domain = f"http://{domain}"
run(["sed", "-i", f"/^external_url/ s|'.*|'{domain}'|", config])
run(["sed", "-i",
fr"/^gitlab_rails\['gitlab_email_from'\]/ s|=.*|= '{email}'|",
config])
run(["gitlab-ctl", "reconfigure"])

print("Setting GitLab 'root' user password. This might take a while.")
p1 = Popen(["echo", "-e", "{}\n{}\n".format(password, password)], stdout=PIPE)
p2 = Popen(["gitlab-rake", "gitlab:password:reset[root]"], stdin=p1.stdout, stdout=PIPE)
p1 = Popen(["echo", "-e", f"{password}\n{password}\n"], stdout=PIPE)
p2 = Popen(["gitlab-rake", "gitlab:password:reset[root]"],
stdin=p1.stdout, stdout=PIPE)
p1.stdout.close()
output = p2.communicate()[0]
print(output)
sys.exit(p2.returncode)


if __name__ == "__main__":
main()