Compare commits
21 Commits
main
...
ogreposito
|
@ -0,0 +1,8 @@
|
|||
__pycache__
|
||||
.venv
|
||||
venvog
|
||||
*.deb
|
||||
*.build
|
||||
*.dsc
|
||||
*.changes
|
||||
*.buildinfo
|
|
@ -44,6 +44,7 @@ Python documentation can be generated using a utility like pdoc3 (there are mult
|
|||
|
||||
The gitapi is designed to run within an existing opengnsys environment. It should be installed in an ogrepository.
|
||||
|
||||
|
||||
## API Examples
|
||||
|
||||
### Get list of branches
|
||||
|
|
245
api/gitapi.py
245
api/gitapi.py
|
@ -59,12 +59,25 @@ from flask_executor import Executor
|
|||
from flask_restx import Api, Resource, fields
|
||||
#from flasgger import Swagger
|
||||
import paramiko
|
||||
import logging
|
||||
import traceback
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
REPOSITORIES_BASE_PATH = "/opt/opengnsys/images"
|
||||
from systemd.journal import JournalHandler
|
||||
|
||||
debug_enabled = False
|
||||
|
||||
log = logging.getLogger('gitapi')
|
||||
log.addHandler(JournalHandler())
|
||||
log.setLevel(logging.INFO)
|
||||
log.info("Started")
|
||||
|
||||
|
||||
REPOSITORIES_BASE_PATH = "/opt/opengnsys/ogrepository/oggit/git/oggit/"
|
||||
|
||||
start_time = time.time()
|
||||
tasks = {}
|
||||
|
||||
tasks_max = 1024
|
||||
|
||||
# Create an instance of the Flask class
|
||||
app = Flask(__name__)
|
||||
|
@ -80,6 +93,24 @@ executor = Executor(app)
|
|||
|
||||
|
||||
|
||||
def add_task(future):
|
||||
task_id = uuid.uuid4().hex
|
||||
task_data = {
|
||||
"future" : future,
|
||||
"start_time" : time.time()
|
||||
}
|
||||
|
||||
while len(tasks) >= tasks_max:
|
||||
oldest_task_id = min(tasks, key=lambda k: tasks[k]['start_time'])
|
||||
task = tasks[task_id]["future"]
|
||||
if task.running():
|
||||
log.error("Cancelling still running task %s, maximum task limit of %i reached", task_id, tasks_max)
|
||||
task.cancel()
|
||||
|
||||
del tasks[oldest_task_id]
|
||||
|
||||
tasks[task_id] = task_data
|
||||
return task_id
|
||||
|
||||
def do_repo_backup(repo, params):
|
||||
"""
|
||||
|
@ -97,7 +128,10 @@ def do_repo_backup(repo, params):
|
|||
bool: True if the backup was successful.
|
||||
"""
|
||||
|
||||
gitrepo = git.Repo(f"{REPOSITORIES_BASE_PATH}/{repo}.git")
|
||||
git_repo_path = f"{REPOSITORIES_BASE_PATH}/{repo}.git"
|
||||
git_repo = git.Repo(git_repo_path)
|
||||
git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path)
|
||||
|
||||
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
@ -107,7 +141,7 @@ def do_repo_backup(repo, params):
|
|||
|
||||
|
||||
with sftp.file(params["filename"], mode='wb+') as remote_file:
|
||||
gitrepo.archive(remote_file, format="tar.gz")
|
||||
git_repo.archive(remote_file, format="tar.gz")
|
||||
|
||||
|
||||
return True
|
||||
|
@ -126,13 +160,16 @@ def do_repo_sync(repo, params):
|
|||
- "remote_ref" (str): The name of the remote reference.
|
||||
- "summary" (str): A summary of the push operation for the reference.
|
||||
"""
|
||||
gitrepo = git.Repo(f"{REPOSITORIES_BASE_PATH}/{repo}.git")
|
||||
git_repo_path = f"{REPOSITORIES_BASE_PATH}/{repo}.git"
|
||||
git_repo = git.Repo(git_repo_path)
|
||||
git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path)
|
||||
|
||||
|
||||
# Recreate the remote every time, it might change
|
||||
if "backup" in gitrepo.remotes:
|
||||
gitrepo.delete_remote("backup")
|
||||
if "backup" in git_repo.remotes:
|
||||
git_repo.delete_remote("backup")
|
||||
|
||||
backup_repo = gitrepo.create_remote("backup", params["remote_repository"])
|
||||
backup_repo = git_repo.create_remote("backup", params["remote_repository"])
|
||||
pushed_references = backup_repo.push("*:*")
|
||||
results = []
|
||||
|
||||
|
@ -152,10 +189,42 @@ def do_repo_gc(repo):
|
|||
Returns:
|
||||
bool: True if the garbage collection command was executed successfully.
|
||||
"""
|
||||
gitrepo = git.Repo(f"{REPOSITORIES_BASE_PATH}/{repo}.git")
|
||||
git_repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
|
||||
git_repo = git.Repo(git_repo_path)
|
||||
git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path)
|
||||
|
||||
gitrepo.git.gc()
|
||||
return True
|
||||
git_repo.git.gc()
|
||||
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_exception(e):
|
||||
"""Return JSON for HTTP errors.
|
||||
|
||||
We create and log an error UUID for each error, and use journald's additional fields for easier searching.
|
||||
"""
|
||||
# start with the correct headers and status code from the error
|
||||
response = e.get_response()
|
||||
|
||||
errid = uuid.uuid4().hex
|
||||
|
||||
|
||||
if debug_enabled:
|
||||
response = {
|
||||
"errcode": e.code,
|
||||
"errname": e.name,
|
||||
"description": e.description,
|
||||
}
|
||||
else:
|
||||
response = {
|
||||
"errcode" : 500,
|
||||
"errname" : "Internal error",
|
||||
"description": f"Please see the log for error {errid}",
|
||||
"error_id" : errid
|
||||
}
|
||||
|
||||
log.error("Error ID %s: code %i, name %s, description %s", errid, e.code, e.name, e.description, extra = { "error_id" : errid, "errcode" : e.code, "errname" : e.name, "description" : e.description })
|
||||
|
||||
# response.content_type = "application/json"
|
||||
return response
|
||||
|
||||
|
||||
# Define a route for the root URL
|
||||
|
@ -170,11 +239,13 @@ class GitLib(Resource):
|
|||
Returns:
|
||||
Response: A Flask JSON response containing a welcome message.
|
||||
"""
|
||||
log.info("Root URL accessed")
|
||||
|
||||
return {
|
||||
"message": "OpenGnsys Git API"
|
||||
}
|
||||
|
||||
@git_ns.route('/oggit/v1/repositories')
|
||||
@git_ns.route('/repositories')
|
||||
class GitRepositories(Resource):
|
||||
def get(self):
|
||||
"""
|
||||
|
@ -197,7 +268,8 @@ class GitRepositories(Resource):
|
|||
"""
|
||||
|
||||
if not os.path.isdir(REPOSITORIES_BASE_PATH):
|
||||
return jsonify({"error": "Repository storage not found, git functionality may not be installed."}), 500
|
||||
log.error("Can't list repositories. Repository storage at %s not found", REPOSITORIES_BASE_PATH, extra = {"path" : REPOSITORIES_BASE_PATH})
|
||||
return {"error": "Repository storage not found, git functionality may not be installed."}, 500
|
||||
|
||||
repos = []
|
||||
for entry in os.scandir(REPOSITORIES_BASE_PATH):
|
||||
|
@ -208,9 +280,10 @@ class GitRepositories(Resource):
|
|||
|
||||
repos = repos + [name]
|
||||
|
||||
return jsonify({
|
||||
log.info("Returning %i repositories", len(repos))
|
||||
return {
|
||||
"repositories": repos
|
||||
})
|
||||
}
|
||||
|
||||
def post(self):
|
||||
"""
|
||||
|
@ -230,13 +303,15 @@ class GitRepositories(Resource):
|
|||
data = request.json
|
||||
|
||||
if data is None:
|
||||
return jsonify({"error" : "Parameters missing"}), 400
|
||||
log.error("Can't create repository, JSON post data missing")
|
||||
return {"error" : "Parameters missing"}, 400
|
||||
|
||||
repo = data["name"]
|
||||
|
||||
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
|
||||
if os.path.isdir(repo_path):
|
||||
return jsonify({"status": "Repository already exists"}), 200
|
||||
log.error("Can't create repository %s, already exists at %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path})
|
||||
return {"status": "Repository already exists"}, 200
|
||||
|
||||
|
||||
installer = OpengnsysGitInstaller()
|
||||
|
@ -244,11 +319,11 @@ class GitRepositories(Resource):
|
|||
|
||||
#installer.init_git_repo(repo + ".git")
|
||||
|
||||
|
||||
return jsonify({"status": "Repository created"}), 201
|
||||
log.info("Repository %s created", repo, extra = {"repository" : repo})
|
||||
return {"status": "Repository created"}, 201
|
||||
|
||||
|
||||
@git_ns.route('/oggit/v1/repositories/<repo>/sync')
|
||||
@git_ns.route('/repositories/<repo>/sync')
|
||||
class GitRepoSync(Resource):
|
||||
def post(self, repo):
|
||||
"""
|
||||
|
@ -268,22 +343,30 @@ class GitRepoSync(Resource):
|
|||
"""
|
||||
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
|
||||
if not os.path.isdir(repo_path):
|
||||
return jsonify({"error": "Repository not found"}), 404
|
||||
log.error("Can't sync repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path })
|
||||
return {"error": "Repository not found"}, 404
|
||||
|
||||
|
||||
data = request.json
|
||||
|
||||
if data is None:
|
||||
return jsonify({"error" : "Parameters missing"}), 400
|
||||
log.error("Can't create repository, JSON post data missing")
|
||||
return {"error" : "Parameters missing"}, 400
|
||||
|
||||
if not "remote_repository" in data:
|
||||
log.error("Can't create repository, parameter 'remote_repository' missing")
|
||||
return {"error" : "Parameter 'remote_repository' missing"}, 400
|
||||
|
||||
|
||||
future = executor.submit(do_repo_sync, repo, data)
|
||||
task_id = str(uuid.uuid4())
|
||||
tasks[task_id] = future
|
||||
return jsonify({"status": "started", "task_id" : task_id}), 200
|
||||
task_id = add_task(future)
|
||||
|
||||
log.info("Starting synchronization of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id})
|
||||
return {"status": "started", "task_id" : task_id}, 200
|
||||
|
||||
|
||||
|
||||
@git_ns.route('/oggit/v1/repositories/<repo>/backup')
|
||||
@git_ns.route('/repositories/<repo>/backup')
|
||||
class GitRepoBackup(Resource):
|
||||
def backup_repository(self, repo):
|
||||
"""
|
||||
|
@ -310,12 +393,14 @@ class GitRepoBackup(Resource):
|
|||
"""
|
||||
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
|
||||
if not os.path.isdir(repo_path):
|
||||
return jsonify({"error": "Repository not found"}), 404
|
||||
log.error("Can't backup repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path })
|
||||
return {"error": "Repository not found"}, 404
|
||||
|
||||
|
||||
data = request.json
|
||||
if data is None:
|
||||
return jsonify({"error" : "Parameters missing"}), 400
|
||||
log.error("Can't create repository, JSON post data missing")
|
||||
return {"error" : "Parameters missing"}, 400
|
||||
|
||||
|
||||
if not "ssh_port" in data:
|
||||
|
@ -323,12 +408,12 @@ class GitRepoBackup(Resource):
|
|||
|
||||
|
||||
future = executor.submit(do_repo_backup, repo, data)
|
||||
task_id = str(uuid.uuid4())
|
||||
tasks[task_id] = future
|
||||
task_id = add_task(future)
|
||||
|
||||
return jsonify({"status": "started", "task_id" : task_id}), 200
|
||||
log.info("Starting backup of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id})
|
||||
return {"status": "started", "task_id" : task_id}, 200
|
||||
|
||||
@git_ns.route('/oggit/v1/repositories/<repo>/compact', methods=['POST'])
|
||||
@git_ns.route('/repositories/<repo>/compact', methods=['POST'])
|
||||
class GitRepoCompact(Resource):
|
||||
def post(self, repo):
|
||||
"""
|
||||
|
@ -348,16 +433,17 @@ class GitRepoCompact(Resource):
|
|||
"""
|
||||
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
|
||||
if not os.path.isdir(repo_path):
|
||||
return jsonify({"error": "Repository not found"}), 404
|
||||
log.error("Can't compact repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path })
|
||||
return {"error": "Repository not found"}, 404
|
||||
|
||||
future = executor.submit(do_repo_gc, repo)
|
||||
task_id = str(uuid.uuid4())
|
||||
tasks[task_id] = future
|
||||
task_id = add_task(future)
|
||||
|
||||
return jsonify({"status": "started", "task_id" : task_id}), 200
|
||||
log.info("Starting compaction of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id})
|
||||
return {"status": "started", "task_id" : task_id}, 200
|
||||
|
||||
|
||||
@git_ns.route('/oggit/v1/tasks/<task_id>/status')
|
||||
@git_ns.route('/tasks/<task_id>/status')
|
||||
class GitTaskStatus(Resource):
|
||||
def get(self, task_id):
|
||||
"""
|
||||
|
@ -373,19 +459,28 @@ class GitTaskStatus(Resource):
|
|||
- If the task is still in progress, returns a 202 status indicating the task is in progress.
|
||||
"""
|
||||
if not task_id in tasks:
|
||||
return jsonify({"error": "Task not found"}), 404
|
||||
log.error("Task %s was not found", task_id, extra = {"task_id" : task_id})
|
||||
return {"error": "Task not found"}, 404
|
||||
|
||||
future = tasks[task_id]
|
||||
future = tasks[task_id]["future"]
|
||||
|
||||
if future.done():
|
||||
result = future.result()
|
||||
return jsonify({"status" : "completed", "result" : result}), 200
|
||||
else:
|
||||
return jsonify({"status" : "in progress"}), 202
|
||||
try:
|
||||
if future.done():
|
||||
result = future.result()
|
||||
log.info("Returning completion of task %s", task_id, extra = {"task_id" : task_id})
|
||||
return {"status" : "completed", "result" : result}, 200
|
||||
else:
|
||||
log.info("Task %s is still in progress", task_id, extra = {"task_id" : task_id})
|
||||
return {"status" : "in progress"}, 202
|
||||
except Exception as e:
|
||||
errid = uuid.uuid4().hex
|
||||
|
||||
|
||||
log.error("Task %s failed with exception %s, UUID %s", task_id, traceback.format_exception(e), errid, extra = {"task_id" : task_id, "exception" : traceback.format_exception(e), "error_id" : errid})
|
||||
return {"status" : "internal error", "error_id" : errid }, 500
|
||||
|
||||
@git_ns.route('/oggit/v1/repositories/<repo>', methods=['DELETE'])
|
||||
|
||||
@git_ns.route('/repositories/<repo>', methods=['DELETE'])
|
||||
class GitRepo(Resource):
|
||||
def delete(self, repo):
|
||||
"""
|
||||
|
@ -405,16 +500,18 @@ class GitRepo(Resource):
|
|||
"""
|
||||
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
|
||||
if not os.path.isdir(repo_path):
|
||||
return jsonify({"error": "Repository not found"}), 404
|
||||
log.error("Can't delete repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path })
|
||||
return {"error": "Repository not found"}, 404
|
||||
|
||||
|
||||
shutil.rmtree(repo_path)
|
||||
return jsonify({"status": "Repository deleted"}), 200
|
||||
log.info("Deleted repository %s", repo, extra = {"repository" : repo})
|
||||
return {"status": "Repository deleted"}, 200
|
||||
|
||||
|
||||
|
||||
|
||||
@git_ns.route('/oggit/v1/repositories/<repo>/branches')
|
||||
@git_ns.route('/repositories/<repo>/branches')
|
||||
class GitRepoBranches(Resource):
|
||||
def get(self, repo):
|
||||
"""
|
||||
|
@ -430,20 +527,52 @@ class GitRepoBranches(Resource):
|
|||
"""
|
||||
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
|
||||
if not os.path.isdir(repo_path):
|
||||
return jsonify({"error": "Repository not found"}), 404
|
||||
log.error("Can't get branches of repository repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path })
|
||||
return {"error": "Repository not found"}, 404
|
||||
|
||||
git_repo = git.Repo(repo_path)
|
||||
git_repo.git.config('--global', '--add', 'safe.directory', repo_path)
|
||||
|
||||
|
||||
branches = []
|
||||
for branch in git_repo.branches:
|
||||
branches = branches + [branch.name]
|
||||
|
||||
|
||||
return jsonify({
|
||||
log.info("Returning %i branches", len(branches))
|
||||
return {
|
||||
"branches": branches
|
||||
})
|
||||
}
|
||||
|
||||
@git_ns.route('/repositories/<repo>/branches/<branch>')
|
||||
class GitRepoBranchesDeleter(Resource):
|
||||
def delete(self, repo, branch):
|
||||
"""Delete a given branch in a given repository
|
||||
|
||||
Args:
|
||||
repo (str): The name of the repository.
|
||||
|
||||
Returns:
|
||||
Response: A JSON response containing a list of branch names or an error message if the repository is not found.
|
||||
- 200: A JSON object with a "status" key containing "deleted"
|
||||
- 404: A JSON object with an "error" key containing the message "Repository not found" or "Branch not found"
|
||||
"""
|
||||
|
||||
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
|
||||
if not os.path.isdir(repo_path):
|
||||
log.error("Can't get branches of repository repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path })
|
||||
return {"error": "Repository not found"}, 404
|
||||
|
||||
git_repo = git.Repo(repo_path)
|
||||
git_repo.git.config('--global', '--add', 'safe.directory', repo_path)
|
||||
|
||||
|
||||
if not branch in git_repo.branches:
|
||||
log.error("Can't delete branch %s, not found in repository %s", branch, repo, extra = {"repository" : repo, "branch" : branch})
|
||||
return {"error": "Branch not found"}, 404
|
||||
|
||||
git_repo.delete_head(branch)
|
||||
log.info("Branch %s of repository %s deleted", branch, repo, extra = {"repository" : repo, "branch" : branch})
|
||||
return {"status": "deleted"}, 200
|
||||
|
||||
|
||||
@git_ns.route('/health')
|
||||
|
@ -460,6 +589,7 @@ class GitHealth(Resource):
|
|||
active and functional.
|
||||
|
||||
"""
|
||||
log.info("Health check endpoint called")
|
||||
return {
|
||||
"status": "OK"
|
||||
}
|
||||
|
@ -476,11 +606,22 @@ class GitStatus(Resource):
|
|||
Response: A JSON response with status information
|
||||
|
||||
"""
|
||||
log.info("Status endpoint called")
|
||||
|
||||
return {
|
||||
"uptime" : time.time() - start_time,
|
||||
"active_tasks" : len(tasks)
|
||||
}
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
log.info("Request from %s: %s %s %s %s", request.remote_addr, request.method, request.scheme, request.full_path, response.status,
|
||||
extra = {"remote_addr" : request.remote_addr, "method" : request.method, "scheme" : request.scheme, "full_path" : request.full_path, "status" : response.status})
|
||||
|
||||
if debug_enabled:
|
||||
log.debug("Response: %s", response.data, extra = {"response" : response.data})
|
||||
|
||||
return response
|
||||
|
||||
api.add_namespace(git_ns)
|
||||
|
||||
|
|
|
@ -1,32 +1,59 @@
|
|||
# Installing Dependencies for Python
|
||||
# Git component installer
|
||||
|
||||
Converting the code to Python 3 currently requires the packages specified in `requirements.txt`.
|
||||
This directory contains the installer for the git component for OpenGnsys.
|
||||
|
||||
To install Python dependencies, the `venv` module (https://docs.python.org/3/library/venv.html) is used, which installs all dependencies in an isolated environment separate from the system.
|
||||
It downloads, installs and configures Forgejo, creates the default repositories and configures SSH keys.
|
||||
|
||||
# Quick Installation
|
||||
|
||||
## Ubuntu 24.04
|
||||
|
||||
sudo apt install python3-git opengnsys-libarchive-c python3-termcolor bsdextrautils
|
||||
### Add the repository
|
||||
|
||||
## Add SSH Keys to oglive
|
||||
|
||||
The Git system accesses the ogrepository via SSH. To work, it needs the oglive to have an SSH key, and the ogrepository must accept it.
|
||||
Create the file `/etc/apt/sources.list.d/opengnsys.sources` with these contents:
|
||||
|
||||
The Git installer can make the required changes with:
|
||||
Types: deb
|
||||
URIs: https://ognproject.evlt.uma.es/debian-opengnsys/
|
||||
Suites: noble
|
||||
Components: main
|
||||
Signed-By:
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
.
|
||||
mDMEZzx/SxYJKwYBBAHaRw8BAQdAa83CuAJ5/+7Pn9LHT/k34EAGpx5FnT/ExHSj
|
||||
XZG1JES0Ik9wZW5HbnN5cyA8b3Blbmduc3lzQG9wZW5nbnN5cy5lcz6ImQQTFgoA
|
||||
QRYhBC+J38Xsso227ZbDVt2S5xJQRhKDBQJnPH9LAhsDBQkFo5qABQsJCAcCAiIC
|
||||
BhUKCQgLAgQWAgMBAh4HAheAAAoJEN2S5xJQRhKDW/MBAO6swnpwdrbm48ypMyPh
|
||||
NboxvF7rCqBqHWwRHvkvrq7pAP9zd98r7z2AvqVXZxnaCsLTUNMEL12+DVZAUZ1G
|
||||
EquRBbg4BGc8f0sSCisGAQQBl1UBBQEBB0B6D6tkrwXSHi7ebGYsiMPntqwdkQ/S
|
||||
84SFTlSxRqdXfgMBCAeIfgQYFgoAJhYhBC+J38Xsso227ZbDVt2S5xJQRhKDBQJn
|
||||
PH9LAhsMBQkFo5qAAAoJEN2S5xJQRhKDJ+cBAM9jYbeq5VXkHLfODeVztgSXnSUe
|
||||
yklJ18oQmpeK5eWeAQDKYk/P0R+1ZJDItxkeP6pw62bCDYGQDvdDGPMAaIT6CA==
|
||||
=xcNc
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
./opengnsys_git_installer.py --set-ssh-key
|
||||
It's required to run `apt update` after creating this file
|
||||
|
||||
Or to do it for a specific oglive:
|
||||
### Install packages
|
||||
|
||||
./opengnsys_git_installer.py --set-ssh-key --oglive 1 # oglive number
|
||||
sudo apt install -y python3-git opengnsys-libarchive-c python3-termcolor python3-requests python3-tqdm bsdextrautils
|
||||
|
||||
Running this command automatically adds the SSH key to Forgejo.
|
||||
## Adding SSH Keys to oglive
|
||||
|
||||
The existing key can be extracted with:
|
||||
The Git system accesses the ogrepository via SSH. To function, it needs the oglive to have an SSH key, and for the ogrepository to accept it.
|
||||
|
||||
The Git installer can make the required changes by extracting an SSH key from an oglive and installing it in Forgejo. If there is a local ogboot installation, the installer will do this automatically. If there is not, it is necessary to provide the installer with an oglive from which to extract the key using the `--oglive-file` or `--oglive-url` parameter.
|
||||
|
||||
For example:
|
||||
|
||||
./opengnsys_git_installer.py --oglive-url https://example.com/ogLive-noble.iso
|
||||
|
||||
The installer will proceed to download the file, mount the ISO, and extract the key.
|
||||
|
||||
To perform the process after completing the installation and only add a key to an existing installation, use the `--set-ssh-key` parameter:
|
||||
|
||||
./opengnsys_git_installer.py --set-ssh-key --oglive-url https://example.com/ogLive-noble.iso
|
||||
|
||||
./opengnsys_git_installer.py --extract-ssh-key --quiet
|
||||
|
||||
# Running the Installer
|
||||
|
||||
|
|
|
@ -1,35 +1,58 @@
|
|||
# Instalación de dependencias para python
|
||||
# Instalador de componente Git
|
||||
|
||||
La conversion del código a Python 3 requiere actualmente los paquetes especificados en `requirements.txt`
|
||||
|
||||
Para instalar dependencias de python se usa el modulo venv (https://docs.python.org/3/library/venv.html) que instala todas las dependencias en un entorno independiente del sistema.
|
||||
Este directorio contiene el instalador de Git para OpenGnsys.
|
||||
|
||||
Descarga, instala y configura Forgejo, crea los repositorios por defecto, y configura claves de SSH.
|
||||
|
||||
# Instalación rápida
|
||||
|
||||
## Ubuntu 24.04
|
||||
|
||||
sudo apt install python3-git opengnsys-libarchive-c python3-termcolor bsdextrautils
|
||||
### Agregar repositorio
|
||||
|
||||
Crear el archivo `/etc/apt/sources.list.d/opengnsys.sources` con este contenido:
|
||||
|
||||
Types: deb
|
||||
URIs: https://ognproject.evlt.uma.es/debian-opengnsys/
|
||||
Suites: noble
|
||||
Components: main
|
||||
Signed-By:
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
.
|
||||
mDMEZzx/SxYJKwYBBAHaRw8BAQdAa83CuAJ5/+7Pn9LHT/k34EAGpx5FnT/ExHSj
|
||||
XZG1JES0Ik9wZW5HbnN5cyA8b3Blbmduc3lzQG9wZW5nbnN5cy5lcz6ImQQTFgoA
|
||||
QRYhBC+J38Xsso227ZbDVt2S5xJQRhKDBQJnPH9LAhsDBQkFo5qABQsJCAcCAiIC
|
||||
BhUKCQgLAgQWAgMBAh4HAheAAAoJEN2S5xJQRhKDW/MBAO6swnpwdrbm48ypMyPh
|
||||
NboxvF7rCqBqHWwRHvkvrq7pAP9zd98r7z2AvqVXZxnaCsLTUNMEL12+DVZAUZ1G
|
||||
EquRBbg4BGc8f0sSCisGAQQBl1UBBQEBB0B6D6tkrwXSHi7ebGYsiMPntqwdkQ/S
|
||||
84SFTlSxRqdXfgMBCAeIfgQYFgoAJhYhBC+J38Xsso227ZbDVt2S5xJQRhKDBQJn
|
||||
PH9LAhsMBQkFo5qAAAoJEN2S5xJQRhKDJ+cBAM9jYbeq5VXkHLfODeVztgSXnSUe
|
||||
yklJ18oQmpeK5eWeAQDKYk/P0R+1ZJDItxkeP6pw62bCDYGQDvdDGPMAaIT6CA==
|
||||
=xcNc
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
Es necesario ejecutar `apt update` después de crear el archivo.
|
||||
|
||||
### Instalar paquetes:
|
||||
|
||||
sudo apt install -y python3-git opengnsys-libarchive-c python3-termcolor python3-requests python3-tqdm bsdextrautils
|
||||
|
||||
|
||||
## Agregar claves de SSH a oglive
|
||||
|
||||
El sistema de Git accede al ogrepository por SSH. Para funcionar, necesita que el oglive tenga una clave de SSH, y que el ogrepository la acepte.
|
||||
|
||||
El instalador de Git puede realizar los cambios requeridos, con:
|
||||
El instalador de Git puede realizar los cambios requeridos, extrayendo una clave de SSH de un oglive e instalándola en Forgejo. Si hay una instalación de ogboot local, el instalador lo hará automáticamente. Si no la hay, es necesario darle al instalador un oglive del que extraer la clave con el parámetro `--oglive-file` o `--oglive-url`.
|
||||
|
||||
./opengnsys_git_installer.py --set-ssh-key
|
||||
Por ejemplo:
|
||||
|
||||
O para hacerlo contra un oglive especifico:
|
||||
./opengnsys_git_installer.py --oglive-url https://example.com/ogLive-noble.iso
|
||||
|
||||
./opengnsys_git_installer.py --set-ssh-key --oglive 1 # numero de oglive
|
||||
El instalador procederá a descargar el archivo, montar el ISO, y extraer la clave.
|
||||
|
||||
Ejecutar este comando agrega la clave de SSH a Forgejo automáticamente.
|
||||
Para hacer el proceso después de haber completado la instalación y solo agregar una clave a una instalación existente, usar el parámetro `--set-ssh-key`:
|
||||
|
||||
|
||||
La clave existente puede extraerse con:
|
||||
|
||||
./opengnsys_git_installer.py --extract-ssh-key --quiet
|
||||
./opengnsys_git_installer.py --set-ssh-key --oglive-url https://example.com/ogLive-noble.iso
|
||||
|
||||
# Ejecutar
|
||||
|
||||
|
@ -49,6 +72,8 @@ El usuario por defecto es `oggit` con password `opengnsys`.
|
|||
|
||||
El sistema OgGit requiere módulos de Python que no vienen en Ubuntu 24.04 o tienen versiones demasiado antiguas.
|
||||
|
||||
Los paquetes se pueden obtener desde el repositorio de OpenGnsys (ver arriba).
|
||||
|
||||
Los fuentes de los paquetes se encuentran en oggit/packages.
|
||||
|
||||
# Documentación de código fuente
|
||||
|
|
|
@ -28,13 +28,28 @@ import requests
|
|||
import tempfile
|
||||
import hashlib
|
||||
import datetime
|
||||
import tqdm
|
||||
|
||||
#FORGEJO_VERSION="8.0.3"
|
||||
FORGEJO_VERSION="9.0.0"
|
||||
FORGEJO_VERSION="9.0.3"
|
||||
FORGEJO_URL=f"https://codeberg.org/forgejo/forgejo/releases/download/v{FORGEJO_VERSION}/forgejo-{FORGEJO_VERSION}-linux-amd64"
|
||||
|
||||
|
||||
|
||||
def download_with_progress(url, output_file):
|
||||
|
||||
with requests.get(url, stream=True, timeout=60) as req:
|
||||
progress = tqdm.tqdm()
|
||||
progress.total = int(req.headers["Content-Length"])
|
||||
progress.unit_scale = True
|
||||
progress.desc = "Downloading"
|
||||
|
||||
for chunk in req.iter_content(chunk_size=8192):
|
||||
output_file.write(chunk)
|
||||
progress.n = progress.n + len(chunk)
|
||||
progress.refresh()
|
||||
|
||||
progress.close()
|
||||
|
||||
def show_error(*args):
|
||||
"""
|
||||
|
@ -65,6 +80,23 @@ class RequirementException(Exception):
|
|||
super().__init__(message)
|
||||
self.message = message
|
||||
|
||||
|
||||
class OptionalDependencyException(Exception):
|
||||
"""Excepción que indica que nos falta algún requisito opcional
|
||||
|
||||
Attributes:
|
||||
message (str): Mensaje de error mostrado al usuario
|
||||
"""
|
||||
|
||||
def __init__(self, message):
|
||||
"""Inicializar OptionalDependencyException.
|
||||
|
||||
Args:
|
||||
message (str): Mensaje de error mostrado al usuario
|
||||
"""
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
|
||||
class FakeTemporaryDirectory:
|
||||
"""Imitación de TemporaryDirectory para depuración"""
|
||||
def __init__(self, dirname):
|
||||
|
@ -74,6 +106,57 @@ class FakeTemporaryDirectory:
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class OgliveMounter:
|
||||
"""
|
||||
A class to handle mounting of Oglive images from a given URL or local file.
|
||||
|
||||
Attributes:
|
||||
logger (logging.Logger): Logger instance for logging messages.
|
||||
squashfs (str): Path to the squashfs file within the mounted Oglive image.
|
||||
initrd (str): Path to the initrd image within the mounted Oglive image.
|
||||
kernel (str): Path to the kernel image within the mounted Oglive image.
|
||||
|
||||
Methods:
|
||||
__init__(url):
|
||||
Initializes the OgliveMounter instance, downloads the Oglive image if URL is provided,
|
||||
and mounts the image to a temporary directory.
|
||||
|
||||
__del__():
|
||||
Unmounts the mounted directory and cleans up resources.
|
||||
"""
|
||||
def __init__(self, url):
|
||||
self.logger = logging.getLogger("OgliveMounter")
|
||||
self.mountdir = tempfile.TemporaryDirectory()
|
||||
|
||||
self.logger.info("Will mount oglive found at %s", url)
|
||||
|
||||
if url.startswith("http://") or url.startswith("https://"):
|
||||
self.logger.debug("We got an URL, downloading %s", url)
|
||||
self.tempfile = tempfile.NamedTemporaryFile(mode='wb')
|
||||
filename = self.tempfile.name
|
||||
|
||||
download_with_progress(url, self.tempfile)
|
||||
else:
|
||||
self.logger.debug("We got a filename")
|
||||
filename = url
|
||||
|
||||
self.logger.debug("Mounting %s at %s", filename, self.mountdir.name)
|
||||
subprocess.run(["/usr/bin/mount", filename, self.mountdir.name], check=True)
|
||||
|
||||
self.squashfs = os.path.join(self.mountdir.name, "ogclient", "ogclient.sqfs")
|
||||
self.initrd = os.path.join(self.mountdir.name, "ogclient", "oginitrd.img")
|
||||
self.kernel = os.path.join(self.mountdir.name, "ogclient", "ogvmlinuz")
|
||||
|
||||
|
||||
def __del__(self):
|
||||
self.logger.debug("Unmounting directory %s", self.mountdir.name)
|
||||
subprocess.run(["/usr/bin/umount", self.mountdir.name], check=True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Oglive:
|
||||
"""Interfaz a utilidad oglivecli
|
||||
|
||||
|
@ -88,6 +171,10 @@ class Oglive:
|
|||
|
||||
def _cmd(self, args):
|
||||
cmd = [self.binary] + args
|
||||
|
||||
if not os.path.exists(self.binary):
|
||||
raise OptionalDependencyException("Missing oglivecli command. Please use --squashfs-file (see README.md for more details)")
|
||||
|
||||
self.__logger.debug("comando: %s", cmd)
|
||||
|
||||
proc = subprocess.run(cmd, shell=False, check=True, capture_output=True)
|
||||
|
@ -122,6 +209,7 @@ class OpengnsysGitInstaller:
|
|||
self.__logger.debug("Inicializando")
|
||||
self.testmode = False
|
||||
self.base_path = "/opt/opengnsys"
|
||||
self.ogrepository_base_path = os.path.join(self.base_path, "ogrepository")
|
||||
self.git_basedir = "base.git"
|
||||
self.email = "OpenGnsys@opengnsys.com"
|
||||
|
||||
|
@ -147,6 +235,7 @@ class OpengnsysGitInstaller:
|
|||
for kp in self.key_paths:
|
||||
self.key_paths_dict[kp] = 1
|
||||
|
||||
os.environ["PATH"] += os.pathsep + os.path.join(self.base_path, "bin")
|
||||
|
||||
self.oglive = Oglive()
|
||||
|
||||
|
@ -159,10 +248,6 @@ class OpengnsysGitInstaller:
|
|||
"""Ignorar requisito de clave de ssh para el instalador"""
|
||||
self.ignoresshkey = value
|
||||
|
||||
def set_usesshkey(self, value):
|
||||
"""Usar clave de ssh especificada"""
|
||||
self.usesshkey = value
|
||||
|
||||
def set_basepath(self, value):
|
||||
"""Establece ruta base de OpenGnsys
|
||||
Valor por defecto: /opt/opengnsys
|
||||
|
@ -218,7 +303,7 @@ class OpengnsysGitInstaller:
|
|||
def init_git_repo(self, reponame):
|
||||
"""Inicializa un repositorio Git"""
|
||||
# Creamos repositorio
|
||||
ogdir_images = os.path.join(self.base_path, "images")
|
||||
ogdir_images = os.path.join(self.ogrepository_base_path, "oggit")
|
||||
self.__logger.info("Creando repositorio de GIT %s", reponame)
|
||||
|
||||
os.makedirs(os.path.join(ogdir_images, self.git_basedir), exist_ok=True)
|
||||
|
@ -294,42 +379,58 @@ class OpengnsysGitInstaller:
|
|||
raise TimeoutError("Timed out waiting for connection!")
|
||||
|
||||
|
||||
def add_ssh_key_from_squashfs(self, oglive_num = None):
|
||||
def add_ssh_key_from_squashfs(self, oglive_num = None, squashfs_file = None, oglive_file = None):
|
||||
|
||||
name = "(unknown)"
|
||||
mounter = None
|
||||
|
||||
if not oglive_file is None:
|
||||
mounter = OgliveMounter(oglive_file)
|
||||
squashfs_file = mounter.squashfs
|
||||
|
||||
if squashfs_file is None:
|
||||
if oglive_num is None:
|
||||
self.__logger.info("Using default oglive")
|
||||
oglive_num = self.oglive.get_default()
|
||||
else:
|
||||
self.__logger.info("Using oglive %i", oglive_num)
|
||||
|
||||
name = self.oglive.get_clients()[str(oglive_num)]
|
||||
|
||||
if oglive_num is None:
|
||||
self.__logger.info("Using default oglive")
|
||||
oglive_num = int(self.oglive.get_default())
|
||||
else:
|
||||
self.__logger.info("Using oglive %i", oglive_num)
|
||||
self.__logger.info("Using specified squashfs file %s", squashfs_file)
|
||||
name = os.path.basename(squashfs_file)
|
||||
|
||||
oglive_client = self.oglive.get_clients()[str(oglive_num)]
|
||||
self.__logger.info("Oglive is %s", oglive_client)
|
||||
|
||||
keys = installer.extract_ssh_keys(oglive_num = oglive_num)
|
||||
keys = installer.extract_ssh_keys_from_squashfs(oglive_num = oglive_num, squashfs_file=squashfs_file)
|
||||
for k in keys:
|
||||
timestamp = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now())
|
||||
installer.add_forgejo_sshkey(k, f"Key for {oglive_client} ({timestamp})")
|
||||
installer.add_forgejo_sshkey(k, f"Key for {name} ({timestamp})")
|
||||
|
||||
|
||||
|
||||
def extract_ssh_keys(self, oglive_num = None):
|
||||
def extract_ssh_keys_from_squashfs(self, oglive_num = None, squashfs_file = None):
|
||||
public_keys = []
|
||||
|
||||
|
||||
squashfs = "ogclient.sqfs"
|
||||
|
||||
tftp_dir = os.path.join(self.base_path, "tftpboot")
|
||||
if squashfs_file is None:
|
||||
tftp_dir = os.path.join(self.base_path, "tftpboot")
|
||||
|
||||
if oglive_num is None:
|
||||
self.__logger.info("Reading from default oglive")
|
||||
oglive_num = self.oglive.get_default()
|
||||
if oglive_num is None:
|
||||
self.__logger.info("Reading from default oglive")
|
||||
oglive_num = self.oglive.get_default()
|
||||
else:
|
||||
self.__logger.info("Reading from oglive %i", oglive_num)
|
||||
|
||||
oglive_client = self.oglive.get_clients()[str(oglive_num)]
|
||||
self.__logger.info("Oglive is %s", oglive_client)
|
||||
|
||||
client_squashfs_path = os.path.join(tftp_dir, oglive_client, squashfs)
|
||||
else:
|
||||
self.__logger.info("Reading from oglive %i", oglive_num)
|
||||
|
||||
oglive_client = self.oglive.get_clients()[str(oglive_num)]
|
||||
self.__logger.info("Oglive is %s", oglive_client)
|
||||
|
||||
client_squashfs_path = os.path.join(tftp_dir, oglive_client, squashfs)
|
||||
self.__logger.info("Using specified squashfs file %s", squashfs_file)
|
||||
client_squashfs_path = squashfs_file
|
||||
|
||||
self.__logger.info("Mounting %s", client_squashfs_path)
|
||||
mount_tempdir = tempfile.TemporaryDirectory()
|
||||
|
@ -352,49 +453,75 @@ class OpengnsysGitInstaller:
|
|||
return public_keys
|
||||
|
||||
|
||||
def _extract_ssh_key_from_initrd(self):
|
||||
def extract_ssh_key_from_initrd(self, oglive_number = None, initrd_file = None):
|
||||
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.__logger.debug("Extracting ssh key from initrd")
|
||||
|
||||
#self.temp_dir = self._get_tempdir()
|
||||
if initrd_file is None:
|
||||
self.__logger.debug("Looking for initrd file")
|
||||
tftp_dir = os.path.join(self.base_path, "tftpboot")
|
||||
if oglive_number is None:
|
||||
oglive_number = self.oglive.get_default()
|
||||
|
||||
if self.usesshkey:
|
||||
with open(self.usesshkey, 'r') as f:
|
||||
public_key = f.read().strip()
|
||||
oglive_client = self.oglive.get_clients()[oglive_number]
|
||||
client_initrd_path = os.path.join(tftp_dir, oglive_client, INITRD)
|
||||
|
||||
self.__logger.debug("Found at %s", client_initrd_path)
|
||||
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)
|
||||
self.__logger.debug("Using provided initrd file %s", initrd_file)
|
||||
client_initrd_path = initrd_file
|
||||
|
||||
pathname = file.pathname;
|
||||
if pathname.startswith("./"):
|
||||
pathname = pathname[2:]
|
||||
self.__logger.debug("Extracting key from %s", client_initrd_path)
|
||||
|
||||
if pathname in self.key_paths_dict:
|
||||
data = bytearray()
|
||||
for block in file.get_blocks():
|
||||
data = data + block
|
||||
public_key = data.decode('utf-8').strip()
|
||||
if os.path.isfile(client_initrd_path):
|
||||
#os.makedirs(temp_dir, exist_ok=True)
|
||||
#os.chdir(self.temp_dir.name)
|
||||
self.__logger.debug("Uncompressing %s", client_initrd_path)
|
||||
public_key = None
|
||||
with libarchive.file_reader(client_initrd_path) as initrd:
|
||||
for file in initrd:
|
||||
self.__logger.debug("File: %s", file)
|
||||
|
||||
break
|
||||
else:
|
||||
print(f"No se encuentra la imagen de initrd {client_initrd_path}")
|
||||
exit(2)
|
||||
pathname = file.pathname;
|
||||
if pathname.startswith("./"):
|
||||
pathname = pathname[2:]
|
||||
|
||||
if pathname in self.key_paths_dict:
|
||||
self.__logger.info("Found key %s, extracting", pathname)
|
||||
|
||||
data = bytearray()
|
||||
for block in file.get_blocks():
|
||||
data = data + block
|
||||
public_key = data.decode('utf-8').strip()
|
||||
|
||||
break
|
||||
else:
|
||||
print(f"Failed to find initrd at {client_initrd_path}")
|
||||
exit(2)
|
||||
|
||||
if not public_key:
|
||||
self.__logger.warning("Failed to find a SSH key")
|
||||
|
||||
return public_key
|
||||
|
||||
def get_image_paths(self, oglive_num = None):
|
||||
squashfs = "ogclient.sqfs"
|
||||
|
||||
if oglive_num is None:
|
||||
self.__logger.info("Will modify default client")
|
||||
oglive_num = self.oglive.get_default()
|
||||
|
||||
tftp_dir = os.path.join(self.base_path, "tftpboot")
|
||||
oglive_client = self.oglive.get_clients()[str(oglive_num)]
|
||||
|
||||
client_squashfs_path = os.path.join(tftp_dir, oglive_client, squashfs)
|
||||
|
||||
self.__logger.info("Squashfs: %s", client_squashfs_path)
|
||||
|
||||
|
||||
def set_ssh_key_in_initrd(self, client_num = None):
|
||||
INITRD = "oginitrd.img"
|
||||
|
||||
|
@ -534,7 +661,25 @@ class OpengnsysGitInstaller:
|
|||
|
||||
self.add_forgejo_sshkey(oglive_public_key, f"Key for {ogclient} ({timestamp})")
|
||||
|
||||
def install(self):
|
||||
|
||||
def verify_requirements(self):
|
||||
self.__logger.info("verify_requirements()")
|
||||
|
||||
# Control básico de errores.
|
||||
self.__logger.debug("Comprobando euid")
|
||||
if os.geteuid() != 0:
|
||||
raise RequirementException("Sólo ejecutable por root")
|
||||
|
||||
if not os.path.exists("/etc/debian_version"):
|
||||
raise RequirementException("Instalación sólo soportada en Debian y Ubuntu")
|
||||
|
||||
|
||||
MIN_PYTHON = (3, 8)
|
||||
if sys.version_info < MIN_PYTHON:
|
||||
raise RequirementException(f"Python %s.%s mínimo requerido.\n" % MIN_PYTHON)
|
||||
|
||||
|
||||
def install_dependencies(self):
|
||||
"""Instalar
|
||||
|
||||
Ejecuta todo el proceso de instalación incluyendo:
|
||||
|
@ -551,31 +696,10 @@ class OpengnsysGitInstaller:
|
|||
"""
|
||||
self.__logger.info("install()")
|
||||
|
||||
ogdir_images = os.path.join(self.base_path, "images")
|
||||
ENGINECFG = os.path.join(self.base_path, "client/etc/engine.cfg")
|
||||
|
||||
os.environ["PATH"] += os.pathsep + os.path.join(self.base_path, "bin")
|
||||
tftp_dir = os.path.join(self.base_path, "tftpboot")
|
||||
INITRD = "oginitrd.img"
|
||||
self.temp_dir = self._get_tempdir()
|
||||
SSHUSER = "opengnsys"
|
||||
self.verify_requirements()
|
||||
|
||||
|
||||
# Control básico de errores.
|
||||
self.__logger.debug("Comprobando euid")
|
||||
if os.geteuid() != 0:
|
||||
raise RequirementException("Sólo ejecutable por root")
|
||||
|
||||
if not os.path.exists("/etc/debian_version"):
|
||||
raise RequirementException("Instalación sólo soportada en Debian y Ubuntu")
|
||||
|
||||
|
||||
MIN_PYTHON = (3, 8)
|
||||
if sys.version_info < MIN_PYTHON:
|
||||
raise RequirementException(f"Python %s.%s mínimo requerido.\n" % MIN_PYTHON)
|
||||
|
||||
|
||||
self.__logger.debug("Instalando dependencias")
|
||||
self.__logger.debug("Installing dependencies")
|
||||
subprocess.run(["apt-get", "install", "-y", "git"], check=True)
|
||||
|
||||
def _install_template(self, template, destination, keysvalues):
|
||||
|
@ -599,34 +723,43 @@ class OpengnsysGitInstaller:
|
|||
return ret.stdout.strip()
|
||||
|
||||
def install_forgejo(self):
|
||||
self.__logger.info("Installing Forgejo")
|
||||
self.__logger.info("Installing Forgejo version %s", FORGEJO_VERSION)
|
||||
|
||||
|
||||
|
||||
|
||||
bin_path = os.path.join(self.base_path, "bin", "forgejo")
|
||||
conf_dir_path = os.path.join(self.base_path, "etc", "forgejo")
|
||||
opengnsys_bin_path = os.path.join(self.base_path, "bin")
|
||||
opengnsys_etc_path = os.path.join(self.base_path, "etc")
|
||||
|
||||
forgejo_bin_path = os.path.join(self.ogrepository_base_path, "bin")
|
||||
bin_path = os.path.join(forgejo_bin_path, "forgejo")
|
||||
conf_dir_path = os.path.join(self.ogrepository_base_path, "etc", "forgejo")
|
||||
|
||||
|
||||
lfs_dir_path = os.path.join(self.base_path, "images", "git-lfs")
|
||||
git_dir_path = os.path.join(self.base_path, "images", "git")
|
||||
lfs_dir_path = os.path.join(self.ogrepository_base_path, "oggit", "git-lfs")
|
||||
git_dir_path = os.path.join(self.ogrepository_base_path, "oggit", "git")
|
||||
|
||||
forgejo_work_dir_path = os.path.join(self.base_path, "var", "lib", "forgejo/work")
|
||||
forgejo_db_dir_path = os.path.join(self.base_path, "var", "lib", "forgejo/db")
|
||||
forgejo_data_dir_path = os.path.join(self.base_path, "var", "lib", "forgejo/data")
|
||||
forgejo_work_dir_path = os.path.join(self.ogrepository_base_path, "var", "lib", "forgejo/work")
|
||||
forgejo_db_dir_path = os.path.join(self.ogrepository_base_path, "var", "lib", "forgejo/db")
|
||||
forgejo_data_dir_path = os.path.join(self.ogrepository_base_path, "var", "lib", "forgejo/data")
|
||||
|
||||
forgejo_db_path = os.path.join(forgejo_db_dir_path, "forgejo.db")
|
||||
|
||||
forgejo_log_dir_path = os.path.join(self.base_path, "log", "forgejo")
|
||||
forgejo_log_dir_path = os.path.join(self.ogrepository_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)
|
||||
self.__logger.info("Stopping opengnsys-forgejo service. This may cause a harmless warning.")
|
||||
|
||||
subprocess.run(["/usr/bin/systemctl", "stop", "opengnsys-forgejo"], check=False)
|
||||
|
||||
self.__logger.debug("Downloading from %s into %s", FORGEJO_URL, bin_path)
|
||||
urllib.request.urlretrieve(FORGEJO_URL, bin_path)
|
||||
pathlib.Path(forgejo_bin_path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(bin_path, "wb") as forgejo_bin:
|
||||
download_with_progress(FORGEJO_URL, forgejo_bin)
|
||||
|
||||
os.chmod(bin_path, 0o755)
|
||||
|
||||
if os.path.exists(forgejo_db_path):
|
||||
|
@ -643,6 +776,7 @@ class OpengnsysGitInstaller:
|
|||
|
||||
self.__logger.debug("Creating directories")
|
||||
|
||||
pathlib.Path(opengnsys_etc_path).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(conf_dir_path).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(git_dir_path).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(lfs_dir_path).mkdir(parents=True, exist_ok=True)
|
||||
|
@ -716,11 +850,6 @@ class OpengnsysGitInstaller:
|
|||
token_file.write(token)
|
||||
|
||||
|
||||
ssh_key = self._extract_ssh_key_from_initrd()
|
||||
|
||||
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:
|
||||
|
@ -799,8 +928,7 @@ if __name__ == '__main__':
|
|||
streamLog = logging.StreamHandler()
|
||||
streamLog.setLevel(logging.INFO)
|
||||
|
||||
if not os.path.exists(opengnsys_log_dir):
|
||||
os.mkdir(opengnsys_log_dir)
|
||||
pathlib.Path(opengnsys_log_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
logFilePath = f"{opengnsys_log_dir}/git_installer.log"
|
||||
fileLog = logging.FileHandler(logFilePath)
|
||||
|
@ -824,15 +952,23 @@ if __name__ == '__main__':
|
|||
|
||||
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")
|
||||
parser.add_argument('--use-ssh-key', metavar="FILE", type=str, help="Add the SSH key from the specified file")
|
||||
parser.add_argument('--test-createuser', action='store_true')
|
||||
parser.add_argument('--extract-ssh-key', action='store_true', help="Extract SSH key from oglive squashfs")
|
||||
parser.add_argument('--set-ssh-key', action='store_true', help="Read SSH key from oglive squashfs and set it in Forgejo")
|
||||
|
||||
parser.add_argument('--extract-ssh-key-from-initrd', action='store_true', help="Extract SSH key from oglive initrd (obsolete)")
|
||||
|
||||
parser.add_argument('--initrd-file', metavar="FILE", help="Initrd file to extract SSH key from")
|
||||
parser.add_argument('--squashfs-file', metavar="FILE", help="Squashfs file to extract SSH key from")
|
||||
parser.add_argument('--oglive-file', metavar="FILE", help="Oglive file (ISO) to extract SSH key from")
|
||||
parser.add_argument('--oglive-url', metavar="URL", help="URL to oglive file (ISO) to extract SSH key from")
|
||||
|
||||
|
||||
parser.add_argument('--set-ssh-key-in-initrd', action='store_true', help="Configure SSH key in oglive (obsolete)")
|
||||
parser.add_argument('--oglive', type=int, metavar='NUM', help = "Do SSH key manipulation on this oglive")
|
||||
parser.add_argument('--quiet', action='store_true', help="Quiet console output")
|
||||
parser.add_argument('--get-image-paths', action='store_true', help="Get paths to image files")
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help = "Verbose console output")
|
||||
|
||||
|
||||
|
@ -848,7 +984,6 @@ if __name__ == '__main__':
|
|||
installer = OpengnsysGitInstaller()
|
||||
installer.set_testmode(args.testmode)
|
||||
installer.set_ignoresshkey(args.ignoresshkey)
|
||||
installer.set_usesshkey(args.usesshkey)
|
||||
|
||||
logger.debug("Inicio de instalación")
|
||||
|
||||
|
@ -860,25 +995,39 @@ if __name__ == '__main__':
|
|||
elif args.test_createuser:
|
||||
installer.set_ssh_user_group("oggit2", "oggit2")
|
||||
elif args.extract_ssh_key:
|
||||
keys = installer.extract_ssh_keys(oglive_num = args.oglive)
|
||||
keys = installer.extract_ssh_keys_from_squashfs(oglive_num = args.oglive)
|
||||
print(f"{keys}")
|
||||
elif args.extract_ssh_key_from_initrd:
|
||||
key = installer._extract_ssh_key_from_initrd()
|
||||
key = installer.extract_ssh_key_from_initrd(oglive_number = args.oglive, initrd_file = args.initrd_file)
|
||||
print(f"{key}")
|
||||
elif args.set_ssh_key:
|
||||
installer.add_ssh_key_from_squashfs(oglive_num=args.oglive)
|
||||
installer.add_ssh_key_from_squashfs(oglive_num=args.oglive, squashfs_file=args.squashfs_file, oglive_file = args.oglive_file or args.oglive_url)
|
||||
elif args.use_ssh_key:
|
||||
with open(args.use_ssh_key, 'r', encoding='utf-8') as ssh_key_file:
|
||||
ssh_key_data = ssh_key_file.read().strip()
|
||||
(keytype, keydata, description) = ssh_key_data.split(" ", 2)
|
||||
|
||||
installer.add_forgejo_sshkey(f"{keytype} {keydata}", description)
|
||||
|
||||
elif args.set_ssh_key_in_initrd:
|
||||
installer.set_ssh_key_in_initrd()
|
||||
elif args.get_image_paths:
|
||||
installer.get_image_paths(oglive_num = args.oglive)
|
||||
else:
|
||||
installer.install()
|
||||
installer.install_dependencies()
|
||||
installer.install_forgejo()
|
||||
|
||||
installer.add_forgejo_repo("windows", "Windows")
|
||||
installer.add_forgejo_repo("linux", "Linux")
|
||||
installer.add_forgejo_repo("mac", "Mac")
|
||||
|
||||
installer.add_ssh_key_from_squashfs(oglive_num = args.oglive, squashfs_file=args.squashfs_file, oglive_file = args.oglive_file or args.oglive_url)
|
||||
|
||||
except RequirementException as req:
|
||||
show_error(f"Requisito para la instalación no satisfecho: {req.message}")
|
||||
exit(1)
|
||||
except OptionalDependencyException as optreq:
|
||||
show_error(optreq.message)
|
||||
exit(1)
|
||||
|
||||
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
git clone https://github.com/vojtechtrefny/pyblkid opengnsys-pyblkid
|
||||
cd opengnsys-pyblkid
|
||||
version=`python3 ./setup.py --version`
|
||||
cd ..
|
||||
|
||||
if [ -d "opengnsys-pyblkid-${version}" ] ; then
|
||||
echo "Directory opengnsys-pyblkid-${version} already exists, won't overwrite"
|
||||
exit 1
|
||||
else
|
||||
rm -rf opengnsys-pyblkid/.git
|
||||
mv opengnsys-pyblkid "opengnsys-pyblkid-${version}"
|
||||
tar -c --xz -v -f "opengnsys-pyblkid_${version}.orig.tar.xz" "opengnsys-pyblkid-${version}"
|
||||
fi
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: python-libarchive-c
|
||||
Source: https://github.com/Changaco/python-libarchive-c
|
||||
|
||||
Files: *
|
||||
Copyright: 2014-2018 Changaco <changaco@changaco.oy.lc>
|
||||
License: CC-0
|
||||
|
||||
Files: tests/surrogateescape.py
|
||||
Copyright: 2015 Changaco <changaco@changaco.oy.lc>
|
||||
2011-2013 Victor Stinner <victor.stinner@gmail.com>
|
||||
License: BSD-2-clause or PSF-2
|
||||
|
||||
Files: debian/*
|
||||
Copyright: 2015 Jerémy Bobbio <lunar@debian.org>
|
||||
2019 Mattia Rizzolo <mattia@debian.org>
|
||||
License: permissive
|
||||
Copying and distribution of this package, with or without
|
||||
modification, are permitted in any medium without royalty
|
||||
provided the copyright notice and this notice are
|
||||
preserved.
|
||||
|
||||
License: BSD-2-clause
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
License: PSF-2
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"),
|
||||
and the Individual or Organization ("Licensee") accessing and otherwise using
|
||||
this software ("Python") in source or binary form and its associated
|
||||
documentation.
|
||||
.
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
grants Licensee a nonexclusive, royalty-free, world-wide license to
|
||||
reproduce, analyze, test, perform and/or display publicly, prepare derivative
|
||||
works, distribute, and otherwise use Python alone or in any derivative
|
||||
version, provided, however, that PSF's License Agreement and PSF's notice of
|
||||
copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python
|
||||
Software Foundation; All Rights Reserved" are retained in Python alone or in
|
||||
any derivative version prepared by Licensee.
|
||||
.
|
||||
3. In the event Licensee prepares a derivative work that is based on or
|
||||
incorporates Python or any part thereof, and wants to make the derivative
|
||||
work available to others as provided herein, then Licensee hereby agrees to
|
||||
include in any such work a brief summary of the changes made to Python.
|
||||
.
|
||||
4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES
|
||||
NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT
|
||||
NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF
|
||||
PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
.
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY
|
||||
INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF
|
||||
MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE
|
||||
THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
.
|
||||
6. This License Agreement will automatically terminate upon a material breach
|
||||
of its terms and conditions.
|
||||
.
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote products
|
||||
or services of Licensee, or any third party.
|
||||
.
|
||||
8. By copying, installing or otherwise using Python, Licensee agrees to be
|
||||
bound by the terms and conditions of this License Agreement.
|
||||
|
||||
License: CC-0
|
||||
Statement of Purpose
|
||||
.
|
||||
The laws of most jurisdictions throughout the world automatically
|
||||
confer exclusive Copyright and Related Rights (defined below) upon
|
||||
the creator and subsequent owner(s) (each and all, an "owner") of an
|
||||
original work of authorship and/or a database (each, a "Work").
|
||||
.
|
||||
Certain owners wish to permanently relinquish those rights to a Work
|
||||
for the purpose of contributing to a commons of creative, cultural
|
||||
and scientific works ("Commons") that the public can reliably and
|
||||
without fear of later claims of infringement build upon, modify,
|
||||
incorporate in other works, reuse and redistribute as freely as
|
||||
possible in any form whatsoever and for any purposes, including
|
||||
without limitation commercial purposes. These owners may contribute
|
||||
to the Commons to promote the ideal of a free culture and the further
|
||||
production of creative, cultural and scientific works, or to gain
|
||||
reputation or greater distribution for their Work in part through the
|
||||
use and efforts of others.
|
||||
.
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he
|
||||
or she is an owner of Copyright and Related Rights in the Work,
|
||||
voluntarily elects to apply CC0 to the Work and publicly distribute
|
||||
the Work under its terms, with knowledge of his or her Copyright and
|
||||
Related Rights in the Work and the meaning and intended legal effect
|
||||
of CC0 on those rights.
|
||||
.
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may
|
||||
be protected by copyright and related or neighboring rights
|
||||
("Copyright and Related Rights"). Copyright and Related Rights
|
||||
include, but are not limited to, the following:
|
||||
.
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or
|
||||
performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image
|
||||
or likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a
|
||||
Work, subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and
|
||||
reuse of data in a Work;
|
||||
vi. database rights (such as those arising under Directive
|
||||
96/9/EC of the European Parliament and of the Council of 11
|
||||
March 1996 on the legal protection of databases, and under
|
||||
any national implementation thereof, including any amended or
|
||||
successor version of such directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout
|
||||
the world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
.
|
||||
2. Waiver. To the greatest extent permitted by, but not in
|
||||
contravention of, applicable law, Affirmer hereby overtly, fully,
|
||||
permanently, irrevocably and unconditionally waives, abandons, and
|
||||
surrenders all of Affirmer's Copyright and Related Rights and
|
||||
associated claims and causes of action, whether now known or
|
||||
unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for
|
||||
the maximum duration provided by applicable law or treaty
|
||||
(including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose
|
||||
whatsoever, including without limitation commercial, advertising
|
||||
or promotional purposes (the "Waiver"). Affirmer makes the Waiver
|
||||
for the benefit of each member of the public at large and to the
|
||||
detriment of Affirmer's heirs and successors, fully intending that
|
||||
such Waiver shall not be subject to revocation, rescission,
|
||||
cancellation, termination, or any other legal or equitable action
|
||||
to disrupt the quiet enjoyment of the Work by the public as
|
||||
contemplated by Affirmer's express Statement of Purpose.
|
||||
.
|
||||
3. Public License Fallback. Should any part of the Waiver for any
|
||||
reason be judged legally invalid or ineffective under applicable law,
|
||||
then the Waiver shall be preserved to the maximum extent permitted
|
||||
taking into account Affirmer's express Statement of Purpose. In
|
||||
addition, to the extent the Waiver is so judged Affirmer hereby
|
||||
grants to each affected person a royalty-free, non transferable, non
|
||||
sublicensable, non exclusive, irrevocable and unconditional license
|
||||
to exercise Affirmer's Copyright and Related Rights in the Work (i)
|
||||
in all territories worldwide, (ii) for the maximum duration provided
|
||||
by applicable law or treaty (including future time extensions), (iii)
|
||||
in any current or future medium and for any number of copies, and
|
||||
(iv) for any purpose whatsoever, including without limitation
|
||||
commercial, advertising or promotional purposes (the "License"). The
|
||||
License shall be deemed effective as of the date CC0 was applied by
|
||||
Affirmer to the Work. Should any part of the License for any reason
|
||||
be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the
|
||||
remainder of the License, and in such case Affirmer hereby affirms
|
||||
that he or she will not (i) exercise any of his or her remaining
|
||||
Copyright and Related Rights in the Work or (ii) assert any
|
||||
associated claims and causes of action with respect to the Work, in
|
||||
either case contrary to Affirmer's express Statement of Purpose.
|
||||
.
|
||||
4. Limitations and Disclaimers.
|
||||
.
|
||||
a. No trademark or patent rights held by Affirmer are waived,
|
||||
abandoned, surrendered, licensed or otherwise affected by
|
||||
this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations
|
||||
or warranties of any kind concerning the Work, express,
|
||||
implied, statutory or otherwise, including without limitation
|
||||
warranties of title, merchantability, fitness for a
|
||||
particular purpose, non infringement, or the absence of
|
||||
latent or other defects, accuracy, or the present or absence
|
||||
of errors, whether or not discoverable, all to the greatest
|
||||
extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of
|
||||
other persons that may apply to the Work or any use thereof,
|
||||
including without limitation any person's Copyright and
|
||||
Related Rights in the Work. Further, Affirmer disclaims
|
||||
responsibility for obtaining any necessary consents,
|
||||
permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons
|
||||
is not a party to this document and has no duty or obligation
|
||||
with respect to this CC0 or use of the Work.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Tests: upstream-tests
|
||||
Depends: @, python3-mock, python3-pytest
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
if ! [ -d "$AUTOPKGTEST_TMP" ]; then
|
||||
echo "AUTOPKGTEST_TMP not set." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp -rv tests "$AUTOPKGTEST_TMP"
|
||||
cd "$AUTOPKGTEST_TMP"
|
||||
mkdir -v libarchive
|
||||
touch README.rst
|
||||
py.test-3 tests -vv -l -r a
|
Loading…
Reference in New Issue