Compare commits

...

55 Commits

Author SHA1 Message Date
Vadim vtroshchinskiy d6c7f8a979 Fix port in templates 2025-06-16 23:42:41 +02:00
Vadim vtroshchinskiy 84a2c52f11 Update changelog 2025-06-16 21:23:34 +00:00
Vadim vtroshchinskiy 9194bf94bb Update changelog 2025-06-16 21:22:10 +00:00
Vadim vtroshchinskiy 5c9d2eac84 Update changelog 2025-06-16 21:21:23 +00:00
Vadim vtroshchinskiy abbc57d4fa Slightly improve API for ogrepo usability 2025-06-16 12:07:31 +02:00
Vadim vtroshchinskiy 83dba76e43 Add git image creation script 2025-06-16 12:07:31 +02:00
Vadim vtroshchinskiy 5eb09c7a1b Add package files 2025-06-06 09:58:00 +02:00
Vadim vtroshchinskiy e626e8f776 Update changelog 2025-06-05 21:46:30 +00:00
Vadim vtroshchinskiy fe2846099c Update changelog 2025-06-05 10:15:31 +02:00
Vadim vtroshchinskiy f981269561 Fix ini path 2025-06-05 09:42:51 +02:00
Vadim vtroshchinskiy c4d9101f2b Fix permission problem 2025-06-04 23:34:11 +02:00
Vadim vtroshchinskiy ebea7af520 Disable tests 2025-06-04 23:21:37 +02:00
Vadim vtroshchinskiy 0ecb4a0aff Add templates 2025-05-22 09:14:04 +02:00
Vadim vtroshchinskiy 9be76a112f Rename service 2025-04-30 10:39:39 +02:00
Vadim vtroshchinskiy 6662e270be Add missing file 2025-04-30 10:27:12 +02:00
Vadim vtroshchinskiy 442324659c Add branches and tags creation endpoints 2025-04-23 08:43:34 +02:00
Vadim vtroshchinskiy 5b739a1c61 Debian packaging 2025-04-16 00:00:13 +02:00
Vadim vtroshchinskiy bbdfed4cc6 Fixes for running under gunicorn 2025-04-15 23:59:07 +02:00
Vadim vtroshchinskiy 1d1f2caab8 Fix post-install for forgejo deployment
Handle initializing the forgejo database and reinstall
2025-04-15 08:59:02 +02:00
Vadim vtroshchinskiy 3ef8fe9dcd opengnsys-forgejo package 2025-04-09 09:44:09 +02:00
Vadim vtroshchinskiy 4d0b383839 Refactoring for packaging 2025-04-03 00:04:48 +02:00
Vadim vtroshchinskiy 5bc05c19f1 Remove old code 2025-04-03 00:02:01 +02:00
Vadim vtroshchinskiy ec9f25d9b0 Refactoring for package support 2025-04-02 23:59:50 +02:00
Vadim vtroshchinskiy f2ce7267f1 Fix port argument 2025-04-01 13:12:02 +02:00
Vadim vtroshchinskiy ece688c582 Add helpful script 2025-04-01 11:56:23 +02:00
Vadim vtroshchinskiy d929d961f1 Bump forgejo version 2025-04-01 11:09:16 +02:00
Vadim vtroshchinskiy eee84f7d25 Fix repository URL 2025-03-31 16:13:39 +02:00
Vadim vtroshchinskiy ec2fd05fdf Load swagger from disk 2025-03-31 12:24:33 +02:00
Vadim vtroshchinskiy 13257ce085 Add README 2025-03-31 10:25:57 +02:00
Vadim vtroshchinskiy 1c9737b398 Fix error handling 2025-03-31 10:25:44 +02:00
Vadim vtroshchinskiy ccb5e518e7 Add port argument 2025-03-31 10:25:36 +02:00
Vadim vtroshchinskiy e518a509cd Convert to blueprint 2025-03-25 15:22:21 +01:00
Vadim vtroshchinskiy f6a5699c58 Add original repo_api 2025-03-25 11:55:54 +01:00
Vadim vtroshchinskiy e67b08cea5 Initial version of the API server 2025-03-25 09:45:19 +01:00
Vadim vtroshchinskiy d4ce9c3ee3 Make branch deletion RESTful 2025-02-06 16:22:38 +01:00
Vadim vtroshchinskiy 8bebeb619a Branch deletion 2025-02-06 16:14:17 +01:00
Vadim vtroshchinskiy 115df98905 Log every request 2025-02-06 16:03:23 +01:00
Vadim vtroshchinskiy 5721e56237 Rework the ability to use a custom SSH key
The code wasn't up to date with the Forgejo changes
2025-02-06 15:31:37 +01:00
Vadim vtroshchinskiy 3ebc728fb9 Mark git repo as a safe directory
Fixes problems due to git not liking the ownership
2025-02-06 13:15:21 +01:00
Vadim vtroshchinskiy 46732216eb More error logging 2025-02-06 13:14:53 +01:00
Vadim vtroshchinskiy 1f2095ce1a Improve task management, cleanup when there are too many 2025-02-06 13:14:31 +01:00
Vadim vtroshchinskiy 09baf6d1e8 Fix HTTP exception handling
Using too general of an exception was causing problems.
2025-02-06 09:38:31 +01:00
Vadim vtroshchinskiy 73118501b3 Improvements for logging and error handling 2025-01-29 09:45:26 +01:00
Vadim vtroshchinskiy 14cd2d4363 Change git repo path 2025-01-24 09:49:32 +01:00
Vadim vtroshchinskiy 4ef29e9fca Fix ogrepository paths 2025-01-23 09:59:44 +01:00
Vadim vtroshchinskiy 6491757535 Fix namespaces 2025-01-17 09:50:47 +01:00
Vadim vtroshchinskiy dc59b33e8a Improve installation process, make it possible to extract keys from oglive 2025-01-17 09:49:12 +01:00
Vadim vtroshchinskiy 1d4100dcc0 Update english documentation 2025-01-13 15:56:10 +01:00
Vadim vtroshchinskiy 62b6319845 Restructure git installer to work without ogboot on the same machine, update docs 2025-01-13 15:16:39 +01:00
Vadim vtroshchinskiy a60d93ce03 Reorder and fix for ogrepository reorganization
Still needs a bit of improvement to deal with the case of not being
on the same machine as ogadmin
2025-01-13 09:54:40 +01:00
Vadim vtroshchinskiy 7c83f24b31 Add make_orig script
This downloads and creates the .orig tar gz for debian packaging
2025-01-10 12:56:28 +01:00
Vadim vtroshchinskiy cbbea5ff47 Add pyblkid copyright file 2025-01-10 12:55:56 +01:00
Vadim vtroshchinskiy 26427a67f3 add python libarchive-c original package 2025-01-10 12:55:20 +01:00
Vadim vtroshchinskiy 1bb520b61c Ignore more files 2025-01-10 12:54:54 +01:00
Vadim vtroshchinskiy f05c0e3943 Ignore python cache 2025-01-09 11:59:39 +01:00
62 changed files with 11034 additions and 227 deletions

8
.gitignore vendored 100644
View File

@ -0,0 +1,8 @@
__pycache__
.venv
venvog
*.deb
*.build
*.dsc
*.changes
*.buildinfo

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,42 @@
# API Server
`api_server.py` is a Flask script that loads and executes Flask blueprints from the `blueprints` directory.
Currently it's intended to combine oggit and ogrepository.
# Usage
## Ubuntu 24.04
sudo apt install -y python3-flask python3-paramiko opengnsys-flask-executor opengnsys-flask-restx
The `opengnsys-flask-executor` and `opengnsys-flask-restx` packages are available on the OpenGnsys package server.
Run with:
./api_server.py
# Operation
## Requirements
The gitapi is designed to run within an existing opengnsys environment. It should be installed in an ogrepository.
## API Examples
### Get list of branches
$ curl -L http://localhost:5000/repositories/linux/branches
{
"branches": [
"master"
]
}
### Synchronize with remote repository
curl --header "Content-Type: application/json" --data '{"remote_repository":"foobar"}' -X POST -L http://localhost:5000/repositories/linux/sync

View File

@ -0,0 +1,170 @@
#!/usr/bin/env python3
import os
import sys
sys.path.insert(0, "/usr/share/opengnsys-modules/python3/dist-packages")
sys.path.insert(0, "/opt/opengnsys/oggit/bin/")
import importlib
import logging
import uuid
import argparse
import yaml
from flask import Flask, request
from flask_executor import Executor
from flask_restx import Api
from flasgger import Swagger
from werkzeug.exceptions import HTTPException
from systemd.journal import JournalHandler
class FakeArgs:
def __init__(self):
self.verbose = False
self.listen = None
self.port = None
self.debug = None
parser = argparse.ArgumentParser(
prog="api_server.py",
description="OpenGnsys Repository API Server",
)
debug_enabled = False
listen_host = '0.0.0.0'
listen_port = 8006
is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "")
if not is_gunicorn:
# Gunicorn passes us all the arguments passed to gunicorn itself, which of course crashes here since we don't recognize them.
# Deal with this by not doing argument handling when running under gunicorn
parser.add_argument('--debug', action='store_true', help="Enable debug output")
parser.add_argument('--listen', metavar="HOST", help="Listen address")
parser.add_argument('--port', metavar="PORT", help="Listen port")
parser.add_argument("-v", "--verbose", action="store_true", help = "Verbose console output")
args = parser.parse_args()
else:
args = FakeArgs()
log = logging.getLogger('api_server')
log.addHandler(JournalHandler())
if args.verbose:
log.addHandler(logging.StreamHandler(stream=sys.stderr))
log.setLevel(logging.DEBUG)
else:
log.setLevel(logging.INFO)
if is_gunicorn:
log.info("Running under gunicorn, argument handling disabled.")
if args.listen:
listen_host = args.listen
if args.port:
listen_port = args.port
if args.debug:
debug_enabled = True
api_base_dir = os.path.dirname(os.path.realpath(__file__))
blueprints_dir = os.path.join(api_base_dir, 'blueprints')
installer_dir = os.path.join(api_base_dir, '../installer')
sys.path.insert(0, installer_dir)
# Create an instance of the Flask class
app = Flask(__name__)
api = Api(app,
version='0.50',
title = "OpenGnsys Git API",
description = "API for managing disk images stored in Git",
doc = "/apidocs/")
executor = Executor(app)
log.info("Loading blueprints from %s", blueprints_dir)
sys.path.insert(0, blueprints_dir)
for filename in os.listdir(blueprints_dir):
if filename.endswith('.py'):
log.info("Loading %s/%s", blueprints_dir, filename)
module_name = filename.replace(".py", "")
swagger_file = os.path.join(blueprints_dir, filename.replace(".py", ".yaml"))
log.info("Importing %s", module_name)
importlib.invalidate_caches()
module = importlib.import_module(module_name)
log.debug("Returned: %s", module)
app.register_blueprint(module.blueprint)
if os.path.exists(swagger_file):
log.info("Loading Swagger documentation from %s...", swagger_file)
with open(swagger_file, "r", encoding='utf-8') as file:
swagger_template = yaml.safe_load(file)
#print(f"Template: {swagger_template}")
#swagger = Swagger(app, template=swagger_template)
else:
log.warning("Swagger not found for this module, looked in %s", swagger_file)
@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
response = {
"errcode": e.code,
"errname": e.name,
"description": e.description,
}
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 })
return response, 500
@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
# Run the Flask app
if __name__ == '__main__':
print(f"Map: {app.url_map}")
app.run(debug=debug_enabled, host=listen_host, port=listen_port)

View File

@ -0,0 +1,713 @@
#!/usr/bin/env python3
"""
This module provides a Flask-based API for managing Git repositories in the OpenGnsys system.
It includes endpoints for creating, deleting, synchronizing, backing up, and performing garbage
collection on Git repositories. The API also provides endpoints for retrieving repository
information such as the list of repositories and branches, as well as checking the status of
asynchronous tasks.
Classes:
None
Functions:
do_repo_backup(repo, params)
do_repo_sync(repo, params)
do_repo_gc(repo)
home()
get_repositories()
create_repo(repo)
sync_repo(repo)
backup_repository(repo)
gc_repo(repo)
tasks_status(task_id)
delete_repo(repo)
get_repository_branches(repo)
health_check()
Constants:
REPOSITORIES_BASE_PATH (str): The base path where Git repositories are stored.
Global Variables:
app (Flask): The Flask application instance.
executor (Executor): The Flask-Executor instance for managing asynchronous tasks.
tasks (dict): A dictionary to store the status of asynchronous tasks.
"""
# pylint: disable=locally-disabled, line-too-long
import os.path
import os
import shutil
import uuid
import time
import logging
import traceback
import git
from opengnsys_git_installer import OpengnsysGitInstaller
from flask import Blueprint, request
from flask_restx import Resource, Api
import paramiko
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
blueprint = Blueprint('git_api', __name__, template_folder='templates', url_prefix = '/oggit/v1')
api = Api(blueprint)
git_ns = api
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):
"""
Creates a backup of the specified Git repository and uploads it to a remote server via SFTP.
Args:
repo (str): The name of the repository to back up.
params (dict): A dictionary containing the following keys:
- ssh_server (str): The SSH server address.
- ssh_port (int): The SSH server port.
- ssh_user (str): The SSH username.
- filename (str): The remote filename where the backup will be stored.
Returns:
bool: True if the backup was successful.
"""
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())
ssh.connect(params["ssh_server"], params["ssh_port"], params["ssh_user"])
sftp = ssh.open_sftp()
with sftp.file(params["filename"], mode='wb+') as remote_file:
git_repo.archive(remote_file, format="tar.gz")
return True
def do_repo_sync(repo, params):
"""
Synchronizes a local Git repository with a remote repository.
Args:
repo (str): The name of the local repository to synchronize.
params (dict): A dictionary containing the remote repository URL with the key "remote_repository".
Returns:
list: A list of dictionaries, each containing:
- "local_ref" (str): The name of the local reference.
- "remote_ref" (str): The name of the remote reference.
- "summary" (str): A summary of the push operation for the reference.
"""
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 git_repo.remotes:
git_repo.delete_remote("backup")
backup_repo = git_repo.create_remote("backup", params["remote_repository"])
pushed_references = backup_repo.push("*:*")
results = []
# This gets returned to the API
for ref in pushed_references:
results = results + [ {"local_ref" : ref.local_ref.name, "remote_ref" : ref.remote_ref.name, "summary" : ref.summary }]
return results
def do_repo_gc(repo):
"""
Perform garbage collection on the specified Git repository.
Args:
repo (str): The name of the repository to perform garbage collection on.
Returns:
bool: True if the garbage collection command was executed successfully.
"""
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)
git_repo.git.gc()
# Define a route for the root URL
@api.route('/')
class GitLib(Resource):
#@api.doc('home')
def get(self):
"""
Home route that returns a JSON response with a welcome message for the OpenGnsys Git API.
Returns:
Response: A Flask JSON response containing a welcome message.
"""
log.info("Root URL accessed")
return {
"message": "OpenGnsys Git API"
}
@git_ns.route('/repositories')
class GitRepositories(Resource):
def get(self):
"""
Retrieve a list of Git repositories.
This endpoint scans the OpenGnsys image path for directories that
appear to be Git repositories (i.e., they contain a "HEAD" file).
It returns a JSON response containing the names of these repositories.
Returns:
Response: A JSON response with a list of repository names or an
error message if the repository storage is not found.
- 200 OK: When the repositories are successfully retrieved.
- 500 Internal Server Error: When the repository storage is not found.
Example JSON response:
{
"repositories": ["repo1", "repo2"]
}
"""
if not os.path.isdir(REPOSITORIES_BASE_PATH):
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):
if entry.is_dir(follow_symlinks=False) and os.path.isfile(os.path.join(entry.path, "HEAD")):
name = entry.name
if name.endswith(".git"):
name = name[:-4]
repos = repos + [name]
log.info("Returning %i repositories", len(repos))
return {
"repositories": repos
}
def post(self):
"""
Create a new Git repository.
This endpoint creates a new Git repository with the specified name.
If the repository already exists, it returns a status message indicating so.
Args:
repo (str): The name of the repository to be created.
Returns:
Response: A JSON response with a status message and HTTP status code.
- 200: If the repository already exists.
- 201: If the repository is successfully created.
"""
data = request.json
if data is None:
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):
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()
installer.add_forgejo_repo(repo)
#installer.init_git_repo(repo + ".git")
log.info("Repository %s created", repo, extra = {"repository" : repo})
return {"status": "Repository created"}, 201
@git_ns.route('/repositories/<repo>/sync')
class GitRepoSync(Resource):
def post(self, repo):
"""
Synchronize a repository with a remote repository.
This endpoint triggers the synchronization process for a specified repository.
It expects a JSON payload with the remote repository details.
Args:
repo (str): The name of the repository to be synchronized.
Returns:
Response: A JSON response indicating the status of the synchronization process.
- 200: If the synchronization process has started successfully.
- 400: If the request payload is missing or invalid.
- 404: If the specified repository is not found.
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
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:
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 = 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('/repositories/<repo>/backup')
class GitRepoBackup(Resource):
def backup_repository(self, repo):
"""
Backup a specified repository.
Endpoint: POST /repositories/<repo>/backup
Args:
repo (str): The name of the repository to back up.
Request Body (JSON):
ssh_port (int, optional): The SSH port to use for the backup. Defaults to 22.
Returns:
Response: A JSON response indicating the status of the backup operation.
- If the repository is not found, returns a 404 error with a message.
- If the request body is missing, returns a 400 error with a message.
- If the backup process starts successfully, returns a 200 status with the task ID.
Notes:
- The repository path is constructed by appending ".git" to the repository name.
- The backup operation is performed asynchronously using a thread pool executor.
- The task ID of the backup operation is generated using UUID and stored in a global tasks dictionary.
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
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:
log.error("Can't create repository, JSON post data missing")
return {"error" : "Parameters missing"}, 400
if not "ssh_port" in data:
data["ssh_port"] = 22
future = executor.submit(do_repo_backup, repo, data)
task_id = add_task(future)
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('/repositories/<repo>/compact', methods=['POST'])
class GitRepoCompact(Resource):
def post(self, repo):
"""
Initiates a garbage collection (GC) process for a specified Git repository.
This endpoint triggers an asynchronous GC task for the given repository.
The task is submitted to an executor, and a unique task ID is generated
and returned to the client.
Args:
repo (str): The name of the repository to perform GC on.
Returns:
Response: A JSON response containing the status of the request and
a unique task ID if the repository is found, or an error
message if the repository is not found.
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
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 = add_task(future)
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('/tasks/<task_id>/status')
class GitTaskStatus(Resource):
def get(self, task_id):
"""
Endpoint to check the status of a specific task.
Args:
task_id (str): The unique identifier of the task.
Returns:
Response: A JSON response containing the status of the task.
- If the task is not found, returns a 404 error with an error message.
- If the task is completed, returns a 200 status with the result.
- If the task is still in progress, returns a 202 status indicating the task is in progress.
"""
if not task_id in tasks:
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"]
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('/repositories/<repo>', methods=['DELETE'])
class GitRepo(Resource):
def delete(self, repo):
"""
Deletes a Git repository.
This endpoint deletes a Git repository specified by the `repo` parameter.
If the repository does not exist, it returns a 404 error with a message
indicating that the repository was not found. If the repository is successfully
deleted, it returns a 200 status with a message indicating that the repository
was deleted.
Args:
repo (str): The name of the repository to delete.
Returns:
Response: A JSON response with a status message and the appropriate HTTP status code.
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
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)
log.info("Deleted repository %s", repo, extra = {"repository" : repo})
return {"status": "Repository deleted"}, 200
@git_ns.route('/repositories/<repo>/branches')
class GitRepoBranches(Resource):
def get(self, repo):
"""
Retrieve the list of branches for 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 "branches" key containing a list of branch names.
- 404: A JSON object with an "error" key containing the message "Repository not found" if the repository does not exist.
"""
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)
branches = []
for branch in git_repo.branches:
branches = branches + [branch.name]
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
def post(self, repo, branch):
"""Create 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"
- 409: A JSON object with an "error" key containing the message "Branch already exists"
"""
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)
data = request.json
if data is None:
log.error("Can't create branch, JSON post data missing")
return {"error" : "Parameters missing"}, 400
if not "commit" in data:
log.error("Can't create branch, commit parameter missing")
return {"error" : "commit parameter missing"}, 400
if branch in git_repo.branches:
log.error("Can't create branch %s, already found in repository %s", branch, repo, extra = {"repository" : repo, "branch" : branch})
return {"error": "Branch already exists"}, 409
git_repo.create_head(branch, commit = data["commit"] )
log.info("Branch %s of repository %s created", branch, repo, extra = {"repository" : repo, "branch" : branch})
return {"status": "created"}, 200
@git_ns.route('/repositories/<repo>/tags')
class GitRepoTags(Resource):
def get(self, repo):
"""
Retrieve the list of tags for a given repository.
Args:
repo (str): The name of the repository.
Returns:
Response: A JSON response containing a list of tags names or an error message if the repository is not found.
- 200: A JSON object with a "tags" key containing a list of tags names.
- 404: A JSON object with an "error" key containing the message "Repository not found" if the repository does not exist.
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
log.error("Can't get tags 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)
tags = []
for tag in git_repo.tags:
tags = tags + [tag.name]
log.info("Returning %i tags", len(tags))
return {
"tags": tags
}
@git_ns.route('/repositories/<repo>/tags/<tag>')
class GitRepoTagsDeleter(Resource):
def delete(self, repo, tag):
"""Delete a given tag in a given repository
Args:
repo (str): The name of the repository.
Returns:
Response: A JSON response containing a list of tag 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 "Tag not found"
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
log.error("Can't get tags 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 tag in git_repo.tags:
log.error("Can't delete tag %s, not found in repository %s", tag, repo, extra = {"repository" : repo, "tag" : tag})
return {"error": "Tag not found"}, 404
git_repo.delete_head(tag)
log.info("Tag %s of repository %s deleted", tag, repo, extra = {"repository" : repo, "tag" : tag})
return {"status": "deleted"}, 200
def post(self, repo, tag):
"""Create a given tag in a given repository
Args:
repo (str): The name of the repository.
Returns:
Response: A JSON response containing a creation status
- 200: A JSON object with a "status" key containing "created"
- 404: A JSON object with an "error" key containing the message "Repository not found"
- 409: A JSON object with an "error" key containing the message "Tag already exists"
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
log.error("Can't get tags 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)
data = request.json
if data is None:
log.error("Can't create tag, JSON post data missing")
return {"error" : "Parameters missing"}, 400
if not "commit" in data:
log.error("Can't create tag, commit parameter missing")
return {"error" : "commit parameter missing"}, 400
if tag in git_repo.tags:
log.error("Can't create tag %s, already found in repository %s", tag, repo, extra = {"repository" : repo, "tag" : tag})
return {"error": "Tag already exists"}, 409
git_repo.create_tag(tag, ref = data["commit"])
log.info("Tag %s of repository %s created", tag, repo, extra = {"repository" : repo, "tag" : tag})
return {"status": "created"}, 200
@git_ns.route('/health')
class GitHealth(Resource):
def get(self):
"""
Health check endpoint.
This endpoint returns a JSON response indicating the health status of the application.
Returns:
Response: A JSON response with a status key set to "OK". Currently it always returns
a successful value, but this endpoint can still be used to check that the API is
active and functional.
"""
log.info("Health check endpoint called")
return {
"status": "OK"
}
@git_ns.route('/status')
class GitStatus(Resource):
def get(self):
"""
Status check endpoint.
This endpoint returns a JSON response indicating the status of the application.
Returns:
Response: A JSON response with status information
"""
log.info("Status endpoint called")
return {
"uptime" : time.time() - start_time,
"active_tasks" : len(tasks)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
oggit (0.5) UNRELEASED; urgency=medium
* Initial release.
-- OpenGnsys <opengnsys@opengnsys.es> Fri, 14 Mar 2025 08:40:35 +0100

View File

@ -0,0 +1,38 @@
Source: oggit
Section: unknown
Priority: optional
Maintainer: OpenGnsys <opengnsys@opengnsys.es>
Rules-Requires-Root: no
Build-Depends:
debhelper-compat (= 13),
Standards-Version: 4.6.2
Homepage: https://opengnsys.es
#Vcs-Browser: https://salsa.debian.org/debian/ogboot
#Vcs-Git: https://salsa.debian.org/debian/ogboot.git
Package: oggit
Architecture: any
Multi-Arch: foreign
Depends:
${shlibs:Depends},
${misc:Depends},
bsdextrautils,
debconf (>= 1.5.0),
gunicorn,
opengnsys-flask-executor,
opengnsys-flask-restx,
opengnsys-libarchive-c,
python3,
python3-aniso8601,
python3-flasgger,
python3-flask,
python3-flask,
python3-git,
python3-paramiko,
python3-requests,
python3-termcolor,
python3-tqdm,
opengnsys-forgejo (>= 0.5)
Conflicts:
Description: Opengnsys Oggit package
Files for OpenGnsys Git support

View File

@ -0,0 +1,43 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Source: <url://example.com>
Upstream-Name: ogboot
Upstream-Contact: <preferred name and address to reach the upstream project>
Files:
*
Copyright:
<years> <put author's name and email here>
<years> <likewise for another author>
License: GPL-3.0+
Files:
debian/*
Copyright:
2025 vagrant <vagrant@build>
License: GPL-3.0+
License: GPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Comment:
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
# Please also look if there are files or directories which have a
# different copyright/license attached and list them here.
# Please avoid picking licenses with terms that are more restrictive than the
# packaged work, as it may make Debian's contributions unacceptable upstream.
#
# If you need, there are some extra license texts available in two places:
# /usr/share/debhelper/dh_make/licenses/
# /usr/share/common-licenses/

View File

@ -0,0 +1,3 @@
README.source
README.Debian
README

View File

@ -0,0 +1,3 @@
/opt/opengnsys/images/git
/opt/opengnsys/ogrepository/oggit
/opt/opengnsys/ogrepository/oggit/api

View File

@ -0,0 +1,6 @@
api_server.py /opt/opengnsys/ogrepository/oggit/api
../installer/opengnsys_git_installer.py /opt/opengnsys/oggit/bin
blueprints/gitapi.py /opt/opengnsys/ogrepository/oggit/api/blueprints
blueprints/repo_api.py /opt/opengnsys/ogrepository/oggit/api/blueprints
opengnsys-repository-api.service /etc/systemd/system

View File

@ -0,0 +1,3 @@
#!/bin/bash
/usr/bin/systemctl daemon-reload
/usr/bin/systemctl enable --now opengnsys-repository-api

View File

@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=

View File

@ -0,0 +1,12 @@
Package: oggit
Version: 0.5
Architecture: amd64
Maintainer: OpenGnsys <opengnsys@opengnsys.es>
Installed-Size: 193
Depends: bsdextrautils, debconf (>= 1.5.0), gunicorn, opengnsys-flask-executor, opengnsys-flask-restx, opengnsys-libarchive-c, python3, python3-aniso8601, python3-flasgger, python3-flask, python3-git, python3-paramiko, python3-requests, python3-termcolor, python3-tqdm
Section: unknown
Priority: optional
Multi-Arch: foreign
Homepage: https://opengnsys.es
Description: Opengnsys Oggit package
Files for OpenGnsys Git support

View File

@ -0,0 +1,6 @@
1a0024adb1d5e54ecff27759c5ac4a7d opt/opengnsys/oggit/bin/api_server.py
bd0a968737c2d62ce44490414426ccbb opt/opengnsys/oggit/bin/opengnsys_git_installer.py
af5f26474949def90af8794458f3f08d opt/opengnsys/oggit/blueprints/gitapi.py
61618848e4caca8b22e3cc7b9c8706b8 opt/opengnsys/oggit/blueprints/repo_api.py
48b531f72dec218fcdd61dce26f6b5ab usr/share/doc/oggit/changelog.gz
8a13e4a3eb6149d56094319bbed84d0c usr/share/doc/oggit/copyright

View File

@ -0,0 +1,139 @@
#!/usr/bin/env python3
import os
import sys
sys.path.insert(0, "/usr/share/opengnsys-modules/python3/dist-packages")
import importlib
import logging
import uuid
import argparse
from flask import Flask, request
from flask_executor import Executor
from flask_restx import Api
from werkzeug.exceptions import HTTPException
from systemd.journal import JournalHandler
parser = argparse.ArgumentParser(
prog="api_server.py",
description="OpenGnsys Repository API Server",
)
debug_enabled = False
listen_host = '0.0.0.0'
parser.add_argument('--debug', action='store_true', help="Enable debug output")
parser.add_argument('--listen', metavar="HOST", help="Listen address")
parser.add_argument("-v", "--verbose", action="store_true", help = "Verbose console output")
args = parser.parse_args()
log = logging.getLogger('api_server')
log.addHandler(JournalHandler())
if args.verbose:
log.addHandler(logging.StreamHandler(stream=sys.stderr))
log.setLevel(logging.DEBUG)
else:
log.setLevel(logging.INFO)
if args.listen:
listen_host = args.listen
if args.debug:
debug_enabled = True
api_base_dir = os.path.dirname(os.path.realpath(__file__))
blueprints_dir = os.path.join(api_base_dir, 'blueprints')
installer_dir = os.path.join(api_base_dir, '../installer')
sys.path.insert(0, installer_dir)
# Create an instance of the Flask class
app = Flask(__name__)
api = Api(app,
version='0.50',
title = "OpenGnsys Git API",
description = "API for managing disk images stored in Git",
doc = "/swagger/")
executor = Executor(app)
log.info("Loading blueprints from %s", blueprints_dir)
sys.path.insert(0, blueprints_dir)
for filename in os.listdir(blueprints_dir):
if filename.endswith('.py'):
log.info("Loading %s/%s", blueprints_dir, filename)
module_name = filename.replace(".py", "")
log.info("Importing %s", module_name)
importlib.invalidate_caches()
module = importlib.import_module(module_name)
log.debug("Returned: %s", module)
app.register_blueprint(module.blueprint)
@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 })
return response, 500
@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
# Run the Flask app
if __name__ == '__main__':
print(f"Map: {app.url_map}")
app.run(debug=debug_enabled, host=listen_host)

View File

@ -0,0 +1,573 @@
#!/usr/bin/env python3
"""
This module provides a Flask-based API for managing Git repositories in the OpenGnsys system.
It includes endpoints for creating, deleting, synchronizing, backing up, and performing garbage
collection on Git repositories. The API also provides endpoints for retrieving repository
information such as the list of repositories and branches, as well as checking the status of
asynchronous tasks.
Classes:
None
Functions:
do_repo_backup(repo, params)
do_repo_sync(repo, params)
do_repo_gc(repo)
home()
get_repositories()
create_repo(repo)
sync_repo(repo)
backup_repository(repo)
gc_repo(repo)
tasks_status(task_id)
delete_repo(repo)
get_repository_branches(repo)
health_check()
Constants:
REPOSITORIES_BASE_PATH (str): The base path where Git repositories are stored.
Global Variables:
app (Flask): The Flask application instance.
executor (Executor): The Flask-Executor instance for managing asynchronous tasks.
tasks (dict): A dictionary to store the status of asynchronous tasks.
"""
# pylint: disable=locally-disabled, line-too-long
import os.path
import os
import shutil
import uuid
import time
import logging
import traceback
import git
from opengnsys_git_installer import OpengnsysGitInstaller
from flask import Blueprint, request
from flask_restx import Resource, Api
import paramiko
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
blueprint = Blueprint('git_api', __name__, template_folder='templates', url_prefix = '/oggit/v1')
api = Api(blueprint)
git_ns = api
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):
"""
Creates a backup of the specified Git repository and uploads it to a remote server via SFTP.
Args:
repo (str): The name of the repository to back up.
params (dict): A dictionary containing the following keys:
- ssh_server (str): The SSH server address.
- ssh_port (int): The SSH server port.
- ssh_user (str): The SSH username.
- filename (str): The remote filename where the backup will be stored.
Returns:
bool: True if the backup was successful.
"""
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())
ssh.connect(params["ssh_server"], params["ssh_port"], params["ssh_user"])
sftp = ssh.open_sftp()
with sftp.file(params["filename"], mode='wb+') as remote_file:
git_repo.archive(remote_file, format="tar.gz")
return True
def do_repo_sync(repo, params):
"""
Synchronizes a local Git repository with a remote repository.
Args:
repo (str): The name of the local repository to synchronize.
params (dict): A dictionary containing the remote repository URL with the key "remote_repository".
Returns:
list: A list of dictionaries, each containing:
- "local_ref" (str): The name of the local reference.
- "remote_ref" (str): The name of the remote reference.
- "summary" (str): A summary of the push operation for the reference.
"""
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 git_repo.remotes:
git_repo.delete_remote("backup")
backup_repo = git_repo.create_remote("backup", params["remote_repository"])
pushed_references = backup_repo.push("*:*")
results = []
# This gets returned to the API
for ref in pushed_references:
results = results + [ {"local_ref" : ref.local_ref.name, "remote_ref" : ref.remote_ref.name, "summary" : ref.summary }]
return results
def do_repo_gc(repo):
"""
Perform garbage collection on the specified Git repository.
Args:
repo (str): The name of the repository to perform garbage collection on.
Returns:
bool: True if the garbage collection command was executed successfully.
"""
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)
git_repo.git.gc()
# Define a route for the root URL
@api.route('/')
class GitLib(Resource):
#@api.doc('home')
def get(self):
"""
Home route that returns a JSON response with a welcome message for the OpenGnsys Git API.
Returns:
Response: A Flask JSON response containing a welcome message.
"""
log.info("Root URL accessed")
return {
"message": "OpenGnsys Git API"
}
@git_ns.route('/repositories')
class GitRepositories(Resource):
def get(self):
"""
Retrieve a list of Git repositories.
This endpoint scans the OpenGnsys image path for directories that
appear to be Git repositories (i.e., they contain a "HEAD" file).
It returns a JSON response containing the names of these repositories.
Returns:
Response: A JSON response with a list of repository names or an
error message if the repository storage is not found.
- 200 OK: When the repositories are successfully retrieved.
- 500 Internal Server Error: When the repository storage is not found.
Example JSON response:
{
"repositories": ["repo1", "repo2"]
}
"""
if not os.path.isdir(REPOSITORIES_BASE_PATH):
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):
if entry.is_dir(follow_symlinks=False) and os.path.isfile(os.path.join(entry.path, "HEAD")):
name = entry.name
if name.endswith(".git"):
name = name[:-4]
repos = repos + [name]
log.info("Returning %i repositories", len(repos))
return {
"repositories": repos
}
def post(self):
"""
Create a new Git repository.
This endpoint creates a new Git repository with the specified name.
If the repository already exists, it returns a status message indicating so.
Args:
repo (str): The name of the repository to be created.
Returns:
Response: A JSON response with a status message and HTTP status code.
- 200: If the repository already exists.
- 201: If the repository is successfully created.
"""
data = request.json
if data is None:
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):
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()
installer.add_forgejo_repo(repo)
#installer.init_git_repo(repo + ".git")
log.info("Repository %s created", repo, extra = {"repository" : repo})
return {"status": "Repository created"}, 201
@git_ns.route('/repositories/<repo>/sync')
class GitRepoSync(Resource):
def post(self, repo):
"""
Synchronize a repository with a remote repository.
This endpoint triggers the synchronization process for a specified repository.
It expects a JSON payload with the remote repository details.
Args:
repo (str): The name of the repository to be synchronized.
Returns:
Response: A JSON response indicating the status of the synchronization process.
- 200: If the synchronization process has started successfully.
- 400: If the request payload is missing or invalid.
- 404: If the specified repository is not found.
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
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:
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 = 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('/repositories/<repo>/backup')
class GitRepoBackup(Resource):
def backup_repository(self, repo):
"""
Backup a specified repository.
Endpoint: POST /repositories/<repo>/backup
Args:
repo (str): The name of the repository to back up.
Request Body (JSON):
ssh_port (int, optional): The SSH port to use for the backup. Defaults to 22.
Returns:
Response: A JSON response indicating the status of the backup operation.
- If the repository is not found, returns a 404 error with a message.
- If the request body is missing, returns a 400 error with a message.
- If the backup process starts successfully, returns a 200 status with the task ID.
Notes:
- The repository path is constructed by appending ".git" to the repository name.
- The backup operation is performed asynchronously using a thread pool executor.
- The task ID of the backup operation is generated using UUID and stored in a global tasks dictionary.
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
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:
log.error("Can't create repository, JSON post data missing")
return {"error" : "Parameters missing"}, 400
if not "ssh_port" in data:
data["ssh_port"] = 22
future = executor.submit(do_repo_backup, repo, data)
task_id = add_task(future)
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('/repositories/<repo>/compact', methods=['POST'])
class GitRepoCompact(Resource):
def post(self, repo):
"""
Initiates a garbage collection (GC) process for a specified Git repository.
This endpoint triggers an asynchronous GC task for the given repository.
The task is submitted to an executor, and a unique task ID is generated
and returned to the client.
Args:
repo (str): The name of the repository to perform GC on.
Returns:
Response: A JSON response containing the status of the request and
a unique task ID if the repository is found, or an error
message if the repository is not found.
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
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 = add_task(future)
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('/tasks/<task_id>/status')
class GitTaskStatus(Resource):
def get(self, task_id):
"""
Endpoint to check the status of a specific task.
Args:
task_id (str): The unique identifier of the task.
Returns:
Response: A JSON response containing the status of the task.
- If the task is not found, returns a 404 error with an error message.
- If the task is completed, returns a 200 status with the result.
- If the task is still in progress, returns a 202 status indicating the task is in progress.
"""
if not task_id in tasks:
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"]
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('/repositories/<repo>', methods=['DELETE'])
class GitRepo(Resource):
def delete(self, repo):
"""
Deletes a Git repository.
This endpoint deletes a Git repository specified by the `repo` parameter.
If the repository does not exist, it returns a 404 error with a message
indicating that the repository was not found. If the repository is successfully
deleted, it returns a 200 status with a message indicating that the repository
was deleted.
Args:
repo (str): The name of the repository to delete.
Returns:
Response: A JSON response with a status message and the appropriate HTTP status code.
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git")
if not os.path.isdir(repo_path):
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)
log.info("Deleted repository %s", repo, extra = {"repository" : repo})
return {"status": "Repository deleted"}, 200
@git_ns.route('/repositories/<repo>/branches')
class GitRepoBranches(Resource):
def get(self, repo):
"""
Retrieve the list of branches for 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 "branches" key containing a list of branch names.
- 404: A JSON object with an "error" key containing the message "Repository not found" if the repository does not exist.
"""
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)
branches = []
for branch in git_repo.branches:
branches = branches + [branch.name]
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')
class GitHealth(Resource):
def get(self):
"""
Health check endpoint.
This endpoint returns a JSON response indicating the health status of the application.
Returns:
Response: A JSON response with a status key set to "OK". Currently it always returns
a successful value, but this endpoint can still be used to check that the API is
active and functional.
"""
log.info("Health check endpoint called")
return {
"status": "OK"
}
@git_ns.route('/status')
class GitStatus(Resource):
def get(self):
"""
Status check endpoint.
This endpoint returns a JSON response indicating the status of the application.
Returns:
Response: A JSON response with status information
"""
log.info("Status endpoint called")
return {
"uptime" : time.time() - start_time,
"active_tasks" : len(tasks)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Source: <url://example.com>
Upstream-Name: ogboot
Upstream-Contact: <preferred name and address to reach the upstream project>
Files:
*
Copyright:
<years> <put author's name and email here>
<years> <likewise for another author>
License: GPL-3.0+
Files:
debian/*
Copyright:
2025 vagrant <vagrant@build>
License: GPL-3.0+
License: GPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Comment:
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
# Please also look if there are files or directories which have a
# different copyright/license attached and list them here.
# Please avoid picking licenses with terms that are more restrictive than the
# packaged work, as it may make Debian's contributions unacceptable upstream.
#
# If you need, there are some extra license texts available in two places:
# /usr/share/debhelper/dh_make/licenses/
# /usr/share/common-licenses/

View File

@ -0,0 +1,33 @@
#!/usr/bin/make -f
# See debhelper(7) (uncomment to enable).
# Output every command that modifies files on the build system.
#export DH_VERBOSE = 1
# See FEATURE AREAS in dpkg-buildflags(1).
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# See ENVIRONMENT in dpkg-buildflags(1).
# Package maintainers to append CFLAGS.
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
# Package maintainers to append LDFLAGS.
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
%:
dh $@
%:
dh $@
# Ejecutar composer install durante la fase de construcción
override_dh_auto_build:
# dh_make generated override targets.
# This is an example for Cmake (see <https://bugs.debian.org/641051>).
#override_dh_auto_configure:
# dh_auto_configure -- \
# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)

View File

@ -0,0 +1,33 @@
#!/bin/bash
#!/bin/bash
set -e
if [ ! -f "/etc/apt/sources.list.d/opengnsys.sources" ] ; then
cat > /etc/apt/sources.list.d/opengnsys.sources <<HERE
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-----
HERE
fi
apt update
apt install -y python3-git opengnsys-libarchive-c python3-termcolor python3-requests python3-tqdm bsdextrautils python3-paramiko python3-aniso8601 opengnsys-flask-restx opengnsys-flask-executor python3-flask python3-psutil

View File

@ -0,0 +1,11 @@
[Service]
RestartSec=10s
Type=simple
User=oggit
Group=oggit
WorkingDirectory=/opt/opengnsys/ogrepository/oggit/api/
ExecStart=/usr/bin/gunicorn -w 4 -b 0.0.0.0:8006 api_server:app
Restart=always
[Install]
WantedBy=multi-user.target

View File

@ -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

View File

@ -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/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

View File

@ -0,0 +1,656 @@
opengnsys-gitinstaller (0.5dev3) UNRELEASED; urgency=medium
[ OpenGnsys ]
* Initial release.
[ Vadim Troshchinskiy ]
* First commit
* Add installer
* Add requirements file
[ lgromero ]
* refs #734 Creates first skeleton of symfony+swagger project
[ Vadim Troshchinskiy ]
* Add Gitlib
[ lgromero ]
* refs #734 Changes OgBootBundle name and adds a first endpoint to test
* refs #734 Adds template of repository and branch endpoints
[ Vadim Troshchinskiy ]
* Update docs to account for changes
* Trivial API server
* Ticket #753: Add repository listing
* Ticket #735: List branches in repo
* Add testing instructions
* Agregar manejo de errrores
* Ticket #741: Crear repo Ticket #736: Eliminar repo
[ lgromero ]
* refs #734 Adds README for Api installation
* refs #734 Control of errores and http codes in controler
* refs #734 Renemas oggitservice
[ Vadim Troshchinskiy ]
* Ticket #738, ticket #739: repo and sync backup protoype
[ lgromero ]
* refs #734 Adds new endpoints sync and backup and status endpoint
* refs #734 Adds nelmio api doc configuration
* Adds .env file to root
* refs #734 use environment variables in .env files and disable web depuration toolbar
* refs #734 fix typo in .env and use oggit_url environment variable
[ Vadim Troshchinskiy ]
* Ticket #738, ticket #739: git sync and backup
[ Nicolas Arenas ]
* Add docker container files
[ Vadim Troshchinskiy ]
* Ticket #737: GC
* Use Paramiko and Gitpython for backups
[ Nicolas Arenas ]
* Add mock api for testing dockerfile
[ Vadim Troshchinskiy ]
* Ticket #740, listen on all hosts
[ lgromero ]
* refs #734 Removes innecesaries parameters and changes php platform to 8.2
* refs #734 just changes name and description in swagger web page
[ Vadim Troshchinskiy ]
* Remove duplicated import
* Documentation prototype
* Update to 24.04, solves deployment issue
* Add more documentation
* Add API README
* Add API examples
* Update list of package requirements in oglive
* Fix commandline parsing bug
* Revert experimental Windows change
* Fix ticket #770: Re-parse filesystems list after mounting
* Use oglive server if ogrepository is not set
* Ticket #770: Add sanity check
* Ticket #771: Correctly create directories on metadata restoration
* Ticket #780: Unmount before clone if needed
* Fix ticket #800: sudo doesn't work
[ Vadim Trochinsky ]
* Fix ticket #802: .git directory in filesystem root
[ Vadim Troshchinskiy ]
* Fix ticket #805: Remove .git directory if it already exists when checking out
* Ticket #770: Correctly update metadata when mounting and unmounting
* Ticket #804: Move log
* Fix ticket #902: .git directories can't be checked out
* Lint fixes
* Remove unused code
* Lint fixes
* Lint fixes
* Lint fixes
* Additional logging message
* Lint fix
* Fix ticket #907: mknod fails due to path not found
* Initial implementation for commit, push, fetch.
* Don't fail on empty lines in metadata, just skip them
* Add documentation and functionality to progress hook (not used yet)
* Pylint fixes
* Ticket #908: Remove some unneeded warnings
* Fix progress report
* Ticket #906: Fix permissions on directories
* Make pylint happy
* Mount fix
* Ticket #808: Initial implementation
* Initial forgejo install
* Deduplicate key extraction
* Fix installer bugs and add documentation
* Change user to oggit
* Fix NTFS ID modification implementation
* Implement system-specific EFI data support
* Fix encoding when reading system uuid
* Fix and refactor slightly EFI implementation
* Add Windows BCD decoding tool
* Check module loading and unloading, modprobe works on oglive now
* Make EFI deployment more flexible
* Add organization API call
* Fix bash library path
* Fix repo paths for forgejo
* Update documentation
* Sync to ensure everything is written
* Refactoring and more pydoc
* Add more documentation
* Improve installer documentation
* Improve gitlib instructions
* Add missing files
* Partial setsshkey implementation
* Fix SSH key generation and extraction
* Initial package contents
* Add Debian packaging
* Add pylkid
* Add pyblkid debian files
* Use packaged pyblkid
* More detailed API logging
* Improve logging
* Add oglive key to forgejo
* Add original source
* Always re-download forgejo, even if installed.
* Remove obsolete code that stopped being relevant with Forgejo
* Move python modules to /opt/opengnsys-modules
* Use absolute paths in initrd modification
* Add timestamp to ssh key title, forgejo doesn't like duplicates
* Skip past symlinks and problems in oglive modification
* Get keys from squashfs instead of initrd to work with current oglive packaging
* Fix trivial bug
* Move modules to /usr/share/opengnsys
* Move packages to /usr/share
[ Angel Rodriguez ]
* Add gitlib/README-en.md
* Add api/README-en.md
* Add installer/README-en.md
[ Vadim Troshchinskiy ]
* Skip NTFS code on non-Windows
* Store and restore GPT partition UUIDs
* Update READMEs
* BCD constants
* Use tqdm
* Constants
* Add extra mounts update
* Better status reports
* Make log filename machine-dependent Move kernel args parsing
* Make unmounting more robust
* Improve repository initialization
* Make --pull work like the other commands
* Add packages
* Update documentation
* Ignore python cache
* Ignore more files
* add python libarchive-c original package
* Add pyblkid copyright file
* Add make_orig script
* Reorder and fix for ogrepository reorganization
* Restructure git installer to work without ogboot on the same machine, update docs
* Update english documentation
* Improve installation process, make it possible to extract keys from oglive
* Fix namespaces
* Fix ogrepository paths
* Change git repo path
* Improvements for logging and error handling
* Fix HTTP exception handling
* Improve task management, cleanup when there are too many
* More error logging
* Mark git repo as a safe directory
* Rework the ability to use a custom SSH key
* Log every request
* Branch deletion
* Make branch deletion RESTful
* Initial version of the API server
* Add original repo_api
* Convert to blueprint
* Add port argument
* Fix error handling
* Add README
* Load swagger from disk
* Fix repository URL
* Bump forgejo version
* Add helpful script
* Fix port argument
* Refactoring for package support
* Remove old code
* Refactoring for packaging
* opengnsys-forgejo package
* Fix post-install for forgejo deployment
* Fixes for running under gunicorn
* Debian packaging
* Add branches and tags creation endpoints
* Add missing file
* Rename service
* Add templates
* Disable tests
* Fix permission problem
* Fix ini path
* Update changelog
* Update changelog
* Add package files
* Add git image creation script
* Slightly improve API for ogrepo usability
* First commit
* Add installer
* Add requirements file
[ lgromero ]
* refs #734 Creates first skeleton of symfony+swagger project
[ Vadim Troshchinskiy ]
* Add Gitlib
[ lgromero ]
* refs #734 Changes OgBootBundle name and adds a first endpoint to test
* refs #734 Adds template of repository and branch endpoints
[ Vadim Troshchinskiy ]
* Update docs to account for changes
* Trivial API server
* Ticket #753: Add repository listing
* Ticket #735: List branches in repo
* Add testing instructions
* Agregar manejo de errrores
* Ticket #741: Crear repo Ticket #736: Eliminar repo
[ lgromero ]
* refs #734 Adds README for Api installation
* refs #734 Control of errores and http codes in controler
* refs #734 Renemas oggitservice
[ Vadim Troshchinskiy ]
* Ticket #738, ticket #739: repo and sync backup protoype
[ lgromero ]
* refs #734 Adds new endpoints sync and backup and status endpoint
* refs #734 Adds nelmio api doc configuration
* Adds .env file to root
* refs #734 use environment variables in .env files and disable web depuration toolbar
* refs #734 fix typo in .env and use oggit_url environment variable
[ Vadim Troshchinskiy ]
* Ticket #738, ticket #739: git sync and backup
[ Nicolas Arenas ]
* Add docker container files
[ Vadim Troshchinskiy ]
* Ticket #737: GC
* Use Paramiko and Gitpython for backups
[ Nicolas Arenas ]
* Add mock api for testing dockerfile
[ Vadim Troshchinskiy ]
* Ticket #740, listen on all hosts
[ lgromero ]
* refs #734 Removes innecesaries parameters and changes php platform to 8.2
* refs #734 just changes name and description in swagger web page
[ Vadim Troshchinskiy ]
* Remove duplicated import
* Documentation prototype
* Update to 24.04, solves deployment issue
* Add more documentation
* Add API README
* Add API examples
* Update list of package requirements in oglive
* Fix commandline parsing bug
* Revert experimental Windows change
* Fix ticket #770: Re-parse filesystems list after mounting
* Use oglive server if ogrepository is not set
* Ticket #770: Add sanity check
* Ticket #771: Correctly create directories on metadata restoration
* Ticket #780: Unmount before clone if needed
* Fix ticket #800: sudo doesn't work
[ Vadim Trochinsky ]
* Fix ticket #802: .git directory in filesystem root
[ Vadim Troshchinskiy ]
* Fix ticket #805: Remove .git directory if it already exists when checking out
* Ticket #770: Correctly update metadata when mounting and unmounting
* Ticket #804: Move log
* Fix ticket #902: .git directories can't be checked out
* Lint fixes
* Remove unused code
* Lint fixes
* Lint fixes
* Lint fixes
* Additional logging message
* Lint fix
* Fix ticket #907: mknod fails due to path not found
* Initial implementation for commit, push, fetch.
* Don't fail on empty lines in metadata, just skip them
* Add documentation and functionality to progress hook (not used yet)
* Pylint fixes
* Ticket #908: Remove some unneeded warnings
* Fix progress report
* Ticket #906: Fix permissions on directories
* Make pylint happy
* Mount fix
* Ticket #808: Initial implementation
* Initial forgejo install
* Deduplicate key extraction
* Fix installer bugs and add documentation
* Change user to oggit
* Fix NTFS ID modification implementation
* Implement system-specific EFI data support
* Fix encoding when reading system uuid
* Fix and refactor slightly EFI implementation
* Add Windows BCD decoding tool
* Check module loading and unloading, modprobe works on oglive now
* Make EFI deployment more flexible
* Add organization API call
* Fix bash library path
* Fix repo paths for forgejo
* Update documentation
* Sync to ensure everything is written
* Refactoring and more pydoc
* Add more documentation
* Improve installer documentation
* Improve gitlib instructions
* Add missing files
* Partial setsshkey implementation
* Fix SSH key generation and extraction
* Initial package contents
* Add Debian packaging
* Add pylkid
* Add pyblkid debian files
* Use packaged pyblkid
* More detailed API logging
* Improve logging
* Add oglive key to forgejo
* Add original source
* Always re-download forgejo, even if installed.
* Remove obsolete code that stopped being relevant with Forgejo
* Move python modules to /opt/opengnsys-modules
* Use absolute paths in initrd modification
* Add timestamp to ssh key title, forgejo doesn't like duplicates
* Skip past symlinks and problems in oglive modification
* Get keys from squashfs instead of initrd to work with current oglive packaging
* Fix trivial bug
* Move modules to /usr/share/opengnsys
* Move packages to /usr/share
[ Angel Rodriguez ]
* Add gitlib/README-en.md
* Add api/README-en.md
* Add installer/README-en.md
[ Vadim Troshchinskiy ]
* Skip NTFS code on non-Windows
* Store and restore GPT partition UUIDs
* Update READMEs
* BCD constants
* Use tqdm
* Constants
* Add extra mounts update
* Better status reports
* Make log filename machine-dependent Move kernel args parsing
* Make unmounting more robust
* Improve repository initialization
* Make --pull work like the other commands
* Add packages
* Update documentation
* Ignore python cache
* Ignore more files
* add python libarchive-c original package
* Add pyblkid copyright file
* Add make_orig script
* Reorder and fix for ogrepository reorganization
* Restructure git installer to work without ogboot on the same machine, update docs
* Update english documentation
* Improve installation process, make it possible to extract keys from oglive
* Fix namespaces
* Fix ogrepository paths
* Change git repo path
* Improvements for logging and error handling
* Fix HTTP exception handling
* Improve task management, cleanup when there are too many
* More error logging
* Mark git repo as a safe directory
* Rework the ability to use a custom SSH key
* Log every request
* Branch deletion
* Make branch deletion RESTful
* Initial version of the API server
* Add original repo_api
* Convert to blueprint
* Add port argument
* Fix error handling
* Add README
* Load swagger from disk
* Fix repository URL
* Bump forgejo version
* Add helpful script
* Fix port argument
* Refactoring for package support
* Remove old code
* Refactoring for packaging
* opengnsys-forgejo package
* Fix post-install for forgejo deployment
* Fixes for running under gunicorn
* Debian packaging
* Add branches and tags creation endpoints
* Add missing file
* Rename service
* Add templates
* Disable tests
* Fix permission problem
* Fix ini path
* Update changelog
* Update changelog
* Add package files
* Add git image creation script
* Slightly improve API for ogrepo usability
* Update changelog
* First commit
* Add installer
* Add requirements file
[ lgromero ]
* refs #734 Creates first skeleton of symfony+swagger project
[ Vadim Troshchinskiy ]
* Add Gitlib
[ lgromero ]
* refs #734 Changes OgBootBundle name and adds a first endpoint to test
* refs #734 Adds template of repository and branch endpoints
[ Vadim Troshchinskiy ]
* Update docs to account for changes
* Trivial API server
* Ticket #753: Add repository listing
* Ticket #735: List branches in repo
* Add testing instructions
* Agregar manejo de errrores
* Ticket #741: Crear repo Ticket #736: Eliminar repo
[ lgromero ]
* refs #734 Adds README for Api installation
* refs #734 Control of errores and http codes in controler
* refs #734 Renemas oggitservice
[ Vadim Troshchinskiy ]
* Ticket #738, ticket #739: repo and sync backup protoype
[ lgromero ]
* refs #734 Adds new endpoints sync and backup and status endpoint
* refs #734 Adds nelmio api doc configuration
* Adds .env file to root
* refs #734 use environment variables in .env files and disable web depuration toolbar
* refs #734 fix typo in .env and use oggit_url environment variable
[ Vadim Troshchinskiy ]
* Ticket #738, ticket #739: git sync and backup
[ Nicolas Arenas ]
* Add docker container files
[ Vadim Troshchinskiy ]
* Ticket #737: GC
* Use Paramiko and Gitpython for backups
[ Nicolas Arenas ]
* Add mock api for testing dockerfile
[ Vadim Troshchinskiy ]
* Ticket #740, listen on all hosts
[ lgromero ]
* refs #734 Removes innecesaries parameters and changes php platform to 8.2
* refs #734 just changes name and description in swagger web page
[ Vadim Troshchinskiy ]
* Remove duplicated import
* Documentation prototype
* Update to 24.04, solves deployment issue
* Add more documentation
* Add API README
* Add API examples
* Update list of package requirements in oglive
* Fix commandline parsing bug
* Revert experimental Windows change
* Fix ticket #770: Re-parse filesystems list after mounting
* Use oglive server if ogrepository is not set
* Ticket #770: Add sanity check
* Ticket #771: Correctly create directories on metadata restoration
* Ticket #780: Unmount before clone if needed
* Fix ticket #800: sudo doesn't work
[ Vadim Trochinsky ]
* Fix ticket #802: .git directory in filesystem root
[ Vadim Troshchinskiy ]
* Fix ticket #805: Remove .git directory if it already exists when checking out
* Ticket #770: Correctly update metadata when mounting and unmounting
* Ticket #804: Move log
* Fix ticket #902: .git directories can't be checked out
* Lint fixes
* Remove unused code
* Lint fixes
* Lint fixes
* Lint fixes
* Additional logging message
* Lint fix
* Fix ticket #907: mknod fails due to path not found
* Initial implementation for commit, push, fetch.
* Don't fail on empty lines in metadata, just skip them
* Add documentation and functionality to progress hook (not used yet)
* Pylint fixes
* Ticket #908: Remove some unneeded warnings
* Fix progress report
* Ticket #906: Fix permissions on directories
* Make pylint happy
* Mount fix
* Ticket #808: Initial implementation
* Initial forgejo install
* Deduplicate key extraction
* Fix installer bugs and add documentation
* Change user to oggit
* Fix NTFS ID modification implementation
* Implement system-specific EFI data support
* Fix encoding when reading system uuid
* Fix and refactor slightly EFI implementation
* Add Windows BCD decoding tool
* Check module loading and unloading, modprobe works on oglive now
* Make EFI deployment more flexible
* Add organization API call
* Fix bash library path
* Fix repo paths for forgejo
* Update documentation
* Sync to ensure everything is written
* Refactoring and more pydoc
* Add more documentation
* Improve installer documentation
* Improve gitlib instructions
* Add missing files
* Partial setsshkey implementation
* Fix SSH key generation and extraction
* Initial package contents
* Add Debian packaging
* Add pylkid
* Add pyblkid debian files
* Use packaged pyblkid
* More detailed API logging
* Improve logging
* Add oglive key to forgejo
* Add original source
* Always re-download forgejo, even if installed.
* Remove obsolete code that stopped being relevant with Forgejo
* Move python modules to /opt/opengnsys-modules
* Use absolute paths in initrd modification
* Add timestamp to ssh key title, forgejo doesn't like duplicates
* Skip past symlinks and problems in oglive modification
* Get keys from squashfs instead of initrd to work with current oglive packaging
* Fix trivial bug
* Move modules to /usr/share/opengnsys
* Move packages to /usr/share
[ Angel Rodriguez ]
* Add gitlib/README-en.md
* Add api/README-en.md
* Add installer/README-en.md
[ Vadim Troshchinskiy ]
* Skip NTFS code on non-Windows
* Store and restore GPT partition UUIDs
* Update READMEs
* BCD constants
* Use tqdm
* Constants
* Add extra mounts update
* Better status reports
* Make log filename machine-dependent Move kernel args parsing
* Make unmounting more robust
* Improve repository initialization
* Make --pull work like the other commands
* Add packages
* Update documentation
* Ignore python cache
* Ignore more files
* add python libarchive-c original package
* Add pyblkid copyright file
* Add make_orig script
* Reorder and fix for ogrepository reorganization
* Restructure git installer to work without ogboot on the same machine, update docs
* Update english documentation
* Improve installation process, make it possible to extract keys from oglive
* Fix namespaces
* Fix ogrepository paths
* Change git repo path
* Improvements for logging and error handling
* Fix HTTP exception handling
* Improve task management, cleanup when there are too many
* More error logging
* Mark git repo as a safe directory
* Rework the ability to use a custom SSH key
* Log every request
* Branch deletion
* Make branch deletion RESTful
* Initial version of the API server
* Add original repo_api
* Convert to blueprint
* Add port argument
* Fix error handling
* Add README
* Load swagger from disk
* Fix repository URL
* Bump forgejo version
* Add helpful script
* Fix port argument
* Refactoring for package support
* Remove old code
* Refactoring for packaging
* opengnsys-forgejo package
* Fix post-install for forgejo deployment
* Fixes for running under gunicorn
* Debian packaging
* Add branches and tags creation endpoints
* Add missing file
* Rename service
* Add templates
* Disable tests
* Fix permission problem
* Fix ini path
* Update changelog
* Update changelog
* Add package files
* Add git image creation script
* Slightly improve API for ogrepo usability
* Update changelog
* Update changelog
-- OpenGnsys <opengnsys@opengnsys.com> Mon, 16 Jun 2025 21:23:34 +0000

View File

@ -0,0 +1,29 @@
Source: opengnsys-gitinstaller
Section: unknown
Priority: optional
Maintainer: OpenGnsys <opengnsys@opengnsys.es>
Rules-Requires-Root: no
Build-Depends:
debhelper-compat (= 13),
Standards-Version: 4.6.2
Homepage: https://opengnsys.es
#Vcs-Browser: https://salsa.debian.org/debian/ogboot
#Vcs-Git: https://salsa.debian.org/debian/ogboot.git
Package: opengnsys-gitinstaller
Architecture: any
Multi-Arch: foreign
Depends:
${shlibs:Depends},
${misc:Depends},
bsdextrautils,
debconf (>= 1.5.0),
opengnsys-libarchive-c,
python3,
python3-aniso8601,
python3-git,
python3-termcolor,
python3-tqdm
Conflicts:
Description: Opengnsys installer library for OgGit
Files for OpenGnsys Git support

View File

@ -0,0 +1,43 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Source: <url://example.com>
Upstream-Name: ogboot
Upstream-Contact: <preferred name and address to reach the upstream project>
Files:
*
Copyright:
<years> <put author's name and email here>
<years> <likewise for another author>
License: GPL-3.0+
Files:
debian/*
Copyright:
2025 vagrant <vagrant@build>
License: GPL-3.0+
License: GPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Comment:
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
# Please also look if there are files or directories which have a
# different copyright/license attached and list them here.
# Please avoid picking licenses with terms that are more restrictive than the
# packaged work, as it may make Debian's contributions unacceptable upstream.
#
# If you need, there are some extra license texts available in two places:
# /usr/share/debhelper/dh_make/licenses/
# /usr/share/common-licenses/

View File

@ -0,0 +1,2 @@
opengnsys-gitinstaller_0.5_amd64.buildinfo unknown optional
opengnsys-gitinstaller_0.5_amd64.deb unknown optional

View File

@ -0,0 +1 @@
/opt/opengnsys/ogrepository/oggit/lib

View File

@ -0,0 +1 @@
opengnsys_git_installer.py /opt/opengnsys/ogrepository/oggit/lib

View File

@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=

View File

@ -0,0 +1,33 @@
#!/usr/bin/make -f
# See debhelper(7) (uncomment to enable).
# Output every command that modifies files on the build system.
#export DH_VERBOSE = 1
# See FEATURE AREAS in dpkg-buildflags(1).
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# See ENVIRONMENT in dpkg-buildflags(1).
# Package maintainers to append CFLAGS.
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
# Package maintainers to append LDFLAGS.
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
%:
dh $@
%:
dh $@
# Ejecutar composer install durante la fase de construcción
override_dh_auto_build:
# dh_make generated override targets.
# This is an example for Cmake (see <https://bugs.debian.org/641051>).
#override_dh_auto_configure:
# dh_auto_configure -- \
# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)

View File

@ -0,0 +1,11 @@
[Service]
RestartSec=10s
Type=simple
User={gitapi_user}
Group={gitapi_group}
WorkingDirectory={gitapi_work_path}
ExecStart=/usr/bin/gunicorn -w 4 -b {gitapi_host}:{gitapi_port} gitapi:app
Restart=always
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,31 @@
#!/bin/bash
set -e
if [ ! -f "/etc/apt/sources.list.d/opengnsys.sources" ] ; then
cat > /etc/apt/sources.list.d/opengnsys.sources <<HERE
Types: deb
URIs: https://ognproject.evlt.uma.es/debian-opengnsys/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-----
HERE
fi
apt update
apt install -y python3-git opengnsys-libarchive-c python3-termcolor python3-requests python3-tqdm bsdextrautils

View File

@ -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="10.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,19 +209,46 @@ 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"
self.opengnsys_bin_path = os.path.join(self.base_path, "bin")
self.opengnsys_etc_path = os.path.join(self.base_path, "etc")
self.forgejo_user = "oggit"
self.forgejo_password = "opengnsys"
self.forgejo_organization = "opengnsys"
self.forgejo_port = 3000
self.forgejo_port = 3100
self.forgejo_bin_path = os.path.join(self.ogrepository_base_path, "bin")
self.forgejo_exe = os.path.join(self.forgejo_bin_path, "forgejo")
self.forgejo_conf_dir_path = os.path.join(self.ogrepository_base_path, "etc", "forgejo")
self.lfs_dir_path = os.path.join(self.ogrepository_base_path, "oggit", "git-lfs")
self.git_dir_path = os.path.join(self.ogrepository_base_path, "oggit", "git")
self.forgejo_var_dir_path = os.path.join(self.ogrepository_base_path, "var", "lib", "forgejo")
self.forgejo_work_dir_path = os.path.join(self.forgejo_var_dir_path, "work")
self.forgejo_work_custom_dir_path = os.path.join(self.forgejo_work_dir_path, "custom")
self.forgejo_db_dir_path = os.path.join(self.forgejo_var_dir_path, "db")
self.forgejo_data_dir_path = os.path.join(self.forgejo_var_dir_path, "data")
self.forgejo_db_path = os.path.join(self.forgejo_db_dir_path, "forgejo.db")
self.forgejo_log_dir_path = os.path.join(self.ogrepository_base_path, "log", "forgejo")
self.dependencies = ["git", "python3-flask", "python3-flasgger", "gunicorn", ]
self.set_ssh_user_group("oggit", "oggit")
self.temp_dir = None
self.script_path = os.path.realpath(os.path.dirname(__file__))
# Where we look for forgejo-app.ini and similar templates.
self.template_path = self.script_path
# Possible names for SSH public keys
self.ssh_key_users = ["root", "opengnsys"]
self.key_names = ["id_rsa.pub", "id_ed25519.pub", "id_ecdsa.pub", "id_ed25519_sk.pub", "id_ecdsa_sk.pub"]
@ -147,10 +261,14 @@ 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()
def set_testmode(self, value):
"""Establece el modo de prueba"""
self.testmode = value
@ -159,10 +277,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 +332,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 +408,60 @@ 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 = self.extract_ssh_keys_from_squashfs(oglive_num = oglive_num, squashfs_file=squashfs_file)
retvals = []
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})")
retvals = retvals + [self.add_forgejo_sshkey(k, f"Key for {name} ({timestamp})")]
return retvals
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 +484,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 +692,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,32 +727,11 @@ 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")
subprocess.run(["apt-get", "install", "-y", "git"], check=True)
self.__logger.debug("Installing dependencies")
subprocess.run(["apt-get", "install", "-y"] + self.dependencies, check=True)
def _install_template(self, template, destination, keysvalues):
@ -587,7 +742,10 @@ class OpengnsysGitInstaller:
data = template_file.read()
for key in keysvalues.keys():
data = data.replace("{" + key + "}", keysvalues[key])
if isinstance(keysvalues[key], int):
data = data.replace("{" + key + "}", str(keysvalues[key]))
else:
data = data.replace("{" + key + "}", keysvalues[key])
with open(destination, "w+", encoding="utf-8") as out_file:
out_file.write(data)
@ -598,88 +756,112 @@ class OpengnsysGitInstaller:
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")
def install_api(self):
self.__logger.info("Installing Git API")
opengnsys_bin_path = os.path.join(self.base_path, "bin")
opengnsys_etc_path = os.path.join(self.base_path, "etc")
pathlib.Path(opengnsys_bin_path).mkdir(parents=True, exist_ok=True)
data = {
"gitapi_user" : "opengnsys",
"gitapi_group" : "opengnsys",
"gitapi_host" : "0.0.0.0",
"gitapi_port" : 8087,
"gitapi_work_path" : opengnsys_bin_path
}
shutil.copy("../api/gitapi.py", opengnsys_bin_path + "/gitapi.py")
shutil.copy("opengnsys_git_installer.py", opengnsys_bin_path + "/opengnsys_git_installer.py")
self._install_template(os.path.join(self.template_path, "gitapi.service"), "/etc/systemd/system/gitapi.service", data)
bin_path = os.path.join(self.base_path, "bin", "forgejo")
conf_dir_path = os.path.join(self.base_path, "etc", "forgejo")
self.__logger.debug("Reloading systemd and starting service")
subprocess.run(["systemctl", "daemon-reload"], check=True)
subprocess.run(["systemctl", "enable", "gitapi"], check=True)
subprocess.run(["systemctl", "restart", "gitapi"], check=True)
lfs_dir_path = os.path.join(self.base_path, "images", "git-lfs")
git_dir_path = os.path.join(self.base_path, "images", "git")
def _get_forgejo_data(self):
conf_path = os.path.join(self.forgejo_conf_dir_path, "app.ini")
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")
data = {
"forgejo_user" : self.ssh_user,
"forgejo_group" : self.ssh_group,
"forgejo_port" : str(self.forgejo_port),
"forgejo_bin" : self.forgejo_exe,
"forgejo_app_ini" : conf_path,
"forgejo_work_path" : self.forgejo_work_dir_path,
"forgejo_data_path" : self.forgejo_data_dir_path,
"forgejo_db_path" : self.forgejo_db_path,
"forgejo_repository_root" : self.git_dir_path,
"forgejo_lfs_path" : self.lfs_dir_path,
"forgejo_log_path" : self.forgejo_log_dir_path,
"forgejo_hostname" : self._runcmd("hostname"),
"forgejo_lfs_jwt_secret" : self._runcmd([self.forgejo_exe,"generate", "secret", "LFS_JWT_SECRET"]),
"forgejo_jwt_secret" : self._runcmd([self.forgejo_exe,"generate", "secret", "JWT_SECRET"]),
"forgejo_internal_token" : self._runcmd([self.forgejo_exe,"generate", "secret", "INTERNAL_TOKEN"]),
"forgejo_secret_key" : self._runcmd([self.forgejo_exe,"generate", "secret", "SECRET_KEY"])
}
forgejo_db_path = os.path.join(forgejo_db_dir_path, "forgejo.db")
return data
forgejo_log_dir_path = os.path.join(self.base_path, "log", "forgejo")
def install_forgejo(self, download=True):
self.__logger.info("Installing Forgejo version %s", FORGEJO_VERSION)
conf_path = os.path.join(self.forgejo_conf_dir_path, "app.ini")
conf_path = os.path.join(conf_dir_path, "app.ini")
self.__logger.info("Stopping opengnsys-forgejo service. This may cause a harmless warning.")
self.__logger.debug("Stopping opengnsys-forgejo service")
subprocess.run(["systemctl", "stop", "opengnsys-forgejo"], check=False)
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)
os.chmod(bin_path, 0o755)
self.__logger.debug("Downloading from %s into %s", FORGEJO_URL, self.forgejo_exe)
pathlib.Path(self.forgejo_bin_path).mkdir(parents=True, exist_ok=True)
if os.path.exists(forgejo_db_path):
with open(self.forgejo_exe, "wb") as forgejo_bin:
download_with_progress(FORGEJO_URL, forgejo_bin)
os.chmod(self.forgejo_exe, 0o755)
if os.path.exists(self.forgejo_db_path):
self.__logger.debug("Removing old configuration")
os.unlink(forgejo_db_path)
os.unlink(self.forgejo_db_path)
else:
self.__logger.debug("Old configuration not present, ok.")
self.__logger.debug("Wiping old data")
for dir in [conf_dir_path, git_dir_path, lfs_dir_path, forgejo_work_dir_path, forgejo_data_dir_path, forgejo_db_dir_path]:
for dir in [self.forgejo_conf_dir_path, self.git_dir_path, self.lfs_dir_path, self.forgejo_work_dir_path, self.forgejo_data_dir_path, self.forgejo_db_dir_path]:
if os.path.exists(dir):
self.__logger.debug("Removing %s", dir)
shutil.rmtree(dir)
self.__logger.debug("Creating directories")
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)
pathlib.Path(forgejo_work_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(forgejo_data_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)
pathlib.Path(self.opengnsys_etc_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.forgejo_conf_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.git_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.lfs_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.forgejo_work_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.forgejo_data_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.forgejo_db_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.forgejo_log_dir_path).mkdir(parents=True, exist_ok=True)
os.chown(lfs_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(git_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(forgejo_data_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)
os.chown(self.lfs_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(self.git_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(self.forgejo_data_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(self.forgejo_work_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(self.forgejo_db_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(self.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_data_path" : forgejo_data_dir_path,
"forgejo_db_path" : forgejo_db_path,
"forgejo_repository_root" : git_dir_path,
"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"])
}
data = self._get_forgejo_data()
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._install_template(os.path.join(self.template_path, "forgejo-app.ini"), conf_path, data)
self._install_template(os.path.join(self.template_path, "opengnsys-forgejo.service"), "/etc/systemd/system/opengnsys-forgejo.service", data)
self.__logger.debug("Reloading systemd and starting service")
@ -694,7 +876,7 @@ class OpengnsysGitInstaller:
self.__logger.info("Configuring forgejo")
def run_forge_cmd(args):
cmd = [bin_path, "--config", conf_path] + args
cmd = [self.forgejo_exe, "--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)
@ -715,10 +897,80 @@ class OpengnsysGitInstaller:
with open(os.path.join(self.base_path, "etc", "ogGitApiToken.cfg"), "w+", encoding='utf-8') as token_file:
token_file.write(token)
def configure_forgejo(self):
data = self._get_forgejo_data()
self.__logger.debug("Creating directories")
ssh_key = self._extract_ssh_key_from_initrd()
pathlib.Path(self.opengnsys_etc_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.forgejo_conf_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.git_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.lfs_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.forgejo_work_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.forgejo_work_custom_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.forgejo_data_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.forgejo_db_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.forgejo_log_dir_path).mkdir(parents=True, exist_ok=True)
self.add_forgejo_sshkey(ssh_key, "Default key")
os.chown(self.lfs_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(self.git_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(self.forgejo_data_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(self.forgejo_work_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(self.forgejo_db_dir_path, self.ssh_uid, self.ssh_gid)
os.chown(self.forgejo_log_dir_path, self.ssh_uid, self.ssh_gid)
conf_path = os.path.join(self.forgejo_conf_dir_path, "app.ini")
self._install_template(os.path.join(self.template_path, "forgejo-app.ini"), conf_path, data)
self._install_template(os.path.join(self.template_path, "opengnsys-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, ignore_errors = []):
cmd = [self.forgejo_exe, "--config", conf_path] + args
self.__logger.info("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.strip())
self.__logger.error("stderr: %s", ret.stderr.strip())
for err in ignore_errors:
if err in ret.stderr:
self.__logger.info("Ignoring error, it's in the ignore list")
return ret.stdout.strip()
raise RuntimeError("Failed to run necessary command")
run_forge_cmd(["migrate"])
run_forge_cmd(["admin", "doctor", "check"])
run_forge_cmd(["admin", "user", "create", "--username", self.forgejo_user, "--password", self.forgejo_password, "--email", self.email], ignore_errors=["user already exists"])
token = run_forge_cmd(["admin", "user", "generate-access-token", "--username", self.forgejo_user, "-t", "gitapi", "--scopes", "all", "--raw"], ignore_errors = ["access token name has been used already"])
if token:
with open(os.path.join(self.base_path, "etc", "ogGitApiToken.cfg"), "w+", encoding='utf-8') as token_file:
token_file.write(token)
else:
self.__logger.info("Keeping the old token")
def add_forgejo_repo(self, repository_name, description = ""):
@ -764,6 +1016,7 @@ class OpengnsysGitInstaller:
)
self.__logger.info("Request status was %i, content %s", r.status_code, r.content)
return r.status_code, r.content.decode('utf-8')
def add_forgejo_organization(self, pubkey, description = ""):
token = ""
@ -799,8 +1052,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)
@ -815,6 +1067,16 @@ if __name__ == '__main__':
logger.addHandler(fileLog)
if "postinst" in os.path.basename(__file__):
logger.info("Running as post-install script")
installer=OpengnsysGitInstaller()
# Templates get installed here
installer.template_path = "/usr/share/opengnsys-forgejo/"
installer.configure_forgejo()
sys.exit(0)
parser = argparse.ArgumentParser(
prog="OpenGnsys Installer",
description="Script para la instalación del repositorio git",
@ -824,15 +1086,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 +1118,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 +1129,40 @@ 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_api()
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)

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
import time
sys.path.insert(0, "/opt/oglive/rootfs/opt/opengnsys/lib/python3/")
sys.path.insert(0, "/opt/opengnsys/interfaceAdm/git/")
sys.path.insert(0, "/opt/opengnsys/ogrepository/oggit/lib/")
import NetLib
import ogGlobals
import SystemLib
from gitlib import OpengnsysGitLibrary, NTFSImplementation
def create_image(disk_num, partition_num, repo, image_name):
ntfs_impl = NTFSImplementation.NTFS3G
og_git = OpengnsysGitLibrary(ntfs_implementation = ntfs_impl)
device = og_git._runBashFunction("ogDiskToDev", [str(disk_num), str(partition_num)])
og_git.initRepo(device, image_name)
def main():
if len(sys.argv) != 6:
sys.exit(SystemLib.ogRaiseError(OG_ERR_FORMAT, "Incorrect number of arguments"))
disk_num, partition_num, image_name, repo, tag = sys.argv[1:6]
retval = create_image(disk_num, partition_num, repo, image_name)
sys.exit(retval)
if __name__ == "__main__":
main()

View File

@ -18,5 +18,8 @@ override_dh_gencontrol:
override_dh_installdocs:
# Nothing, we don't want docs
override_dh_auto_test:
# Nothing
#
override_dh_installchangelogs:
# Nothing, we don't want the changelog

View File

@ -0,0 +1,220 @@
opengnsys-forgejo (0.5.1dev1) UNRELEASED; urgency=medium
[ OpenGnsys ]
* Fix config path
*
[ Vadim Troshchinskiy ]
* First commit
* Add installer
* Add requirements file
[ lgromero ]
* refs #734 Creates first skeleton of symfony+swagger project
[ Vadim Troshchinskiy ]
* Add Gitlib
[ lgromero ]
* refs #734 Changes OgBootBundle name and adds a first endpoint to test
* refs #734 Adds template of repository and branch endpoints
[ Vadim Troshchinskiy ]
* Update docs to account for changes
* Trivial API server
* Ticket #753: Add repository listing
* Ticket #735: List branches in repo
* Add testing instructions
* Agregar manejo de errrores
* Ticket #741: Crear repo Ticket #736: Eliminar repo
[ lgromero ]
* refs #734 Adds README for Api installation
* refs #734 Control of errores and http codes in controler
* refs #734 Renemas oggitservice
[ Vadim Troshchinskiy ]
* Ticket #738, ticket #739: repo and sync backup protoype
[ lgromero ]
* refs #734 Adds new endpoints sync and backup and status endpoint
* refs #734 Adds nelmio api doc configuration
* Adds .env file to root
* refs #734 use environment variables in .env files and disable web depuration toolbar
* refs #734 fix typo in .env and use oggit_url environment variable
[ Vadim Troshchinskiy ]
* Ticket #738, ticket #739: git sync and backup
[ Nicolas Arenas ]
* Add docker container files
[ Vadim Troshchinskiy ]
* Ticket #737: GC
* Use Paramiko and Gitpython for backups
[ Nicolas Arenas ]
* Add mock api for testing dockerfile
[ Vadim Troshchinskiy ]
* Ticket #740, listen on all hosts
[ lgromero ]
* refs #734 Removes innecesaries parameters and changes php platform to 8.2
* refs #734 just changes name and description in swagger web page
[ Vadim Troshchinskiy ]
* Remove duplicated import
* Documentation prototype
* Update to 24.04, solves deployment issue
* Add more documentation
* Add API README
* Add API examples
* Update list of package requirements in oglive
* Fix commandline parsing bug
* Revert experimental Windows change
* Fix ticket #770: Re-parse filesystems list after mounting
* Use oglive server if ogrepository is not set
* Ticket #770: Add sanity check
* Ticket #771: Correctly create directories on metadata restoration
* Ticket #780: Unmount before clone if needed
* Fix ticket #800: sudo doesn't work
[ Vadim Trochinsky ]
* Fix ticket #802: .git directory in filesystem root
[ Vadim Troshchinskiy ]
* Fix ticket #805: Remove .git directory if it already exists when checking out
* Ticket #770: Correctly update metadata when mounting and unmounting
* Ticket #804: Move log
* Fix ticket #902: .git directories can't be checked out
* Lint fixes
* Remove unused code
* Lint fixes
* Lint fixes
* Lint fixes
* Additional logging message
* Lint fix
* Fix ticket #907: mknod fails due to path not found
* Initial implementation for commit, push, fetch.
* Don't fail on empty lines in metadata, just skip them
* Add documentation and functionality to progress hook (not used yet)
* Pylint fixes
* Ticket #908: Remove some unneeded warnings
* Fix progress report
* Ticket #906: Fix permissions on directories
* Make pylint happy
* Mount fix
* Ticket #808: Initial implementation
* Initial forgejo install
* Deduplicate key extraction
* Fix installer bugs and add documentation
* Change user to oggit
* Fix NTFS ID modification implementation
* Implement system-specific EFI data support
* Fix encoding when reading system uuid
* Fix and refactor slightly EFI implementation
* Add Windows BCD decoding tool
* Check module loading and unloading, modprobe works on oglive now
* Make EFI deployment more flexible
* Add organization API call
* Fix bash library path
* Fix repo paths for forgejo
* Update documentation
* Sync to ensure everything is written
* Refactoring and more pydoc
* Add more documentation
* Improve installer documentation
* Improve gitlib instructions
* Add missing files
* Partial setsshkey implementation
* Fix SSH key generation and extraction
* Initial package contents
* Add Debian packaging
* Add pylkid
* Add pyblkid debian files
* Use packaged pyblkid
* More detailed API logging
* Improve logging
* Add oglive key to forgejo
* Add original source
* Always re-download forgejo, even if installed.
* Remove obsolete code that stopped being relevant with Forgejo
* Move python modules to /opt/opengnsys-modules
* Use absolute paths in initrd modification
* Add timestamp to ssh key title, forgejo doesn't like duplicates
* Skip past symlinks and problems in oglive modification
* Get keys from squashfs instead of initrd to work with current oglive packaging
* Fix trivial bug
* Move modules to /usr/share/opengnsys
* Move packages to /usr/share
[ Angel Rodriguez ]
* Add gitlib/README-en.md
* Add api/README-en.md
* Add installer/README-en.md
[ Vadim Troshchinskiy ]
* Skip NTFS code on non-Windows
* Store and restore GPT partition UUIDs
* Update READMEs
* BCD constants
* Use tqdm
* Constants
* Add extra mounts update
* Better status reports
* Make log filename machine-dependent Move kernel args parsing
* Make unmounting more robust
* Improve repository initialization
* Make --pull work like the other commands
* Add packages
* Update documentation
* Ignore python cache
* Ignore more files
* add python libarchive-c original package
* Add pyblkid copyright file
* Add make_orig script
* Reorder and fix for ogrepository reorganization
* Restructure git installer to work without ogboot on the same machine, update docs
* Update english documentation
* Improve installation process, make it possible to extract keys from oglive
* Fix namespaces
* Fix ogrepository paths
* Change git repo path
* Improvements for logging and error handling
* Fix HTTP exception handling
* Improve task management, cleanup when there are too many
* More error logging
* Mark git repo as a safe directory
* Rework the ability to use a custom SSH key
* Log every request
* Branch deletion
* Make branch deletion RESTful
* Initial version of the API server
* Add original repo_api
* Convert to blueprint
* Add port argument
* Fix error handling
* Add README
* Load swagger from disk
* Fix repository URL
* Bump forgejo version
* Add helpful script
* Fix port argument
* Refactoring for package support
* Remove old code
* Refactoring for packaging
* opengnsys-forgejo package
* Fix post-install for forgejo deployment
* Fixes for running under gunicorn
* Debian packaging
* Add branches and tags creation endpoints
* Add missing file
* Rename service
* Add templates
* Disable tests
* Fix permission problem
* Fix ini path
* Update changelog
-- OpenGnsys <opengnsys@opengnsys.com> Thu, 05 Jun 2025 21:46:30 +0000

View File

@ -0,0 +1,37 @@
Source: opengnsys-forgejo
Section: unknown
Priority: optional
Maintainer: OpenGnsys <opengnsys@opengnsys.es>
Rules-Requires-Root: no
Build-Depends:
debhelper-compat (= 13),
Standards-Version: 4.6.2
Homepage: https://opengnsys.es
#Vcs-Browser: https://salsa.debian.org/debian/ogboot
#Vcs-Git: https://salsa.debian.org/debian/ogboot.git
Package: opengnsys-forgejo
Architecture: any
Multi-Arch: foreign
Depends:
${shlibs:Depends},
${misc:Depends},
bsdextrautils,
debconf (>= 1.5.0),
gunicorn,
opengnsys-flask-executor,
opengnsys-flask-restx,
opengnsys-libarchive-c,
python3,
python3-aniso8601,
python3-flasgger,
python3-flask,
python3-flask,
python3-git,
python3-paramiko,
python3-requests,
python3-termcolor,
python3-tqdm
Conflicts:
Description: Opengnsys Forgejo package for OgGit
Files for OpenGnsys Git support

View File

@ -0,0 +1,43 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Source: <url://example.com>
Upstream-Name: ogboot
Upstream-Contact: <preferred name and address to reach the upstream project>
Files:
*
Copyright:
<years> <put author's name and email here>
<years> <likewise for another author>
License: GPL-3.0+
Files:
debian/*
Copyright:
2025 vagrant <vagrant@build>
License: GPL-3.0+
License: GPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Comment:
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
# Please also look if there are files or directories which have a
# different copyright/license attached and list them here.
# Please avoid picking licenses with terms that are more restrictive than the
# packaged work, as it may make Debian's contributions unacceptable upstream.
#
# If you need, there are some extra license texts available in two places:
# /usr/share/debhelper/dh_make/licenses/
# /usr/share/common-licenses/

View File

@ -0,0 +1,2 @@
opengnsys-forgejo_0.5_amd64.buildinfo unknown optional
opengnsys-forgejo_0.5_amd64.deb unknown optional

View File

@ -0,0 +1,2 @@
/opt/opengnsys/oggit/bin
/opt/opengnsys/ogrepository/etc/forgejo/

View File

@ -0,0 +1,3 @@
forgejo /opt/opengnsys/ogrepository/bin
forgejo-app.ini /usr/share/opengnsys-forgejo/
opengnsys-forgejo.service /usr/share/opengnsys-forgejo/

View File

@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=

View File

@ -0,0 +1,25 @@
Template: opengnsys/forgejo_organization
Type: string
Default: opegnsys
Description: Organizacion de Forgejo
Template: opengnsys/forgejo_user
Type: string
Default: oggit
Description: Usuario de oggit Forgejo
Template: opengnsys/forgejo_password
Type: password
Default: opegnsys
Description: Password de cuenta de oggit de Forgejo
Template: opengnsys/forgejo_email
Type: string
Default: opegnsys@opengnsys.com
Description: Email de cuenta de oggit de Forgejo
Template: opengnsys/forgejo_port
Type: string
Default: 3100
Description: Puerto TCP de Forgejo

View File

@ -0,0 +1,43 @@
#!/usr/bin/env python3
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_data_path" : forgejo_data_dir_path,
"forgejo_db_path" : forgejo_db_path,
"forgejo_repository_root" : git_dir_path,
"forgejo_lfs_path" : lfs_dir_path,
"forgejo_log_path" : forgejo_log_dir_path,
"forgejo_hostname" : _runcmd("hostname"),
"forgejo_lfs_jwt_secret" : _runcmd([bin_path,"generate", "secret", "LFS_JWT_SECRET"]),
"forgejo_jwt_secret" : _runcmd([bin_path,"generate", "secret", "JWT_SECRET"]),
"forgejo_internal_token" : _runcmd([bin_path,"generate", "secret", "INTERNAL_TOKEN"]),
"forgejo_secret_key" : _runcmd([bin_path,"generate", "secret", "SECRET_KEY"])
}
ini_template = "/usr/share/opengnsys-forgejo/forgejo-app.ini"
def _install_template(self, template, destination, keysvalues):
data = ""
with open(template, "r", encoding="utf-8") as template_file:
data = template_file.read()
for key in keysvalues.keys():
if isinstance(keysvalues[key], int):
data = data.replace("{" + key + "}", str(keysvalues[key]))
else:
data = data.replace("{" + key + "}", keysvalues[key])
with open(destination, "w+", encoding="utf-8") as out_file:
out_file.write(data)
_install_template(os.path.join(self.script_path, "forgejo-app.ini"), conf_path, data)

View File

@ -0,0 +1,41 @@
#!/usr/bin/make -f
# See debhelper(7) (uncomment to enable).
# Output every command that modifies files on the build system.
#export DH_VERBOSE = 1
# See FEATURE AREAS in dpkg-buildflags(1).
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# See ENVIRONMENT in dpkg-buildflags(1).
# Package maintainers to append CFLAGS.
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
# Package maintainers to append LDFLAGS.
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
%:
dh $@
%:
dh $@
# Ejecutar composer install durante la fase de construcción
override_dh_auto_build:
cp -v ../../installer/opengnsys_git_installer.py debian/opengnsys-forgejo.postinst
override_dh_auto_install:
dh_auto_install
mkdir -p debian/opengnsys-forgejo/opt/opengnsys/ogrepository/var/lib/forgejo
mkdir -p debian/opengnsys-forgejo/opt/opengnsys/ogrepository/var/lib/forgejo/work
# fails under fakeroot for some reason, fix in postinst
# chown -R oggit:oggit debian/opengnsys-forgejo/opt/opengnsys/ogrepository/var/lib/forgejo
# dh_make generated override targets.
# This is an example for Cmake (see <https://bugs.debian.org/641051>).
#override_dh_auto_configure:
# dh_auto_configure -- \
# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)

View File

@ -0,0 +1,6 @@
#!/bin/bash
VERSION=10.0.1
wget https://codeberg.org/forgejo/forgejo/releases/download/v${VERSION}/forgejo-${VERSION}-linux-amd64 -O forgejo
chmod 755 forgejo

View File

@ -0,0 +1,78 @@
APP_NAME = OpenGnsys Git
APP_SLOGAN =
RUN_USER = {forgejo_user}
WORK_PATH = {forgejo_work_path}
RUN_MODE = prod
[database]
DB_TYPE = sqlite3
HOST = 127.0.0.1:3306
NAME = forgejo
USER = forgejo
PASSWD =
SCHEMA =
SSL_MODE = disable
PATH = {forgejo_db_path}
LOG_SQL = false
[repository]
ROOT = {forgejo_repository_root}
[server]
SSH_DOMAIN = og-admin
DOMAIN = og-admin
HTTP_PORT = {forgejo_port}
ROOT_URL = http://{forgejo_hostname}:{forgejo_port}/
APP_DATA_PATH = {forgejo_data_path}
DISABLE_SSH = false
SSH_PORT = 22
LFS_START_SERVER = true
LFS_JWT_SECRET = {forgejo_lfs_jwt_secret}
OFFLINE_MODE = true
[lfs]
PATH = {forgejo_lfs_path}
[mailer]
ENABLED = false
[service]
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
DISABLE_REGISTRATION = true
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
ENABLE_CAPTCHA = false
REQUIRE_SIGNIN_VIEW = false
DEFAULT_KEEP_EMAIL_PRIVATE = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING = true
NO_REPLY_ADDRESS = noreply.localhost
[openid]
ENABLE_OPENID_SIGNIN = true
ENABLE_OPENID_SIGNUP = true
[cron.update_checker]
ENABLED = true
[session]
PROVIDER = file
[log]
MODE = console
LEVEL = info
ROOT_PATH = {forgejo_log_path} #/tmp/log
[repository.pull-request]
DEFAULT_MERGE_STYLE = merge
[repository.signing]
DEFAULT_TRUST_MODEL = committer
[security]
INSTALL_LOCK = true
INTERNAL_TOKEN = {forgejo_internal_token}
PASSWORD_HASH_ALGO = pbkdf2_hi
[oauth2]
JWT_SECRET = {forgejo_jwt_secret}

View File

@ -0,0 +1,11 @@
[Service]
RestartSec=10s
Type=simple
User=oggit
Group=oggit
WorkingDirectory=/opt/opengnsys/ogrepository/var/lib/forgejo/work
ExecStart=/opt/opengnsys/ogrepository/bin/forgejo web --config /opt/opengnsys/ogrepository/etc/forgejo/app.ini
Restart=always
[Install]
WantedBy=multi-user.target

View File

@ -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

View File

@ -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.

View File

@ -0,0 +1,2 @@
Tests: upstream-tests
Depends: @, python3-mock, python3-pytest

View File

@ -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