From 48161614f16d87d01ee01d603899632e51584163 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Thu, 17 Oct 2024 19:22:22 +0200 Subject: [PATCH] Initial forgejo install --- api/gitapi.py | 4 +- gitlib/gitlib.py | 1 + installer/opengnsys_git_installer.py | 274 ++++++++++++++++++++++++++- 3 files changed, 275 insertions(+), 4 deletions(-) diff --git a/api/gitapi.py b/api/gitapi.py index 7f1372a..a0a53fd 100755 --- a/api/gitapi.py +++ b/api/gitapi.py @@ -240,7 +240,9 @@ class GitRepositories(Resource): installer = OpengnsysGitInstaller() - installer.init_git_repo(repo + ".git") + installer.add_forgejo_repo(repo) + + #installer.init_git_repo(repo + ".git") return jsonify({"status": "Repository created"}), 201 diff --git a/gitlib/gitlib.py b/gitlib/gitlib.py index c2b86c7..e4cae29 100755 --- a/gitlib/gitlib.py +++ b/gitlib/gitlib.py @@ -1628,6 +1628,7 @@ class OpengnsysGitLibrary: """ Restore the repository to the state it had before the non-committed modifications """ + self.logger.info("Undoing any user changes to the filesystem") repo = git.Repo(path) repo.head.reset(index=True, working_tree=True) diff --git a/installer/opengnsys_git_installer.py b/installer/opengnsys_git_installer.py index 993e563..f18e0e3 100755 --- a/installer/opengnsys_git_installer.py +++ b/installer/opengnsys_git_installer.py @@ -10,9 +10,21 @@ import subprocess import sys import pwd import grp -from termcolor import colored, cprint +from termcolor import cprint import git import libarchive +import urllib.request +import pathlib +import socket +import time +import requests + + +#FORGEJO_VERSION="8.0.3" +FORGEJO_VERSION="9.0.0" +FORGEJO_URL=f"https://codeberg.org/forgejo/forgejo/releases/download/v{FORGEJO_VERSION}/forgejo-{FORGEJO_VERSION}-linux-amd64" + + def show_error(*args): @@ -27,6 +39,7 @@ def show_error(*args): """ cprint(*args, "red", attrs = ["bold"], file=sys.stderr) + class RequirementException(Exception): """Excepción que indica que nos falta algún requisito @@ -102,11 +115,16 @@ class OpengnsysGitInstaller: self.git_basedir = "base.git" self.ssh_user = "opengnsys" self.ssh_group = "opengnsys" + self.email = "OpenGnsys@opengnsys.com" + self.forgejo_password = "opengnsys" + self.forgejo_port = 3000 + self.ssh_homedir = pwd.getpwnam(self.ssh_user).pw_dir self.ssh_uid = pwd.getpwnam(self.ssh_user).pw_uid self.ssh_gid = grp.getgrnam(self.ssh_group).gr_gid self.temp_dir = None + self.script_path = os.path.realpath(os.path.dirname(__file__)) # Possible names for SSH key self.key_paths = ["scripts/ssl/id_rsa.pub", "scripts/ssl/id_ed25519.pub", "scripts/ssl/id_ecdsa.pub", "scripts/ssl/id_ed25519_sk.pub", "scripts/ssl/id_ecdsa_sk.pub"] @@ -180,7 +198,7 @@ class OpengnsysGitInstaller: self.__logger.info("Configurando repositorio de GIT") repo.config_writer().set_value("user", "name", "OpenGnsys").release() - repo.config_writer().set_value("user", "email", "OpenGnsys@opengnsys.com").release() + repo.config_writer().set_value("user", "email", self.email).release() self._recursive_chown(repo_path, ouid=self.ssh_uid, ogid=self.ssh_gid) @@ -209,6 +227,71 @@ class OpengnsysGitInstaller: for filename in filenames: os.chown(os.path.join(dirpath, filename), uid=ouid, gid=ogid) + def _wait_for_port(self, host, port): + self.__logger.info("Waiting for %s:%i to be up", host, port) + + timeout = 60 + start_time = time.time() + + ready = False + while not ready and (time.time() - start_time) < 60: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.connect((host, port)) + ready = True + s.close() + except TimeoutError: + self.__logger.debug("Timed out, no connection yet.") + except OSError as oserr: + self.__logger.debug("%s, no connection yet. %.1f seconds left.", oserr.strerror, timeout - (time.time() - start_time)) + + time.sleep(0.1) + + if ready: + self.__logger.info("Connection established.") + else: + self.__logger.error("Timed out waiting for connection!") + raise TimeoutError("Timed out waiting for connection!") + + + def _extract_ssh_key(self): + public_key="" + + INITRD = "oginitrd.img" + + tftp_dir = os.path.join(self.base_path, "tftpboot") + default_num = self.oglive.get_default() + default_client = self.oglive.get_clients()[default_num] + client_initrd_path = os.path.join(tftp_dir, default_client, INITRD) + + #self.temp_dir = self._get_tempdir() + + if self.usesshkey: + with open(self.usesshkey, 'r') as f: + public_key = f.read().strip() + + else: + if os.path.isfile(client_initrd_path): + #os.makedirs(temp_dir, exist_ok=True) + #os.chdir(self.temp_dir.name) + self.__logger.debug("Descomprimiendo %s", client_initrd_path) + public_key = None + with libarchive.file_reader(client_initrd_path) as initrd: + for file in initrd: + #self.__logger.debug("Archivo: %s", file) + + if file.pathname in self.key_paths_dict: + data = bytearray() + for block in file.get_blocks(): + data = data + block + public_key = data.decode('utf-8').strip() + + break + else: + print(f"No se encuentra la imagen de initrd {client_initrd_path}") + exit(2) + + return public_key def install(self): """Instalar @@ -338,6 +421,182 @@ class OpengnsysGitInstaller: for DIR in ["base.git", "linux.git", "windows.git"]: #, "LinAcl", "WinAcl"]: self._recursive_chown(os.path.join(ogdir_images, DIR), ouid=self.ssh_uid, ogid=self.ssh_gid) + def _install_template(self, template, destination, keysvalues): + + self.__logger.info("Writing template %s into %s", template, destination) + + data = "" + with open(template, "r", encoding="utf-8") as template_file: + data = template_file.read() + + for key in keysvalues.keys(): + data = data.replace("{" + key + "}", keysvalues[key]) + + with open(destination, "w+", encoding="utf-8") as out_file: + out_file.write(data) + + def _runcmd(self, cmd): + self.__logger.debug("Running: %s", cmd) + + ret = subprocess.run(cmd, check=True,capture_output=True, encoding='utf-8') + return ret.stdout.strip() + + def install_forgejo(self): + self.__logger.info("Installing Forgejo") + + + + + bin_path = os.path.join(self.base_path, "bin", "forgejo") + conf_dir_path = os.path.join(self.base_path, "etc", "forgejo") + + + lfs_dir_path = os.path.join(self.base_path, "images", "lfs") + forgejo_work_dir_path = os.path.join(self.base_path, "var", "run", "forgejo") + forgejo_db_dir_path = os.path.join(self.base_path, "var", "run", "forgejo") + forgejo_db_path = os.path.join(forgejo_db_dir_path, "forgejo.db") + + forgejo_log_dir_path = os.path.join(self.base_path, "log", "forgejo") + + + conf_path = os.path.join(conf_dir_path, "app.ini") + + self.__logger.debug("Stopping opengnsys-forgejo service") + subprocess.run(["systemctl", "stop", "opengnsys-forgejo"], check=False) + + if not os.path.exists(bin_path): + self.__logger.debug("Downloading from %s into %s", FORGEJO_URL, bin_path) + urllib.request.urlretrieve(FORGEJO_URL, bin_path) + os.chmod(bin_path, 0o755) + + + + if os.path.exists(forgejo_db_path): + self.__logger.debug("Removing old configuration") + os.unlink(forgejo_db_path) + else: + self.__logger.debug("Old configuration not present, ok.") + + self.__logger.debug("Creating directories") + + pathlib.Path(conf_dir_path).mkdir(parents=True, exist_ok=True) + pathlib.Path(lfs_dir_path).mkdir(parents=True, exist_ok=True) + pathlib.Path(forgejo_work_dir_path).mkdir(parents=True, exist_ok=True) + pathlib.Path(forgejo_db_dir_path).mkdir(parents=True, exist_ok=True) + pathlib.Path(forgejo_log_dir_path).mkdir(parents=True, exist_ok=True) + + + os.chown(lfs_dir_path, self.ssh_uid, self.ssh_gid) + os.chown(forgejo_work_dir_path, self.ssh_uid, self.ssh_gid) + os.chown(forgejo_db_dir_path, self.ssh_uid, self.ssh_gid) + os.chown(forgejo_log_dir_path, self.ssh_uid, self.ssh_gid) + + data = { + "forgejo_user" : self.ssh_user, + "forgejo_group" : self.ssh_group, + "forgejo_port" : str(self.forgejo_port), + "forgejo_bin" : bin_path, + "forgejo_app_ini" : conf_path, + "forgejo_work_path" : forgejo_work_dir_path, + "forgejo_db_path" : forgejo_db_path, + "forgejo_repository_root" : os.path.join(self.base_path, "images"), + "forgejo_lfs_path" : lfs_dir_path, + "forgejo_log_path" : forgejo_log_dir_path, + "forgejo_hostname" : self._runcmd("hostname"), + "forgejo_lfs_jwt_secret" : self._runcmd([bin_path,"generate", "secret", "LFS_JWT_SECRET"]), + "forgejo_jwt_secret" : self._runcmd([bin_path,"generate", "secret", "JWT_SECRET"]), + "forgejo_internal_token" : self._runcmd([bin_path,"generate", "secret", "INTERNAL_TOKEN"]), + "forgejo_secret_key" : self._runcmd([bin_path,"generate", "secret", "SECRET_KEY"]) + } + + self._install_template(os.path.join(self.script_path, "forgejo-app.ini"), conf_path, data) + self._install_template(os.path.join(self.script_path, "forgejo.service"), "/etc/systemd/system/opengnsys-forgejo.service", data) + + + self.__logger.debug("Reloading systemd and starting service") + subprocess.run(["systemctl", "daemon-reload"], check=True) + subprocess.run(["systemctl", "enable", "opengnsys-forgejo"], check=True) + subprocess.run(["systemctl", "restart", "opengnsys-forgejo"], check=True) + + self.__logger.info("Waiting for forgejo to start") + self._wait_for_port("localhost", self.forgejo_port) + + + self.__logger.info("Configuring forgejo") + + def run_forge_cmd(args): + cmd = [bin_path, "--config", conf_path] + args + self.__logger.debug("Running command: %s", cmd) + + ret = subprocess.run(cmd, check=False, capture_output=True, encoding='utf-8', user=self.ssh_user) + if ret.returncode == 0: + return ret.stdout.strip() + else: + self.__logger.error("Failed to run command: %s, return code %i", cmd, ret.returncode) + self.__logger.error("stdout: %s", ret.stdout) + self.__logger.error("stderr: %s", ret.stderr) + raise RuntimeError("Failed to run necessary command") + + run_forge_cmd(["admin", "doctor", "check"]) + + run_forge_cmd(["admin", "user", "create", "--username", self.ssh_user, "--password", self.forgejo_password, "--email", self.email]) + + token = run_forge_cmd(["admin", "user", "generate-access-token", "--username", self.ssh_user, "-t", "gitapi", "--scopes", "all", "--raw"]) + + with open(os.path.join(self.base_path, "etc", "ogGitApiToken.cfg"), "w+", encoding='utf-8') as token_file: + token_file.write(token) + + + ssh_key = self._extract_ssh_key() + + self.add_forgejo_sshkey(ssh_key, "Default key") + + + def add_forgejo_repo(self, repository_name, description = ""): + token = "" + with open(os.path.join(self.base_path, "etc", "ogGitApiToken.cfg"), "r", encoding='utf-8') as token_file: + token = token_file.read().strip() + + self.__logger.info("Adding repository %s for Forgejo", repository_name) + + r = requests.post( + f"http://localhost:{self.forgejo_port}/api/v1/user/repos", + json={ + "auto_init" : False, + "default_branch" : "main", + "description" : description, + "name" : repository_name, + "private" : False + }, headers={ + 'Authorization' : f"token {token}" + }, + timeout = 60 + ) + + self.__logger.info("Request status was %i", r.status_code) + + def add_forgejo_sshkey(self, pubkey, description = ""): + token = "" + with open(os.path.join(self.base_path, "etc", "ogGitApiToken.cfg"), "r", encoding='utf-8') as token_file: + token = token_file.read().strip() + + self.__logger.info("Adding SSH key to Forgejo: %s", pubkey) + + r = requests.post( + f"http://localhost:{self.forgejo_port}/api/v1/user/keys", + json={ + "key" : pubkey, + "read_only" : False, + "title" : description + }, headers={ + 'Authorization' : f"token {token}" + }, + timeout = 60 + ) + + self.__logger.info("Request status was %i", r.status_code) + + if __name__ == '__main__': @@ -350,6 +609,9 @@ if __name__ == '__main__': prog="OpenGnsys Installer", description="Script para la instalación del repositorio git", ) + parser.add_argument('--forgejo-only', action='store_true', help="Solo instalar forgejo") + parser.add_argument('--forgejo-addrepos', action='store_true', help="Solo agregar repositorios forgejo") + parser.add_argument('--testmode', action='store_true', help="Modo de prueba") parser.add_argument('--ignoresshkey', action='store_true', help="Ignorar clave de SSH") parser.add_argument('--usesshkey', type=str, help="Usar clave SSH especificada") @@ -364,7 +626,13 @@ if __name__ == '__main__': logger.debug("Inicio de instalación") try: - installer.install() + if args.forgejo_only: + installer.install_forgejo() + elif args.forgejo_addrepos: + installer.add_forgejo_repo("linux") + else: + installer.install() + installer.install_forgejo() except RequirementException as req: show_error(f"Requisito para la instalación no satisfecho: {req.message}") exit(1)