Compare commits

...

21 Commits

Author SHA1 Message Date
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
11 changed files with 775 additions and 183 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

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

@ -28,13 +28,28 @@ import requests
import tempfile
import hashlib
import datetime
import tqdm
#FORGEJO_VERSION="8.0.3"
FORGEJO_VERSION="9.0.0"
FORGEJO_VERSION="9.0.3"
FORGEJO_URL=f"https://codeberg.org/forgejo/forgejo/releases/download/v{FORGEJO_VERSION}/forgejo-{FORGEJO_VERSION}-linux-amd64"
def download_with_progress(url, output_file):
with requests.get(url, stream=True, timeout=60) as req:
progress = tqdm.tqdm()
progress.total = int(req.headers["Content-Length"])
progress.unit_scale = True
progress.desc = "Downloading"
for chunk in req.iter_content(chunk_size=8192):
output_file.write(chunk)
progress.n = progress.n + len(chunk)
progress.refresh()
progress.close()
def show_error(*args):
"""
@ -65,6 +80,23 @@ class RequirementException(Exception):
super().__init__(message)
self.message = message
class OptionalDependencyException(Exception):
"""Excepción que indica que nos falta algún requisito opcional
Attributes:
message (str): Mensaje de error mostrado al usuario
"""
def __init__(self, message):
"""Inicializar OptionalDependencyException.
Args:
message (str): Mensaje de error mostrado al usuario
"""
super().__init__(message)
self.message = message
class FakeTemporaryDirectory:
"""Imitación de TemporaryDirectory para depuración"""
def __init__(self, dirname):
@ -74,6 +106,57 @@ class FakeTemporaryDirectory:
def __str__(self):
return self.name
class OgliveMounter:
"""
A class to handle mounting of Oglive images from a given URL or local file.
Attributes:
logger (logging.Logger): Logger instance for logging messages.
squashfs (str): Path to the squashfs file within the mounted Oglive image.
initrd (str): Path to the initrd image within the mounted Oglive image.
kernel (str): Path to the kernel image within the mounted Oglive image.
Methods:
__init__(url):
Initializes the OgliveMounter instance, downloads the Oglive image if URL is provided,
and mounts the image to a temporary directory.
__del__():
Unmounts the mounted directory and cleans up resources.
"""
def __init__(self, url):
self.logger = logging.getLogger("OgliveMounter")
self.mountdir = tempfile.TemporaryDirectory()
self.logger.info("Will mount oglive found at %s", url)
if url.startswith("http://") or url.startswith("https://"):
self.logger.debug("We got an URL, downloading %s", url)
self.tempfile = tempfile.NamedTemporaryFile(mode='wb')
filename = self.tempfile.name
download_with_progress(url, self.tempfile)
else:
self.logger.debug("We got a filename")
filename = url
self.logger.debug("Mounting %s at %s", filename, self.mountdir.name)
subprocess.run(["/usr/bin/mount", filename, self.mountdir.name], check=True)
self.squashfs = os.path.join(self.mountdir.name, "ogclient", "ogclient.sqfs")
self.initrd = os.path.join(self.mountdir.name, "ogclient", "oginitrd.img")
self.kernel = os.path.join(self.mountdir.name, "ogclient", "ogvmlinuz")
def __del__(self):
self.logger.debug("Unmounting directory %s", self.mountdir.name)
subprocess.run(["/usr/bin/umount", self.mountdir.name], check=True)
class Oglive:
"""Interfaz a utilidad oglivecli
@ -88,6 +171,10 @@ class Oglive:
def _cmd(self, args):
cmd = [self.binary] + args
if not os.path.exists(self.binary):
raise OptionalDependencyException("Missing oglivecli command. Please use --squashfs-file (see README.md for more details)")
self.__logger.debug("comando: %s", cmd)
proc = subprocess.run(cmd, shell=False, check=True, capture_output=True)
@ -122,6 +209,7 @@ class OpengnsysGitInstaller:
self.__logger.debug("Inicializando")
self.testmode = False
self.base_path = "/opt/opengnsys"
self.ogrepository_base_path = os.path.join(self.base_path, "ogrepository")
self.git_basedir = "base.git"
self.email = "OpenGnsys@opengnsys.com"
@ -147,6 +235,7 @@ class OpengnsysGitInstaller:
for kp in self.key_paths:
self.key_paths_dict[kp] = 1
os.environ["PATH"] += os.pathsep + os.path.join(self.base_path, "bin")
self.oglive = Oglive()
@ -159,10 +248,6 @@ class OpengnsysGitInstaller:
"""Ignorar requisito de clave de ssh para el instalador"""
self.ignoresshkey = value
def set_usesshkey(self, value):
"""Usar clave de ssh especificada"""
self.usesshkey = value
def set_basepath(self, value):
"""Establece ruta base de OpenGnsys
Valor por defecto: /opt/opengnsys
@ -218,7 +303,7 @@ class OpengnsysGitInstaller:
def init_git_repo(self, reponame):
"""Inicializa un repositorio Git"""
# Creamos repositorio
ogdir_images = os.path.join(self.base_path, "images")
ogdir_images = os.path.join(self.ogrepository_base_path, "oggit")
self.__logger.info("Creando repositorio de GIT %s", reponame)
os.makedirs(os.path.join(ogdir_images, self.git_basedir), exist_ok=True)
@ -294,42 +379,58 @@ class OpengnsysGitInstaller:
raise TimeoutError("Timed out waiting for connection!")
def add_ssh_key_from_squashfs(self, oglive_num = None):
def add_ssh_key_from_squashfs(self, oglive_num = None, squashfs_file = None, oglive_file = None):
name = "(unknown)"
mounter = None
if not oglive_file is None:
mounter = OgliveMounter(oglive_file)
squashfs_file = mounter.squashfs
if squashfs_file is None:
if oglive_num is None:
self.__logger.info("Using default oglive")
oglive_num = self.oglive.get_default()
else:
self.__logger.info("Using oglive %i", oglive_num)
name = self.oglive.get_clients()[str(oglive_num)]
if oglive_num is None:
self.__logger.info("Using default oglive")
oglive_num = int(self.oglive.get_default())
else:
self.__logger.info("Using oglive %i", oglive_num)
self.__logger.info("Using specified squashfs file %s", squashfs_file)
name = os.path.basename(squashfs_file)
oglive_client = self.oglive.get_clients()[str(oglive_num)]
self.__logger.info("Oglive is %s", oglive_client)
keys = installer.extract_ssh_keys(oglive_num = oglive_num)
keys = installer.extract_ssh_keys_from_squashfs(oglive_num = oglive_num, squashfs_file=squashfs_file)
for k in keys:
timestamp = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now())
installer.add_forgejo_sshkey(k, f"Key for {oglive_client} ({timestamp})")
installer.add_forgejo_sshkey(k, f"Key for {name} ({timestamp})")
def extract_ssh_keys(self, oglive_num = None):
def extract_ssh_keys_from_squashfs(self, oglive_num = None, squashfs_file = None):
public_keys = []
squashfs = "ogclient.sqfs"
tftp_dir = os.path.join(self.base_path, "tftpboot")
if squashfs_file is None:
tftp_dir = os.path.join(self.base_path, "tftpboot")
if oglive_num is None:
self.__logger.info("Reading from default oglive")
oglive_num = self.oglive.get_default()
if oglive_num is None:
self.__logger.info("Reading from default oglive")
oglive_num = self.oglive.get_default()
else:
self.__logger.info("Reading from oglive %i", oglive_num)
oglive_client = self.oglive.get_clients()[str(oglive_num)]
self.__logger.info("Oglive is %s", oglive_client)
client_squashfs_path = os.path.join(tftp_dir, oglive_client, squashfs)
else:
self.__logger.info("Reading from oglive %i", oglive_num)
oglive_client = self.oglive.get_clients()[str(oglive_num)]
self.__logger.info("Oglive is %s", oglive_client)
client_squashfs_path = os.path.join(tftp_dir, oglive_client, squashfs)
self.__logger.info("Using specified squashfs file %s", squashfs_file)
client_squashfs_path = squashfs_file
self.__logger.info("Mounting %s", client_squashfs_path)
mount_tempdir = tempfile.TemporaryDirectory()
@ -352,49 +453,75 @@ class OpengnsysGitInstaller:
return public_keys
def _extract_ssh_key_from_initrd(self):
def extract_ssh_key_from_initrd(self, oglive_number = None, initrd_file = None):
public_key=""
INITRD = "oginitrd.img"
tftp_dir = os.path.join(self.base_path, "tftpboot")
default_num = self.oglive.get_default()
default_client = self.oglive.get_clients()[default_num]
client_initrd_path = os.path.join(tftp_dir, default_client, INITRD)
self.__logger.debug("Extracting ssh key from initrd")
#self.temp_dir = self._get_tempdir()
if initrd_file is None:
self.__logger.debug("Looking for initrd file")
tftp_dir = os.path.join(self.base_path, "tftpboot")
if oglive_number is None:
oglive_number = self.oglive.get_default()
if self.usesshkey:
with open(self.usesshkey, 'r') as f:
public_key = f.read().strip()
oglive_client = self.oglive.get_clients()[oglive_number]
client_initrd_path = os.path.join(tftp_dir, oglive_client, INITRD)
self.__logger.debug("Found at %s", client_initrd_path)
else:
if os.path.isfile(client_initrd_path):
#os.makedirs(temp_dir, exist_ok=True)
#os.chdir(self.temp_dir.name)
self.__logger.debug("Descomprimiendo %s", client_initrd_path)
public_key = None
with libarchive.file_reader(client_initrd_path) as initrd:
for file in initrd:
self.__logger.debug("Archivo: %s", file)
self.__logger.debug("Using provided initrd file %s", initrd_file)
client_initrd_path = initrd_file
pathname = file.pathname;
if pathname.startswith("./"):
pathname = pathname[2:]
self.__logger.debug("Extracting key from %s", client_initrd_path)
if pathname in self.key_paths_dict:
data = bytearray()
for block in file.get_blocks():
data = data + block
public_key = data.decode('utf-8').strip()
if os.path.isfile(client_initrd_path):
#os.makedirs(temp_dir, exist_ok=True)
#os.chdir(self.temp_dir.name)
self.__logger.debug("Uncompressing %s", client_initrd_path)
public_key = None
with libarchive.file_reader(client_initrd_path) as initrd:
for file in initrd:
self.__logger.debug("File: %s", file)
break
else:
print(f"No se encuentra la imagen de initrd {client_initrd_path}")
exit(2)
pathname = file.pathname;
if pathname.startswith("./"):
pathname = pathname[2:]
if pathname in self.key_paths_dict:
self.__logger.info("Found key %s, extracting", pathname)
data = bytearray()
for block in file.get_blocks():
data = data + block
public_key = data.decode('utf-8').strip()
break
else:
print(f"Failed to find initrd at {client_initrd_path}")
exit(2)
if not public_key:
self.__logger.warning("Failed to find a SSH key")
return public_key
def get_image_paths(self, oglive_num = None):
squashfs = "ogclient.sqfs"
if oglive_num is None:
self.__logger.info("Will modify default client")
oglive_num = self.oglive.get_default()
tftp_dir = os.path.join(self.base_path, "tftpboot")
oglive_client = self.oglive.get_clients()[str(oglive_num)]
client_squashfs_path = os.path.join(tftp_dir, oglive_client, squashfs)
self.__logger.info("Squashfs: %s", client_squashfs_path)
def set_ssh_key_in_initrd(self, client_num = None):
INITRD = "oginitrd.img"
@ -534,7 +661,25 @@ class OpengnsysGitInstaller:
self.add_forgejo_sshkey(oglive_public_key, f"Key for {ogclient} ({timestamp})")
def install(self):
def verify_requirements(self):
self.__logger.info("verify_requirements()")
# Control básico de errores.
self.__logger.debug("Comprobando euid")
if os.geteuid() != 0:
raise RequirementException("Sólo ejecutable por root")
if not os.path.exists("/etc/debian_version"):
raise RequirementException("Instalación sólo soportada en Debian y Ubuntu")
MIN_PYTHON = (3, 8)
if sys.version_info < MIN_PYTHON:
raise RequirementException(f"Python %s.%s mínimo requerido.\n" % MIN_PYTHON)
def install_dependencies(self):
"""Instalar
Ejecuta todo el proceso de instalación incluyendo:
@ -551,31 +696,10 @@ class OpengnsysGitInstaller:
"""
self.__logger.info("install()")
ogdir_images = os.path.join(self.base_path, "images")
ENGINECFG = os.path.join(self.base_path, "client/etc/engine.cfg")
os.environ["PATH"] += os.pathsep + os.path.join(self.base_path, "bin")
tftp_dir = os.path.join(self.base_path, "tftpboot")
INITRD = "oginitrd.img"
self.temp_dir = self._get_tempdir()
SSHUSER = "opengnsys"
self.verify_requirements()
# Control básico de errores.
self.__logger.debug("Comprobando euid")
if os.geteuid() != 0:
raise RequirementException("Sólo ejecutable por root")
if not os.path.exists("/etc/debian_version"):
raise RequirementException("Instalación sólo soportada en Debian y Ubuntu")
MIN_PYTHON = (3, 8)
if sys.version_info < MIN_PYTHON:
raise RequirementException(f"Python %s.%s mínimo requerido.\n" % MIN_PYTHON)
self.__logger.debug("Instalando dependencias")
self.__logger.debug("Installing dependencies")
subprocess.run(["apt-get", "install", "-y", "git"], check=True)
def _install_template(self, template, destination, keysvalues):
@ -599,34 +723,43 @@ class OpengnsysGitInstaller:
return ret.stdout.strip()
def install_forgejo(self):
self.__logger.info("Installing Forgejo")
self.__logger.info("Installing Forgejo version %s", FORGEJO_VERSION)
bin_path = os.path.join(self.base_path, "bin", "forgejo")
conf_dir_path = os.path.join(self.base_path, "etc", "forgejo")
opengnsys_bin_path = os.path.join(self.base_path, "bin")
opengnsys_etc_path = os.path.join(self.base_path, "etc")
forgejo_bin_path = os.path.join(self.ogrepository_base_path, "bin")
bin_path = os.path.join(forgejo_bin_path, "forgejo")
conf_dir_path = os.path.join(self.ogrepository_base_path, "etc", "forgejo")
lfs_dir_path = os.path.join(self.base_path, "images", "git-lfs")
git_dir_path = os.path.join(self.base_path, "images", "git")
lfs_dir_path = os.path.join(self.ogrepository_base_path, "oggit", "git-lfs")
git_dir_path = os.path.join(self.ogrepository_base_path, "oggit", "git")
forgejo_work_dir_path = os.path.join(self.base_path, "var", "lib", "forgejo/work")
forgejo_db_dir_path = os.path.join(self.base_path, "var", "lib", "forgejo/db")
forgejo_data_dir_path = os.path.join(self.base_path, "var", "lib", "forgejo/data")
forgejo_work_dir_path = os.path.join(self.ogrepository_base_path, "var", "lib", "forgejo/work")
forgejo_db_dir_path = os.path.join(self.ogrepository_base_path, "var", "lib", "forgejo/db")
forgejo_data_dir_path = os.path.join(self.ogrepository_base_path, "var", "lib", "forgejo/data")
forgejo_db_path = os.path.join(forgejo_db_dir_path, "forgejo.db")
forgejo_log_dir_path = os.path.join(self.base_path, "log", "forgejo")
forgejo_log_dir_path = os.path.join(self.ogrepository_base_path, "log", "forgejo")
conf_path = os.path.join(conf_dir_path, "app.ini")
self.__logger.debug("Stopping opengnsys-forgejo service")
subprocess.run(["systemctl", "stop", "opengnsys-forgejo"], check=False)
self.__logger.info("Stopping opengnsys-forgejo service. This may cause a harmless warning.")
subprocess.run(["/usr/bin/systemctl", "stop", "opengnsys-forgejo"], check=False)
self.__logger.debug("Downloading from %s into %s", FORGEJO_URL, bin_path)
urllib.request.urlretrieve(FORGEJO_URL, bin_path)
pathlib.Path(forgejo_bin_path).mkdir(parents=True, exist_ok=True)
with open(bin_path, "wb") as forgejo_bin:
download_with_progress(FORGEJO_URL, forgejo_bin)
os.chmod(bin_path, 0o755)
if os.path.exists(forgejo_db_path):
@ -643,6 +776,7 @@ class OpengnsysGitInstaller:
self.__logger.debug("Creating directories")
pathlib.Path(opengnsys_etc_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(conf_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(git_dir_path).mkdir(parents=True, exist_ok=True)
pathlib.Path(lfs_dir_path).mkdir(parents=True, exist_ok=True)
@ -716,11 +850,6 @@ class OpengnsysGitInstaller:
token_file.write(token)
ssh_key = self._extract_ssh_key_from_initrd()
self.add_forgejo_sshkey(ssh_key, "Default key")
def add_forgejo_repo(self, repository_name, description = ""):
token = ""
with open(os.path.join(self.base_path, "etc", "ogGitApiToken.cfg"), "r", encoding='utf-8') as token_file:
@ -799,8 +928,7 @@ if __name__ == '__main__':
streamLog = logging.StreamHandler()
streamLog.setLevel(logging.INFO)
if not os.path.exists(opengnsys_log_dir):
os.mkdir(opengnsys_log_dir)
pathlib.Path(opengnsys_log_dir).mkdir(parents=True, exist_ok=True)
logFilePath = f"{opengnsys_log_dir}/git_installer.log"
fileLog = logging.FileHandler(logFilePath)
@ -824,15 +952,23 @@ if __name__ == '__main__':
parser.add_argument('--testmode', action='store_true', help="Modo de prueba")
parser.add_argument('--ignoresshkey', action='store_true', help="Ignorar clave de SSH")
parser.add_argument('--usesshkey', type=str, help="Usar clave SSH especificada")
parser.add_argument('--use-ssh-key', metavar="FILE", type=str, help="Add the SSH key from the specified file")
parser.add_argument('--test-createuser', action='store_true')
parser.add_argument('--extract-ssh-key', action='store_true', help="Extract SSH key from oglive squashfs")
parser.add_argument('--set-ssh-key', action='store_true', help="Read SSH key from oglive squashfs and set it in Forgejo")
parser.add_argument('--extract-ssh-key-from-initrd', action='store_true', help="Extract SSH key from oglive initrd (obsolete)")
parser.add_argument('--initrd-file', metavar="FILE", help="Initrd file to extract SSH key from")
parser.add_argument('--squashfs-file', metavar="FILE", help="Squashfs file to extract SSH key from")
parser.add_argument('--oglive-file', metavar="FILE", help="Oglive file (ISO) to extract SSH key from")
parser.add_argument('--oglive-url', metavar="URL", help="URL to oglive file (ISO) to extract SSH key from")
parser.add_argument('--set-ssh-key-in-initrd', action='store_true', help="Configure SSH key in oglive (obsolete)")
parser.add_argument('--oglive', type=int, metavar='NUM', help = "Do SSH key manipulation on this oglive")
parser.add_argument('--quiet', action='store_true', help="Quiet console output")
parser.add_argument('--get-image-paths', action='store_true', help="Get paths to image files")
parser.add_argument("-v", "--verbose", action="store_true", help = "Verbose console output")
@ -848,7 +984,6 @@ if __name__ == '__main__':
installer = OpengnsysGitInstaller()
installer.set_testmode(args.testmode)
installer.set_ignoresshkey(args.ignoresshkey)
installer.set_usesshkey(args.usesshkey)
logger.debug("Inicio de instalación")
@ -860,25 +995,39 @@ if __name__ == '__main__':
elif args.test_createuser:
installer.set_ssh_user_group("oggit2", "oggit2")
elif args.extract_ssh_key:
keys = installer.extract_ssh_keys(oglive_num = args.oglive)
keys = installer.extract_ssh_keys_from_squashfs(oglive_num = args.oglive)
print(f"{keys}")
elif args.extract_ssh_key_from_initrd:
key = installer._extract_ssh_key_from_initrd()
key = installer.extract_ssh_key_from_initrd(oglive_number = args.oglive, initrd_file = args.initrd_file)
print(f"{key}")
elif args.set_ssh_key:
installer.add_ssh_key_from_squashfs(oglive_num=args.oglive)
installer.add_ssh_key_from_squashfs(oglive_num=args.oglive, squashfs_file=args.squashfs_file, oglive_file = args.oglive_file or args.oglive_url)
elif args.use_ssh_key:
with open(args.use_ssh_key, 'r', encoding='utf-8') as ssh_key_file:
ssh_key_data = ssh_key_file.read().strip()
(keytype, keydata, description) = ssh_key_data.split(" ", 2)
installer.add_forgejo_sshkey(f"{keytype} {keydata}", description)
elif args.set_ssh_key_in_initrd:
installer.set_ssh_key_in_initrd()
elif args.get_image_paths:
installer.get_image_paths(oglive_num = args.oglive)
else:
installer.install()
installer.install_dependencies()
installer.install_forgejo()
installer.add_forgejo_repo("windows", "Windows")
installer.add_forgejo_repo("linux", "Linux")
installer.add_forgejo_repo("mac", "Mac")
installer.add_ssh_key_from_squashfs(oglive_num = args.oglive, squashfs_file=args.squashfs_file, oglive_file = args.oglive_file or args.oglive_url)
except RequirementException as req:
show_error(f"Requisito para la instalación no satisfecho: {req.message}")
exit(1)
except OptionalDependencyException as optreq:
show_error(optreq.message)
exit(1)

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