Compare commits

...

33 Commits

Author SHA1 Message Date
Vadim Trochinsky 199ef8c5e1 ref #2317 Fix performance problem, remove stats 2025-06-30 15:52:54 +02:00
Vadim Trochinsky 05b51de851 Better error handling for bad tag names 2025-06-30 15:52:54 +02:00
Vadim Trochinsky 228bd85aa8 Add commit listing 2025-06-30 15:52:54 +02:00
Vadim vtroshchinskiy 3992ff27f0 update changelog 2025-06-30 15:52:51 +02:00
Vadim vtroshchinskiy 8728682936 update dependencies 2025-06-30 15:46:00 +02:00
Vadim Trochinsky a1f0561f5c Add SSH key endpoint 2025-06-30 15:46:00 +02:00
Vadim vtroshchinskiy e355562714 Fix path 2025-06-30 15:46:00 +02:00
Vadim vtroshchinskiy 8dcd1faefd Add another search path for installer module 2025-06-30 15:46:00 +02:00
Vadim vtroshchinskiy 05b963d611 Fix path handling when there are no repositories 2025-06-30 15:46:00 +02:00
Vadim vtroshchinskiy da5d6fd8c5 Fix base path handling 2025-06-30 15:32:19 +02:00
Vadim vtroshchinskiy 3c2fc27e2e Package build fixes 2025-06-30 15:32:17 +02:00
Vadim vtroshchinskiy b04bf3c5b1 Cambiar endpoint de crear tags 2025-06-30 15:30:52 +02:00
Vadim vtroshchinskiy bff65cde01 Fix repository already exists error code 2025-06-30 15:30:52 +02:00
Vadim vtroshchinskiy 984b251615 Use annotated tags 2025-06-30 15:30:52 +02:00
Vadim vtroshchinskiy 28f2537aed Swagger fix: use the right name in example 2025-06-30 15:30:51 +02:00
Vadim vtroshchinskiy c949cfb9a9 Backup endpoint 2025-06-30 15:30:51 +02:00
Vadim vtroshchinskiy 9f87997722 Add git repo synchronization 2025-06-30 15:30:51 +02:00
Vadim vtroshchinskiy a387af27d0 Fix error logging, wrong constant 2025-06-30 15:30:51 +02:00
Vadim vtroshchinskiy 9a9cf17403 Fix installer module loading 2025-06-30 15:30:51 +02:00
Vadim vtroshchinskiy ba5384ea77 Add git repository GC function 2025-06-30 15:30:51 +02:00
Vadim vtroshchinskiy 74efebf3c8 Swagger fixes 2025-06-30 15:30:51 +02:00
Vadim vtroshchinskiy 88953696e7 Git implementation and Swagger 2025-06-30 15:30:51 +02:00
opengnsys f972ae1ae9 Merge pull request 'update_torrent_repos' (#38) from update_torrent_repos into main
ogrepository/pipeline/head This commit looks good Details
ogrepository/pipeline/tag This commit looks good Details
Remplaza las utilidades de los paquetes de ctorrent , bittornado, bittorrent por

- opentracker como tracker de archivos torrent.
- aria2c como cliente de descargas.
- mktorrent para crear los archivos torrent.

Tickets implicados:

Historia #2288
- refs #2291: El seeding inicial del archivo se realiza ahora mediante aria2c
- refs #2293: El script reunTorrentTracker.py no se invoca ahora por los endpoints, opentracker se ejecuta como servicio.
- refs #2340: El motor de clonación ya no usa ctorrent, su funcionalidad ha sido remplazada por aria2c y mktorrent
- refs #2290: Og repository ya no usa ctorrent para crear el los archivos torrent, lo hace ahora mktorrent
- refs #2292: Eliminadas las referencias a bttrack en el api como tracker de archivos torrent.
- refs #2310: Se ha creado el paquete opentracker, que se ha compilado sin soporte a listas blancas para simplificar el proceso de servir torrents.
- refs #2331: Se ha añadido a oglive aria2c y mktorrent, se ha eliminado la instalación de ctorrent.
- refs #2294: Se han ajustado las dependencias de ogrepository, se han eliminado las dependencias de bittornado, bittorrent y ctorrent y se han sustituido por opentracker, mktorrent y aria2.

Historia #2259
- refs #2260: se ha cambiado la forma de instalar los paquetes de torrent.
- refs #2261: el despliegu por torrent no funcionaba correctamente, daba problemas de conexión dependiendo de las situaciones, se ha simplificado el proceso.
- refs #2282: Se añaden backup y restore de las configuraciones en caso de actualización de los paquetes.
2025-06-26 19:31:02 +02:00
Nicolas Arenas 95524ac9cf Updated Changelog.md 2025-06-25 12:40:55 +02:00
Nicolas Arenas ccf04ba8fc refs 2294: include opengnsys-opentracker as dependency 2025-06-25 11:58:48 +02:00
Nicolas Arenas 9f170c23c7 refs #2291 Commentout call to old utilities
refs #2291 soft kill of aria2c process
2025-06-25 11:55:47 +02:00
Nicolas Arenas 6236be85d8 refs 2293: Removes information for unwanted services 2025-06-25 11:53:43 +02:00
Nicolas Arenas 0118ca53a5 refs #2293: Removes calls to runTorrentTracker 2025-06-25 11:48:25 +02:00
Nicolas Arenas bada82e88a refs #2291 and #2293: Replace bittomany for aria2c
refs #2293: changes way to kill aria2c process
refs #2293: a aria2c is launched for each image to be served
2025-06-24 11:33:40 +00:00
Nicolas Arenas a3b938e41e refs #2290: Removes -l parameter in mktorrent
refs #2294: Updates dependency with mktorrent
2025-06-24 05:20:39 +00:00
Nicolas Arenas dd60800c97 refs #2290: Replaces ctorrent by mktorrent to create torrent file for images 2025-06-23 23:55:18 +02:00
Nicolas Arenas ecbba3d45e refs #2282 Review errors in ogrepo update
refs #2260 Change the way to install dependency
refs #2260 Remove bittorrent dependency
2025-06-23 09:59:13 +02:00
Nicolas Arenas a58023f893 Fixed BT issue in sudoers file
ogrepository/pipeline/tag There was a failure building this commit Details
2025-06-02 12:41:33 +02:00
11 changed files with 1535 additions and 115 deletions

View File

@ -1,5 +1,20 @@
# Changelog
## [0.9.0] - 2025-06-25
## Added
- Changed old tools for tools non dependant of Pyhton2 in repo
- mktorrent to handle creation of torrent files
- aria2c as torrent client for initial seeding
- opentracker as torrent tracker tool
## [0.8.2] - 2025-06-01
### Changed
- Modified sudoersfile to start torrent
## [0.8.1] - 2025-04-01
### Changed

View File

@ -2,16 +2,16 @@
# -*- coding: utf-8 -*-
"""
API de ogRepository, programada en Flask.
API de ogRepository, programada en Flask.
Responde a peticiones HTTP (en principio, enviadas desde ogCore) mediante endpoints, que a su vez ejecutan los scripts Python almacenados en ogRepository.
En ciertos casos, transforma los parámetros recibidos desde el portal, para adaptarlos a los que es necesario enviar a los scripts
(por ejemplo, a partir del ID de una imagen obtiene su nombre y su extensión).
(por ejemplo, a partir del ID de una imagen obtiene su nombre y su extensión).
Librerías Python requeridas: - flask (se puede instalar con "sudo apt install python3-flask")
- paramiko (se puede instalar con "sudo apt install python3-paramiko")
Librerías Python requeridas: - flask (se puede instalar con "sudo apt install python3-flask")
- paramiko (se puede instalar con "sudo apt install python3-paramiko")
- requests (se puede instalar con "sudo apt install python3-requests") - No es necesario instalarlo en Ubuntu 24
- flasgger (se puede instalar con "sudo apt install python3-flasgger")
- flasgger (se puede instalar con "sudo apt install python3-flasgger")
"""
# --------------------------------------------------------------------------------------------
@ -48,6 +48,22 @@ trash_file = '/opt/opengnsys/ogrepository/etc/trashinfo.json'
config_file = '/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg'
# --------------------------------------------------------------------------------------------
# GIT
# --------------------------------------------------------------------------------------------
REPOSITORIES_BASE_PATH = "/opt/opengnsys/ogrepository/oggit/git/"
OGGIT_USER = "oggit"
import sys
import git
import pkgutil
import importlib
import paramiko
# --------------------------------------------------------------------------------------------
# FUNCTIONS
# --------------------------------------------------------------------------------------------
@ -73,9 +89,9 @@ swagger = Swagger(app, template=swagger_template)
def get_IPcore():
""" Obtiene el valor asociado a la variable "IPcore", desde el archivo '/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg'.
Retorna la IP encontrada (que corresponde a la IP de ogCore), o un error (si no la encuentra).
"""
"""
journal.send("Running function 'get_IPcore'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
IPcore = None
with open(config_file, 'r') as file:
for line in file:
@ -143,7 +159,7 @@ def search_process(process, string_to_search):
""" Busca procesos que contengan el valor del parámetro "process" y el valor del parámetro "string_to_search" (la ruta de la imagen, normalmente).
Si encuentra alguno retorna "True", y si no encuentra ninguno retorna "False".
"""
journal.send("Running function 'search_process'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send(f"Running function 'search_process' {process} wit string {string_to_search}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
try:
# Obtenemos todos los procesos que están corriendo, y almacenamos la salida y los errores:
@ -155,11 +171,11 @@ def search_process(process, string_to_search):
# Si hemos encontrado algún proceso que cumpla las condiciones, retornamos "True", y si no retornamos "False":
if process_list != []:
journal.send("Process found", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send(f"Process found: {process} with string {string_to_search} ", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function search_process', 'desc':'Process found'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
return True
else:
journal.send("Process not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send(f"Process not found: {process} with string {string_to_search}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send("{'component':'ogRepo', 'severity':'WARNING', 'operation':'Run function search_process', 'desc':'Process not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api")
return False
# Si se ha producido una excepción, imprimimos el error:
@ -327,7 +343,7 @@ def check_remote_backup(image_name, remote_ip, remote_user, remote_path, job_id)
# Creamos un bucle infinito:
while True:
# Comprobamos si ya se ha copiado la imagen y cada uno de sus archivos asociados (y almacenamos el resultado):
try:
try:
for ext in extensions:
sftp_client.stat(f"{remote_path}{image_name}{ext}")
all_files_copied = True
@ -601,7 +617,7 @@ def recall_ogcore(data):
def check_file_exists(file_path):
""" Comprueba la existencia del archivo cuya ruta recibe como parámetro.
Si el archivo existe devuelve "True", y si no devuelve "False".
Si el archivo existe devuelve "True", y si no devuelve "False".
"""
journal.send("Running function 'check_file_exists'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
@ -671,7 +687,7 @@ def get_repo_status():
journal.send("Running script 'getRepoStatus.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
# Ejecutamos el script "getRepoStatus.py", y almacenamos el resultado:
result = subprocess.run(['python3', f"{script_path}/getRepoStatus.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')
# Evaluamos el resultado de la ejecución, y devolvemos la respuesta:
if result.returncode == 0:
journal.send("Script 'getRepoStatus.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
@ -973,7 +989,7 @@ def recover_image():
# Almacenamos el parámetro "ID_img" (enviado por JSON):
json_data = json.loads(request.data)
image_id = json_data.get("ID_img")
image_id = json_data.get("ID_img")
# Obtenemos el nombre y la extensión de la imagen:
param_dict = get_image_params(image_id, "trash")
@ -1136,7 +1152,7 @@ def import_image():
if result.returncode is None:
journal.send("Script 'importImage.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script importImage.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
# Si el resultado es correcto, llamamos a la función "check_lock_local" en un hilo paralelo
# (para que compruebe si la imagen se ha acabado de importar exitosamente):
journal.send("Calling function 'check_lock_local'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
@ -1179,7 +1195,7 @@ def import_image():
def backup_image():
""" Este endpoint exporta la imagen especificada como primer parámetro (y todos sus archivos asociados), desde el servidor local a un equipo remoto (que no tiene por qué ser un repositorio).
Para ello, ejecuta el script "backupImage.py", con el nombre de la imagen como primer parámetro, la IP o hostname del equipo remoto como segundo parámetro,
el usuario con el que conectar al equipo remoto como tercer parámetro, y la ruta remota en la que copiar la imagen como cuarto parámetro.
el usuario con el que conectar al equipo remoto como tercer parámetro, y la ruta remota en la que copiar la imagen como cuarto parámetro.
"""
journal.send("Running endpoint 'Hacer backup de una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
@ -1260,7 +1276,7 @@ def backup_image():
}), 200
else:
journal.send("Script 'backupImage.py' result KO (Backup image failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Backup image failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Backup image failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
return jsonify({
"success": False,
"error": "Backup image failed"
@ -1357,7 +1373,7 @@ def create_torrent_sum():
# ---------------------------------------------------------
# 12 - Endpoint "Enviar paquete Wake On Lan" (SINCRONO):
# 12 - Endoint "Enviar paquete Wake On Lan" (SINCRONO):
@app.route("/ogrepository/v1/wol", methods=['POST'])
def send_wakeonlan():
""" Este endpoint envía un paquete mágico Wake On Lan a la dirección MAC especificada, a través de la IP de broadcast especificadac.
@ -1567,8 +1583,7 @@ def send_p2p():
# Evaluamos los parámetros obtenidos, para construir las llamadas a los scripts, o para devolver un error si no se ha encontrado la imagen:
if param_dict:
cmd_tracker = ['sudo', 'python3', f"{script_path}/runTorrentTracker.py"] # Este script si que requiere ser ejecutado con "sudo"
cmd_seeder = ['sudo', 'python3', f"{script_path}/runTorrentSeeder.py"] # Este script si que requiere ser ejecutado con "sudo"
cmd_seeder = ['python3', f"{script_path}/runTorrentSeeder.py" , param_dict['name']] # Este script si que requiere ser ejecutado con "sudo" , Lanzamos el seeder con el nombre de la imagen como parámetro
base_path = repo_path.rstrip('/') # Le quito la última barra para poder buscar correctamente en los procesos
else:
journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
@ -1578,11 +1593,18 @@ def send_p2p():
"error": "Image not found"
}), 400
# Ejecutamos los scripts "runTorrentSeeder.py" y "runTorrentSeeder.py", que no reciben parámetros.
# NOTA: No almacenamos la salida ni comprobamos los errores, porque los procesos quedarán corriendo hasta que se finalicen manualmente,
# por lo que no podemos comprobar el returncode (luego comprobaremos si los procesos se han iniciado correctamente).
journal.send("Running script 'runTorrentTracker.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
subprocess.Popen(cmd_tracker)
# Comprobamos si el tracker esta ejecutandose, si no lo esta devolevemos un error y salimos del endpoint:
tracker_running = search_process('opentracker' , '/etc/opentracker/opentracker.conf') # El tracker se ececuta con "opentracker" y el fichero de configuración "/etc/opentracker/opentracker.conf"
if not tracker_running:
journal.send("Tracker not running", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run opentrack', 'desc':'Tracker not running'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
error_message = "Tracker not running. Check if the tracker is installed and configured correctly."
return jsonify({
"success": False,
"error": error_message
}), 500
# Ejecutamos los scripts "runTorrentSeeder.py", que no reciben parámetros.
journal.send("Running script 'runTorrentSeeder.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
subprocess.Popen(cmd_seeder)
@ -1590,25 +1612,28 @@ def send_p2p():
# Comprobamos si el tracker y el seeder están corriendo, y si apuntan al directorio que le hemos pasado
# (esperamos 10 segundos antes de hacerlo, porque los procesos no se inician inmediatamente):
sleep(10)
tracker_running = search_process('bttrack', base_path)
seeder_running = search_process('btlaunchmany', base_path)
seeder_running = search_process('aria2c', f"{param_dict['name']}.img.torrent") # El seeder se ejecuta con "aria2c" y el nombre de la imagen como parámetro
# Evaluamos las comprobaciones anteriores, para devolver la respuesta que corresponda:
if tracker_running and seeder_running:
journal.send("Scripts 'runTorrentTracker.py' and 'runTorrentSeeder.py' results OK (ReturnCodes: None), and processes running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run scripts runTorrentTracker.py and runTorrentSeeder.py', 'desc':'Results OK (ReturnCodes: None), and processes running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
if seeder_running:
journal.send("'runTorrentSeeder.py' results OK (ReturnCodes: None), and processes running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script runTorrentSeeder.py', 'desc':'Results OK (ReturnCodes: None), and processes running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
return jsonify({
"success": True,
"output": "Tracker and Seeder serving image correctly"
}), 200
else:
journal.send("Scripts 'runTorrentTracker.py' and 'runTorrentSeeder.py' results KO (Tracker or/and Seeder not runnig)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run scripts runTorrentTracker.py and runTorrentSeeder.py', 'desc':'Results KO (Tracker or/and Seeder not runnig)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
journal.send("Script 'runTorrentSeeder.py' results KO (Seeder not runnig)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script runTorrentSeeder.py', 'desc': '" + error_message + "'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
error_message = f"Seeder not running. Check if the image {param_dict['name']}.{param_dict['extension']} exists in the repository."
return jsonify({
"success": False,
"error": "Tracker or Seeder (or both) not running"
"error": error_message
}), 500
# ---------------------------------------------------------
@ -1875,7 +1900,7 @@ def stop_p2p():
journal.send("Running script 'stopP2P.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
# Ejecutamos el script "stopP2P.py", y almacenamos el resultado (este script si que requiere ser ejecutado con "sudo"):
result = subprocess.run(['sudo', 'python3', f"{script_path}/stopP2P.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8')
# Evaluamos el resultado de la ejecución, y devolvemos la respuesta:
if result.returncode == 0:
journal.send("Script 'stopP2P.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
@ -1909,7 +1934,7 @@ def convert_virtual_image():
""" Este endpoint convierte la imagen virtual especificada como parámetro en una imagen "img" como las que se generan desde OpenGnsys
(con "partclone" y "lzop"), por lo que luego puede ser restaurada como cualquier otra imagen del repositorio.
Para ello, ejecuta el script "convertVMtoIMG.py", con el nombre de la imagen virtual como primer parámetro,
y el sistema de archivos de la partición a clonar (en formato "blkid") como segundo parámetro.
y el sistema de archivos de la partición a clonar (en formato "blkid") como segundo parámetro.
"""
journal.send("Running endpoint 'Convertir imagen virtual a imagen OpenGnsys'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
@ -1972,7 +1997,7 @@ def convert_virtual_image():
if result.returncode is None:
journal.send("Script 'convertVMtoIMG.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
# Si el resultado es correcto, llamamos a la función "check_virtual_image_conversion" en un hilo paralelo
# (para que compruebe si la imagen se ha acabado de convertir exitosamente):
journal.send("Calling function 'check_virtual_image_conversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
@ -2015,7 +2040,7 @@ def convert_virtual_image():
def convert_image_to_virtual():
""" Este endpoint convierte la imagen de OpenGnsys especificada como parámetro en una imagen virtual con la extensión especificada (".vdi", ".vmdk", etc).
Para ello, ejecuta el script "convertIMGtoVM.py", con el nombre de la imagen "img" como primer parámetro,
y la extensión del disco virtual destino (sin punto) como segundo parámetro.
y la extensión del disco virtual destino (sin punto) como segundo parámetro.
"""
journal.send("Running endpoint 'Convertir imagen OpenGnsys a imagen virtual'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
@ -2077,7 +2102,7 @@ def convert_image_to_virtual():
if result.returncode is None:
journal.send("Script 'convertIMGtoVM.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
# Si el resultado es correcto, llamamos a la función "check_virtual_image_reconversion" en un hilo paralelo
# (para que compruebe si la imagen se ha acabado de convertir exitosamente):
journal.send("Calling function 'check_virtual_image_reconversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
@ -2119,7 +2144,7 @@ def convert_image_to_virtual():
@app.route("/ogrepository/v1/images/rename", methods=['PUT'])
def rename_image():
""" Este endpoint renombra la imagen especificada como primer parámetro (y todos sus archivos asociados), asignando el nombre especificado como segundo parámetro.
Para ello, ejecuta el script "renameImage.py", con el nombre original de la imagen como primer parámetro, y el nuevo nombre a asignar como segundo parámetro.
Para ello, ejecuta el script "renameImage.py", con el nombre original de la imagen como primer parámetro, y el nuevo nombre a asignar como segundo parámetro.
"""
journal.send("Running endpoint 'Renombrar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
@ -2185,6 +2210,720 @@ def rename_image():
# -----------------------------------------------------------
# ____ _ _
# / ___(_) |_
# | | _| | __|
# | |_| | | |_
# \____|_|\__|
#
# -----------------------------------------------------------
def git_compact_repository_task(repo, job_id):
journal.send("Running function 'git_compact_repository_task'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
git_repo_path = os.path.join(REPOSITORIES_BASE_PATH, OGGIT_USER, repo + ".git")
git_repo = git.Repo(git_repo_path)
git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path)
git_repo.git.gc()
data = {
'job_id': job_id,
'success': True
}
journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
recall_ogcore(data)
def git_sync_repository_task(repo, remote_repository, job_id):
journal.send("Running function 'git_sync_repository_task'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
git_repo_path = os.path.join(REPOSITORIES_BASE_PATH, OGGIT_USER, 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", 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 }]
data = {
'job_id': job_id,
'success': True,
'updated_references' : results
}
journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
recall_ogcore(data)
def git_backup_repository_task(repo, params, job_id):
journal.send("Running function 'git_sync_repository_task'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
git_repo_path = os.path.join(REPOSITORIES_BASE_PATH, OGGIT_USER, 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")
data = {
'job_id': job_id,
'success': True,
}
journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
recall_ogcore(data)
def git_add_sshkey_task(oglive, description, job_id):
module = _load_installer()
print(f"Got {module}")
OpengnsysGitInstaller = getattr(module, 'OpengnsysGitInstaller')
installer = OpengnsysGitInstaller()
results = installer.add_ssh_key_from_squashfs(oglive_file = oglive)
keys_added = 0
keys_existed = 0
keys_failed = 0
for status, message in results:
if status == 200 or status == 201:
keys_added = keys_added + 1
elif status == 422:
keys_existed = keys_existed + 1
else:
keys_failed = keys_failed + 1
journal.send(f"Unrecognized reply from forgejo: code {status}, content {message}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
data = {
'job_id': job_id,
'keys_added' : keys_added,
'keys_failed' : keys_failed,
'keys_existed' : keys_existed,
'output' : message
}
journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
recall_ogcore(data)
@app.route("/ogrepository/v1/git/repositories", methods=['GET'])
def git_list_repositories():
"""
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"]
}
"""
journal.send("Running endpoint 'Obtener Información de todos los repositorios Git'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
if not os.path.isdir(REPOSITORIES_BASE_PATH):
journal.send(f"Can't list repositories. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error": "Repository storage not found, git functionality may not be installed."}), 500
repos = []
if os.path.exists(os.path.join(REPOSITORIES_BASE_PATH, OGGIT_USER)):
# If the base path exists, but the OGGIT_USER subpath doesn't, it means we've got an empty
# install. OgGit is present, but there's no repos yet.
for entry in os.scandir(os.path.join(REPOSITORIES_BASE_PATH, OGGIT_USER)):
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]
journal.send(f"Returning {len(repos)} repositories", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({
"repositories": repos
}), 200
def _load_module(module_name):
# module = importlib.util.find_spec(module_name)
module = importlib.import_module(module_name)
if module is not None:
journal.send(f"Module {module_name} loaded successfully. Got {module}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="oggit-api_DEBUG")
return module
journal.send(f"Module {module_name} failed to load, not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="oggit-api_DEBUG")
return False
def _load_installer():
journal.send(f"Loading oggit installer module", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="oggit-api_DEBUG")
script_dir = os.path.dirname(os.path.realpath(__file__))
system_bin_path = '/opt/opengnsys/ogrepository/oggit/bin/'
system_lib_path = '/opt/opengnsys/ogrepository/oggit/lib/'
devel_lib_path = os.path.join(script_dir, "../../oggit/installer")
if devel_lib_path not in sys.path and os.path.isdir(devel_lib_path):
journal.send(f"Using {devel_lib_path} development library path", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="oggit-api_DEBUG")
sys.path.append(devel_lib_path)
if system_bin_path not in sys.path:
journal.send(f"Using {system_bin_path} system library path", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="oggit-api_DEBUG")
sys.path.append(system_bin_path)
if system_lib_path not in sys.path:
journal.send(f"Using {system_lib_path} system library path", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="oggit-api_DEBUG")
sys.path.append(system_lib_path)
return _load_module("opengnsys_git_installer")
@app.route("/ogrepository/v1/git/repositories", methods=['POST'])
def git_create_repository():
"""
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.
- 409: If the repository already exists.
- 201: If the repository is successfully created.
"""
data = request.json
if data is None:
journal.send(f"Can't create repository, JSON post data missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing"}), 400
repo = data["name"]
repo_path = os.path.join(REPOSITORIES_BASE_PATH, OGGIT_USER, repo + ".git")
if os.path.isdir(repo_path):
journal.send(f"Can't create repository {repo}, already exists at {repo_path}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"status": "Repository already exists"}), 409
module = _load_installer()
print(f"Got {module}")
OpengnsysGitInstaller = getattr(module, 'OpengnsysGitInstaller')
installer = OpengnsysGitInstaller()
installer.add_forgejo_repo(repo)
journal.send(f"Repository {repo} created", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"status": "Repository created"}), 201
@app.route("/ogrepository/v1/git/repositories", methods=['DELETE'])
def git_delete_repository():
return jsonify({"error" : "Not implemented"}), 500
@app.route("/ogrepository/v1/git/repositories/<string:repo>/sync", methods=['POST'])
def git_sync_repository(repo):
journal.send("Running endpoint 'Sincronizar repositorio Git'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
if not os.path.isdir(REPOSITORIES_BASE_PATH):
journal.send(f"Can't sync repositories. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error": "Repository storage not found, git functionality may not be installed."}), 500
data = request.json
if data is None:
journal.send(f"Can't sync repository, JSON post data missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing"}), 400
if "remote_repository" in data:
remote_repository = data["remote_repository"]
else:
journal.send(f"Can't sync repository, JSON remote_repository parameter missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing"}), 400
job_id = f"GitSync_{''.join(random.choice('0123456789abcdef') for char in range(8))}"
threading.Thread(target=git_sync_repository_task, args=(repo, remote_repository, job_id,)).start()
return jsonify({
"success": True,
"output": "Synchronizing...",
"job_id": job_id
}), 200
@app.route("/ogrepository/v1/git/repositories/<string:repo>/backup", methods=['POST'])
def git_backup_repository(repo):
journal.send("Running endpoint 'Backup de repositorio Git'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
if not os.path.isdir(REPOSITORIES_BASE_PATH):
journal.send(f"Can't backup repositories. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error": "Repository storage not found, git functionality may not be installed."}), 500
data = request.json
if data is None:
journal.send(f"Can't backup repository, JSON post data missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing"}), 400
if not "ssh_server" in data:
journal.send(f"Can't sync repository, JSON ssh_server parameter missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing"}), 400
if not "ssh_port" in data:
journal.send(f"Can't sync repository, JSON ssh_port parameter missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing"}), 400
if not "ssh_user" in data:
journal.send(f"Can't sync repository, JSON ssh_user parameter missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing"}), 400
if not "filename" in data:
journal.send(f"Can't sync repository, JSON filename parameter missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing"}), 400
job_id = f"GitBackup_{''.join(random.choice('0123456789abcdef') for char in range(8))}"
threading.Thread(target=git_backup_repository_task, args=(repo, data, job_id,)).start()
return jsonify({
"success": True,
"output": "Backing up...",
"job_id": job_id
}), 200
@app.route("/ogrepository/v1/git/repositories/<string:repo>/compact", methods=['POST'])
def git_compact_repository(repo):
journal.send("Running endpoint 'Compactar repositorio Git'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
if not os.path.isdir(REPOSITORIES_BASE_PATH):
journal.send(f"Can't list repositories. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error": "Repository storage not found, git functionality may not be installed."}), 500
job_id = f"GitGC_{''.join(random.choice('0123456789abcdef') for char in range(8))}"
threading.Thread(target=git_compact_repository_task, args=(repo, job_id,)).start()
return jsonify({
"success": True,
"output": "Compacting...",
"job_id": job_id
}), 200
@app.route("/ogrepository/v1/git/repositories/<string:repo>/branches", methods=['GET'])
def git_get_branches(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, OGGIT_USER, repo + ".git")
if not os.path.isdir(repo_path):
journal.send(f"Can't list repositories. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"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]
journal.send(f"Returning {len(branches)} branches", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return {
"branches": branches
}
@app.route("/ogrepository/v1/git/repositories/<string:repo>/branches/<string:branch>/commits", methods=['GET'])
def git_get_commits(repo, branch):
"""
Retrieve the list of commits in a branch in a given repository.
Args:
repo (str): The name of the repository.
Returns:
Response: A JSON response containing a list of commits
- 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, OGGIT_USER, repo + ".git")
if not os.path.isdir(repo_path):
journal.send(f"Can't list repositories. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error": "Repository not found"}), 404
max_commits = request.args.get('max_commits') or 100
skip_commits = request.args.get('skip') or 0
git_repo = git.Repo(repo_path)
git_repo.git.config('--global', '--add', 'safe.directory', repo_path)
if not branch in git_repo.branches:
journal.send(f"Branch {branch} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error": "Branch not found"}), 404
# Lookup table for hash/tag
hash_to_tag = {}
for tag in git_repo.tags:
sha = tag.commit.hexsha
if not sha in hash_to_tag:
hash_to_tag[sha] = []
hash_to_tag[sha] = hash_to_tag[sha] + [ tag.name ]
commits = []
for com in git_repo.iter_commits(branch, max_count = max_commits, skip = skip_commits):
tag_list = []
if com.hexsha in hash_to_tag:
tag_list = hash_to_tag[com.hexsha]
commits = commits + [
{
"hexsha" : com.hexsha,
"message" : com.message,
"committed_date" : com.committed_date,
"tags" : tag_list,
"size" : com.size
}
]
journal.send(f"Returning {len(commits)} commits", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return {
"commits": commits
}
@app.route("/ogrepository/v1/git/repositories/<string:repo>/branches", methods=['POST'])
def git_create_branch(repo):
"""Create a given branch in a given repository
Args:
repo (str): The name of the repository.
Returns:
Response: A JSON response containing a creation status
- 201: 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 "Branch already exists"
"""
repo_path = os.path.join(REPOSITORIES_BASE_PATH, OGGIT_USER, repo + ".git")
if not os.path.isdir(repo_path):
journal.send(f"Can't create branch. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"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:
journal.send(f"Can't create branch. JSON post data missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing"}), 400
if not "commit" in data:
journal.send(f"Can't create branch. Commit parameter missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "commit parameter missing"}), 400
if not "name" in data:
journal.send(f"Can't create branch. Name parameter missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "name parameter missing"}), 400
branch = data["name"]
if branch in git_repo.branches:
journal.send(f"Can't create branch. Already found in repository {repo}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error": "Branch already exists"}), 409
git_repo.create_tag(branch, ref = data["commit"])
journal.send(f"Branch {branch} created in repo {repo}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"status": "created"}), 201
@app.route("/ogrepository/v1/git/repositories/<string:repo>/branches/<string:branch>", methods=['DELETE'])
def git_delete_branch(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, OGGIT_USER, repo + ".git")
if not os.path.isdir(repo_path):
journal.send(f"Can't delete branch. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
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:
journal.send(f"Can't delete branch. Not found in repository {repo}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error": "Branch not found"}), 404
git_repo.delete_head(branch)
journal.send(f"Branch {branch} deleted in repo {repo}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"status": "deleted"}), 200
@app.route("/ogrepository/v1/git/repositories/<string:repo>/tags", methods=['GET'])
def git_list_tags(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 tag names or an error message if the repository is not found.
- 200: A JSON object with a "branches" key containing a list of tag 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, OGGIT_USER, repo + ".git")
if not os.path.isdir(repo_path):
journal.send(f"Can't list repositories. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"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:
tag_info = {
"name" : tag.name,
"commit" : tag.commit.hexsha,
"committer" : tag.commit.committer.name,
"committed_datetime" : tag.commit.committed_datetime.timestamp()
}
if not tag.tag is None:
tag_info["message"] = tag.tag.message
tag_info["tagged_date"] = tag.tag.tagged_date
tag_info["tagger"] = tag.tag.tagger.name
tag_info["tagger_tz_offset"] = tag.tag.tagger_tz_offset
tags = tags + [ tag_info ]
journal.send(f"Returning {len(tags)} branches", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return {
"tags": tags
}
@app.route("/ogrepository/v1/git/repositories/<string:repo>/tags", methods=['POST'])
def git_create_tag(repo):
"""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, OGGIT_USER, repo + ".git")
if not os.path.isdir(repo_path):
journal.send(f"Can't create tag. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"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:
journal.send(f"Can't create tag. JSON post data missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing"}), 400
if not "commit" in data:
journal.send(f"Can't create tag. Commit parameter missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "commit parameter missing"}), 400
if not "name" in data:
journal.send(f"Can't create tag. Name parameter missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "name parameter missing"}), 400
commit_message = ""
tag = data["name"]
if "message" in data:
commit_message = data["message"]
if tag in git_repo.tags:
journal.send(f"Can't create tag. Already found in repository {repo}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error": "Tag already exists"}), 409
try:
git_repo.create_tag(tag, ref = data["commit"], message = commit_message)
except git.exc.GitCommandError as ge:
if "not a valid tag name" in ge.stderr:
journal.send(f"Tag name {tag} is invalid", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Invalid tag name"}), 400
else:
journal.send(f"Git error {ge}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Error when performing git command"}), 500
journal.send(f"Tag {tag} created in repo {repo}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"status": "created"}), 200
@app.route("/ogrepository/v1/git/repositories/<string:repo>/tags/<string:tag>", methods=['DELETE'])
def git_delete_tag(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, OGGIT_USER, repo + ".git")
if not os.path.isdir(repo_path):
journal.send(f"Can't delete tag. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
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:
journal.send(f"Can't delete tag. Not found in repository {repo}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error": "Tag not found"}), 404
git_repo.delete_head(tag)
journal.send(f"Tag {tag} deleted in repo {repo}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"status": "deleted"}), 200
@app.route("/ogrepository/v1/git/ssh_key", methods=['POST'])
def git_add_sshkey():
"""Add a SSH key
Args:
ssh_key (str): The SSH key
oglive (str): URL to an oglive image from which to extract the key. May be a local file or HTTP.
description (str): Description for the SSH key
Returns:
Response: A JSON response containing a list of tag names or an error message if the key can't be added.
- 200: A JSON object with a "status" key containing "added"
"""
data = request.json
if data is None:
journal.send(f"Can't add SSH keys, POST data missing", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing"}), 400
if not "description" in data:
data["description"] = ""
if not ("ssh_key" in data or "oglive" in data):
journal.send(f"Can't add SSH keys, either ssh_key or oglive is required", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({"error" : "Parameters missing, specify ssh_key or oglive"}), 400
module = _load_installer()
print(f"Got {module}")
OpengnsysGitInstaller = getattr(module, 'OpengnsysGitInstaller')
installer = OpengnsysGitInstaller()
if "oglive" in data:
job_id = f"GitSshKey_{''.join(random.choice('0123456789abcdef') for char in range(8))}"
threading.Thread(target=git_add_sshkey_task, args=(data["oglive"], data["description"], job_id)).start()
return jsonify({
"success": True,
"output": "Extracting key from ogLive...",
"job_id": job_id
}), 200
else:
status, content = installer.add_forgejo_sshkey(data["ssh_key"], data["description"])
message = "Result unrecognized"
success = False
httpcode = 500
if status == 200 or status == 201:
message = "SSH key added"
success = True
httpcode = 200
elif status == 422:
message = "SSH key already existed"
success = True
httpcode = 200
else:
message = "Unrecognized reply from forgejo"
success = False
httpcode = status
journal.send(f"Unrecognized reply from forgejo: code {status}, content {content}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
return jsonify({
"success": success,
"output": message,
}), httpcode
# --------------------------------------------------------------------------------------------

View File

@ -4,7 +4,7 @@ info:
version: "1.0"
description: |
---
# -----------------------------------------------------------------------------------------------------------
# Esto hace que el Swagger se ordene por los tags (apartados), de la forma especificada:
@ -18,7 +18,7 @@ tags:
- name: "Transferencia entre Repositorios y Backup"
- name: "Importar y Exportar Máquinas Virtuales"
- name: "Varios"
- name: "Git"
# -----------------------------------------------------------------------------------------------------------
# Apartado "Estado de ogRepository"
@ -30,7 +30,7 @@ paths:
summary: "Obtener Información de Estado de ogRepository"
description: >
Este endpoint ejecuta el script "**getRepoStatus.py**" y devuelve su salida en formato JSON,
incluyendo información sobre la CPU, memoria RAM, disco duro, servicios, y procesos específicos de ogRepository, e instalación de ogGit.
incluyendo información sobre la CPU, memoria RAM, disco duro, servicios, y procesos específicos de ogRepository, e instalación de ogGit.
tags:
- "Estado de ogRepository"
responses:
@ -140,14 +140,14 @@ paths:
# -----------------------------------------------------------------------------------------------------------
# Apartado "Información de Imágenes"
# -----------------------------------------------------------------------------------------------------------
/ogrepository/v1/images:
put:
summary: "Actualizar Información del Repositorio"
description: |
Este endpoint actualiza la información de las imágenes almacenadas en el repositorio, reflejándola en los archivos "**repoinfo.json**" y "**trashinfo.json**".
Utiliza el script "**updateRepoInfo.py**", que a su vez llama al script "**updateTrashInfo.py**", para actualizar también la información de la papelera.
No hace falta que se le llame al crear o exportar una imagen, ya que lo llama el endpoint "**Crear archivos auxiliares**" (que sí debe ser llamado en esos casos).
tags:
- "Información de Imágenes"
@ -192,7 +192,7 @@ paths:
get:
summary: "Obtener Información de todas las Imágenes"
description: |
Este endpoint ejecuta el script "**getRepoInfo.py**" con el parámetro "**all**", para devolver información de todas las imágenes almacenadas en el repositorio y en la papelera, que a su vez llama al script "**updateRepoInfo.py**", para actualizar previamente la información del repositorio.
Este endpoint ejecuta el script "**getRepoInfo.py**" con el parámetro "**all**", para devolver información de todas las imágenes almacenadas en el repositorio y en la papelera, que a su vez llama al script "**updateRepoInfo.py**", para actualizar previamente la información del repositorio.
Devuelve detalles como el nombre de la imagen, tipo, nombre del cliente, clonador, compresor, sistema de archivos, tamaño de los datos, tamaño de la imagen, y hashes MD5.
tags:
- "Información de Imágenes"
@ -1257,7 +1257,7 @@ paths:
# -----------------------------------------------------------------------------------------------------------
#/ogrepository/v1/p2p:
#/ogrepository/v1/p2p:
delete:
summary: "Cancelar Transmisiones P2P"
description: |
@ -1311,10 +1311,10 @@ paths:
Este endpoint importa la imagen especificada desde un repositorio remoto al repositorio local (en el que se ejecuta el endpoint).
Utiliza el script "**importImage.py**", que recibe como parámetros el nombre de la imagen, la IP o hostname del servidor remoto, y el usuario con el que conectar al servidor remoto.
que a su vez llama al script "**updateRepoInfo.py**", para actualizar la información del repositorio.
**NOTA**: Este endpoint es asíncrono, ya que puede tardar mucho tiempo, por lo que solo informa de que la imagen se está importando, y abre un proceso paralelo, que avisará a ogCore cuando finalice la tarea (llamando a un endpoint de ogCore).
tags:
- "Transferencia entre Repositorios y Backup"
- "Transferencia entre Repositorios y Backup"
parameters:
- name: JSON
in: body
@ -1436,7 +1436,7 @@ paths:
* **ID_img** - Identificador de la imagen, correspondiente al contenido del archivo 'full.sum'
* **repo_ip** - Dirección IP del servidor remoto
* **user** - Usuario con el que conectar al servidor remoto
* **remote_path** - Ruta remota en la que copiar la imagen
* **remote_path** - Ruta remota en la que copiar la imagen
schema:
type: object
properties:
@ -1457,7 +1457,7 @@ paths:
example: "/home/opengnsys"
responses:
"200":
description: "Se está haciendo backup de la imagen."
description: "Se está haciendo backup de la imagen."
schema:
type: object
properties:
@ -1532,20 +1532,20 @@ paths:
post:
summary: "Convertir Imagen Virtual a Imagen OpenGnsys"
description: |
Este endpoint convierte la imagen virtual especificada como primer parámetro en una imagen "img" como las que se generan desde OpenGnsys, debiendo haberse copiado previamente en la ruta "opt/opengnsys/ogrepository/images_virtual".
Utiliza el script "**convertVMtoIMG.py**", que recibe como parámetros el nombre de la imagen virtual, y el sistema de archivos de la partición a clonar (en formato "blkid").
Se puede comprobar todos los sistemas de archivos aceptados por "blkid" ejecutando el comando "blkid -k".
Este endpoint convierte la imagen virtual especificada como primer parámetro en una imagen "img" como las que se generan desde OpenGnsys, debiendo haberse copiado previamente en la ruta "opt/opengnsys/ogrepository/images_virtual".
Utiliza el script "**convertVMtoIMG.py**", que recibe como parámetros el nombre de la imagen virtual, y el sistema de archivos de la partición a clonar (en formato "blkid").
Se puede comprobar todos los sistemas de archivos aceptados por "blkid" ejecutando el comando "blkid -k".
**NOTA**: Este endpoint es asíncrono, ya que puede tardar mucho tiempo, por lo que solo informa de que la imagen virtual se está convirtiendo, y abre un proceso paralelo, que avisará a ogCore cuando finalice la tarea (llamando a un endpoint de ogCore).
tags:
- "Importar y Exportar Máquinas Virtuales"
- "Importar y Exportar Máquinas Virtuales"
parameters:
- name: JSON
in: body
required: true
description: |
* **virtual_image** - Nombre de la imagen virtual, con extensión
* **filesystem** - Sistema de archivos de la partición a clonar, en formato "blkid"
* **virtual_image** - Nombre de la imagen virtual, con extensión
* **filesystem** - Sistema de archivos de la partición a clonar, en formato "blkid"
schema:
type: object
properties:
@ -1640,19 +1640,19 @@ paths:
put:
summary: "Convertir Imagen OpenGnsys a Imagen Virtual"
description: |
Este endpoint convierte la imagen "img" especificada como primer parámetro en una imagen virtual con la extensión especificada como segundo parámetro ("vdi", "vmdk", etc), guardándola en la ruta "opt/opengnsys/ogrepository/images_virtual/export".
Utiliza el script "**convertIMGtoVM.py**", que recibe como parámetros el nombre de la imagen, y la extensión del disco virtual destino ("vdi", "vmdk", etc).
Este endpoint convierte la imagen "img" especificada como primer parámetro en una imagen virtual con la extensión especificada como segundo parámetro ("vdi", "vmdk", etc), guardándola en la ruta "opt/opengnsys/ogrepository/images_virtual/export".
Utiliza el script "**convertIMGtoVM.py**", que recibe como parámetros el nombre de la imagen, y la extensión del disco virtual destino ("vdi", "vmdk", etc).
**NOTA**: Este endpoint es asíncrono, ya que puede tardar mucho tiempo, por lo que solo informa de que la imagen se está convirtiendo a virtual, y abre un proceso paralelo, que avisará a ogCore cuando finalice la tarea (llamando a un endpoint de ogCore).
tags:
- "Importar y Exportar Máquinas Virtuales"
- "Importar y Exportar Máquinas Virtuales"
parameters:
- name: JSON
in: body
required: true
description: |
* **ID_img** - Identificador de la imagen, correspondiente al contenido del archivo 'full.sum'
* **vm_extension** - Extensión del disco virtual destino ("vdi", "vmdk", etc)
* **vm_extension** - Extensión del disco virtual destino ("vdi", "vmdk", etc)
schema:
type: object
properties:
@ -1883,7 +1883,7 @@ paths:
summary: "Renombrar una Imagen"
description: |
Este endpoint renombra la imagen especificada como primer parámetro (y todos sus archivos asociados), asignando el nombre especificado como segundo parámetro.
Utiliza el script "**renameImage.py**", que recibe como parámetros el nombre original y el nuevo nombre a asignar.
Utiliza el script "**renameImage.py**", que recibe como parámetros el nombre original y el nuevo nombre a asignar.
tags:
- "Varios"
parameters:
@ -1960,3 +1960,521 @@ paths:
example: "(Exception description)"
# -----------------------------------------------------------------------------------------------------------
# -----------------------------------------------------------
# ____ _ _
# / ___(_) |_
# | | _| | __|
# | |_| | | |_
# \____|_|\__|
#
# -----------------------------------------------------------
/ogrepository/v1/git/ssh_key:
post:
summary: "Agregar clave de SSH de ogLive"
description: |
Agrega una clave de SSH que va a usarse desde el ogLive
para interactuar con Git.
Es necesario especificar **ssh_key** o **oglive**.
Especificando **ssh_key** se agrega la cclave especificada directamente.
Especificando **oglive** se descarga si es necesario el .iso, se monta, y se extrae la clave.
Esta acción se hace en el fondo, y se devuelve un job_id.
tags:
- "Git"
parameters:
- name: JSON
in: body
required: true
description: |
* **ssh_key** - Clave de SSH (opcional)
* **oglive** - URL a ogLive (opcional, NO USAR DE MOMENTO)
* **description** - Descripcion (opcional)
schema:
type: object
properties:
ssh_key:
type: string
example: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINEOttwhJ+9THRZ1Zv/6QUwPUDq1X7opG9V7EFLVWxQV"
required: False
description:
type: string
example: "OgLive r20250518"
required: False
oglive:
type: string
example: "https://ognproject.evlt.uma.es/oglive/ogLive-noble-6.8.0-31-generic-amd64-r20250518.cf13a6d_20250519.iso"
required: False
responses:
"200":
description: "Exito"
schema:
type: object
properties:
success:
type: boolean
example: True
required: True
output:
type: string
example: "SSH key added"
required: True
job_id:
type: string
example: "GitSshKey_873f353f"
required: False
/ogrepository/v1/git/repositories:
get:
summary: "Obtener lista de repositorios"
description: |
Devuelve una lista de repositorios de Git
tags:
- "Git"
responses:
"200":
description: "Lista de repositorios"
schema:
type: object
properties:
repositories:
type: array
items:
type: string
example: linux
"500":
description: "Excepción"
schema:
type: object
properties:
success:
type: boolean
example: false
exception:
type: string
example: "(Exception description)"
post:
summary: "Crear repositorio"
description: |
Crea un repositorio nuevo de Git
tags:
- "Git"
parameters:
- name: JSON
in: body
required: true
description: |
* **name** - Nombre de repositorio
schema:
type: object
properties:
name:
type: string
example: linux
responses:
"201":
description: "Repositorio creado"
schema:
type: object
properties:
status:
type: string
example: "Repository created"
"500 (Exception)":
description: "JSON post data missing"
schema:
type: object
properties:
error:
type: string
example: "Parameters missing"
/ogrepository/v1/git/repositories/{repository}/tags:
get:
summary: "Obtener lista de tags"
description: |
Devuelve una lista de tags de Git
tags:
- "Git"
parameters:
- name: repository
in: path
required: true
type: string
description: "Nombre de repositorio"
responses:
"200":
description: "Lista de tags"
schema:
type: object
properties:
tags:
type: array
items:
type: object
properties:
name:
type: string
example: v0.1
commit:
type: string
example: db8e84d5d2548f589ee503c1c6d5003cc6a0d803
committer:
type: string
example: John Smith
committed_datetime:
type: int
example: 1745360193
message:
type: string
example: "Initial release"
required: False
tagged_date:
type: int
example: 1745360194
required: False
tagger:
type: string
example: John Smith
required: False
tagger_tz_offset:
type: int
example: -7200
required: False
"500":
description: "Excepción"
schema:
type: object
properties:
success:
type: boolean
example: false
exception:
type: string
example: "(Exception description)"
post:
summary: "Crear tag"
description: |
Crea una tag de git
tags:
- "Git"
parameters:
- name: repository
in: path
required: true
type: string
description: "Nombre de repositorio"
- name: JSON
in: body
required: true
description: |
* **name** - Nombre del tag
* **commit** - Commit al que apunta el tag nuevo. Puede ser un nombre de otra rama/tag.
* **message** - Mensaje descriptivo para el tag. Opcional, si no se especifica se asume una cadena vacía.
schema:
type: object
properties:
name:
type: string
example: v1.0
required: True
commit:
type: string
example: HEAD
required: True
message:
type: string
example: Version 1.0
required: False
responses:
"201":
description: "Tag creado"
schema:
type: object
properties:
status:
type: string
example: created
"500":
description: "Excepción"
schema:
type: object
properties:
success:
type: boolean
example: false
exception:
type: string
example: "(Exception description)"
"404":
description: "El repositorio no existe"
schema:
type: object
properties:
error:
type: string
example: "Repository not found"
"409":
description: "El tag ya existe"
schema:
type: object
properties:
error:
type: string
example: "Tag already exists"
delete:
summary: "Eliminar tag"
description: |
Elimina un tag de git
tags:
- "Git"
parameters:
- name: repository
in: path
required: true
type: string
description: "Nombre de repositorio"
- name: tag
in: path
required: true
type: string
description: "Rama del repositorio"
responses:
"200":
description: "Tag eliminado"
schema:
type: object
properties:
status:
type: string
example: deleted
"500":
description: "Excepción"
schema:
type: object
properties:
success:
type: boolean
example: false
exception:
type: string
example: "(Exception description)"
/ogrepository/v1/git/repositories/{repository}/branches/{branch}/commits:
get:
summary: "Obtener lista de commits en una rama"
description: |
Devuelve una lista de commits de Git
tags:
- "Git"
parameters:
- name: repository
in: path
required: true
type: string
description: "Nombre de repositorio"
- name: branch
in: path
required: true
type: string
description: "Rama dentro del repositorio"
- name: max_commits
in: query
required: false
type: int
description: "Máximo de commits a obtener"
- name: skip
in: query
required: false
type: int
description: "Commits a saltar (para paginación)"
responses:
"200":
description: "Lista de commits"
schema:
type: object
properties:
commits:
type: array
items:
type: object
properties:
committed_date:
type: int
example: 1745360193
hexsha:
type: string
example: "db8e84d5d2548f589ee503c1c6d5003cc6a0d803"
message:
type: string
example: "Install updates"
size:
type: int
example: 67108864
tags:
type: array
example: ["updates"]
"500":
description: "Excepción"
schema:
type: object
properties:
success:
type: boolean
example: false
exception:
type: string
example: "(Exception description)"
"404":
description: "El repositorio o branch no existe"
schema:
type: object
properties:
error:
type: string
example: "Repository not found"
/ogrepository/v1/git/repositories/{repository}/branches:
get:
summary: "Obtener lista de branches"
description: |
Devuelve una lista de branches de Git
tags:
- "Git"
parameters:
- name: repository
in: path
required: true
type: string
description: "Nombre de repositorio"
responses:
"200":
description: "Lista de branches"
schema:
type: object
properties:
branches:
type: array
items:
type: string
example: main
"500":
description: "Excepción"
schema:
type: object
properties:
success:
type: boolean
example: false
exception:
type: string
example: "(Exception description)"
"404":
description: "El repositorio no existe"
schema:
type: object
properties:
error:
type: string
example: "Repository not found"
"409":
description: "El branch ya existe"
schema:
type: object
properties:
error:
type: string
example: "Branch already exists"
post:
summary: "Crear branch"
description: |
Crea una rama de git
tags:
- "Git"
parameters:
- name: repository
in: path
required: true
type: string
description: "Nombre de repositorio"
- name: JSON
in: body
required: true
description: |
* **commit** - Commit al que apunta la rama nueva. Puede ser un nombre de otra rama/tag.
schema:
type: object
properties:
name:
type: string
example: devel
required: True
commit:
type: string
example: HEAD
required: True
responses:
"201":
description: "Rama creada"
schema:
type: array
items:
type: string
example: main
"500":
description: "Git no instalado"
schema:
type: object
properties:
success:
type: boolean
example: false
exception:
type: string
example: "(Exception description)"
delete:
summary: "Eliminar branch"
description: |
Elimina una rama de git
tags:
- "Git"
parameters:
- name: repository
in: path
required: true
type: string
description: "Nombre de repositorio"
- name: branch
in: path
required: true
type: string
description: "Branch del repositorio"
responses:
"200":
description: "Branch eliminado"
schema:
type: object
properties:
status:
type: string
example: deleted
"500":
description: "Excepción"
schema:
type: object
properties:
success:
type: boolean
example: false
exception:
type: string
example: "(Exception description)"

View File

@ -134,8 +134,8 @@ def create_torrent(file_path, torrent_file, datafullsum):
repo_ip = get_IPlocal()
tracker_url = f"http://{repo_ip}:6969/announce"
# Creamos una lista con el comando para crear el torrrent, y lo imprimimos con espacios:
splitted_cmd = f"nice -n 0 ctorrent -t {file_path} -u {tracker_url} -s {torrent_file} -c {datafullsum} -l 4194304".split()
# Creamos una litas para ejecutar el comando mktorrent para crear el archivo torrent
splitted_cmd = f"nice -n 0 mktorrent -a {tracker_url} -c {datafullsum} -o {torrent_file} {file_path}".split()
print(f"Sending command: {' '.join(splitted_cmd)}")
# Ejecutamos el comando en el sistema, e imprimimos el resultado:

View File

@ -106,11 +106,11 @@ def main():
total_disk, used_disk, free_disk, percent_disk = get_disk_info()
# Obtenemos el estado de los servicios listados, que almacenamos en un diccionario:
service_list = ['ssh', 'smbd', 'rsync']
service_list = ['ssh', 'smbd', 'opentracker']
services_status = {service: get_service_status(service) for service in service_list}
# Obtenemos el estado de los procesos listados, que almacenamos en un diccionario:
process_list = ['udp-sender', 'uftp', 'bttrack', 'btlaunchmany.bittornado']
process_list = ['udp-sender', 'uftp', 'aria2c']
process_status = {process: get_process_status(process) for process in process_list}
# Creamos un diccionario con toda la información obtenida:

View File

@ -16,6 +16,8 @@ No recibe ningún parámetro.
import os
import sys
import subprocess
import psutil
import re
from systemd import journal
@ -31,29 +33,73 @@ repo_path = '/opt/opengnsys/ogrepository/images' # En este caso, no lleva barra
# --------------------------------------------------------------------------------------------
def run_bittornado(repo_path):
""" Ejecuta el comando "btlaunchmany.bittornado", con sus parámetros correspondientes.
Además, captura el resultado y los posibles errores, y los imprime.
"""
# Creamos una lista con el comando "btlaunchmany.bittornado" y sus parámetros, y lo imprimimos con espacios:
splitted_cmd = f"btlaunchmany.bittornado {repo_path}".split()
print(f"Sending command: {' '.join(splitted_cmd)}")
journal.send(f"runTorrentSeeder.py: Running command: {' '.join(splitted_cmd)}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
# Ejecutamos el comando "btlaunchmany.bittornado" en el sistema, e imprimimos el resultado:
# def run_bittornado(repo_path):
# """ Ejecuta el comando "btlaunchmany.bittornado", con sus parámetros correspondientes.
# Además, captura el resultado y los posibles errores, y los imprime.
# """
# # Creamos una lista con el comando "btlaunchmany.bittornado" y sus parámetros, y lo imprimimos con espacios:
# splitted_cmd = f"btlaunchmany.bittornado {repo_path}".split()
# print(f"Sending command: {' '.join(splitted_cmd)}")
# journal.send(f"runTorrentSeeder.py: Running command: {' '.join(splitted_cmd)}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
# # Ejecutamos el comando "btlaunchmany.bittornado" en el sistema, e imprimimos el resultado:
# try:
# result = subprocess.run(splitted_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# journal.send(f"runTorrentSeeder.py: Command ReturnCode: {result.returncode}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
# print(f"Bittornado ReturnCode: {result.returncode}")
# except subprocess.CalledProcessError as error:
# journal.send("runTorrentSeeder.py: Process finalized", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
# print(f"Bittornado ReturnCode: {error.returncode}")
# print(f"Bittornado Error Output: {error.stderr.decode()}")
# except Exception as error:
# journal.send(f"runTorrentSeeder.py: Command exception: {error}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
# print(f"Unexpected bittornado error: {error}")
def run_aria2c_seeder(image_name):
"""Lanza aria2c como seeder puro para una imagen concreta ya existente."""
repo_path = '/opt/opengnsys/ogrepository/images'
torrent_file = os.path.join(repo_path, f"{image_name}.img.torrent")
image_file = os.path.join(repo_path, f"{image_name}.img")
# Verificación básica
if not os.path.exists(torrent_file):
print(f"Torrent file not found: {torrent_file}")
journal.send(f"Seeder error: Torrent file not found: {torrent_file}",
PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
return
if not os.path.exists(image_file):
print(f"Image file not found: {image_file}")
journal.send(f"Seeder error: Image file not found: {image_file}",
PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
return
# Comando aria2c como seeder puro
cmd = [
'aria2c',
'--enable-peer-exchange=true',
'--bt-seed-unverified=true',
'--check-integrity=true',
'--seed-ratio=0.0',
'--dir=' + repo_path,
torrent_file
]
journal.send(f"Launching aria2c seeder for {image_name}",
PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
journal.send(f"Command: {' '.join(cmd)}",
PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
print("Running command:", ' '.join(cmd))
try:
result = subprocess.run(splitted_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
journal.send(f"runTorrentSeeder.py: Command ReturnCode: {result.returncode}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
print(f"Bittornado ReturnCode: {result.returncode}")
except subprocess.CalledProcessError as error:
journal.send("runTorrentSeeder.py: Process finalized", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
print(f"Bittornado ReturnCode: {error.returncode}")
print(f"Bittornado Error Output: {error.stderr.decode()}")
except Exception as error:
journal.send(f"runTorrentSeeder.py: Command exception: {error}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
print(f"Unexpected bittornado error: {error}")
subprocess.run(cmd, check=True)
except subprocess.CalledProcessError as e:
journal.send(f"aria2c seeder failed: {e}",
PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
print(f"Seeder process exited with code {e.returncode}")
# --------------------------------------------------------------------------------------------
# MAIN
@ -64,16 +110,40 @@ def main():
"""
"""
# Finalizamos el proceso "btlaunchmany.bittornado" (en caso de que estuviera corriendo):
try:
journal.send("runTorrentSeeder.py: Killing process 'btlaunchmany.bittornado'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
subprocess.run(f"pkill btlaunchmany".split(), check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except Exception as error_description:
journal.send("runTorrentSeeder.py: No 'btlaunchmany.bittornado' process running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
print(f"No btlaunchmany.bittornado process running? Returned error: {error_description}")
if len(sys.argv) != 2:
print("Usage: runTorrentSeeder.py <image_name>")
journal.send("runTorrentSeeder.py: Invalid number of arguments. Expected 1 argument: <image_name>",
PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
sys.exit(1)
image_name = sys.argv[1]
torrent_file = os.path.join(repo_path, f"{image_name}.img.torrent")
found = False
# Matamos los procesos de aria2c que sirvan la imagen en concreto. Chequeamos todos los procesos
# Ejecutamos el comando "btlaunchmany.bittornado" (para hacer seed de los torrents):
run_bittornado(repo_path)
journal.send(f"runTorrentSeeder.py: looking for aria2c processes for {image_name}.torrent",
PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
for proc in psutil.process_iter(['pid','name','cmdline']):
try:
if proc.info['name'] != 'aria2c':
continue
if any(arg.endswith(torrent_file) for arg in proc.info['cmdline']):
proc.terminate()
found = True
print(f"Killed aria2c process with PID {proc.info['pid']} for {image_name}.torrent")
journal.send(f"runTorrentSeeder.py: Killed aria2c process with PID {proc.info['pid']} for {image_name}.torrent",
PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
if not found:
print(f"No aria2c process found for {image_name}.torrent")
journal.send(f"runTorrentSeeder.py: No aria2c process found for {image_name}.torrent",
PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG")
# Lanzamos aria2c como seeder para la imagen proporcionada
run_aria2c_seeder(image_name)
# --------------------------------------------------------------------------------------------

20
debian/changelog vendored
View File

@ -1,3 +1,23 @@
ogrepository (1.1.2) UNRELEASED; urgency=medium
* Fix path handling when there are no repositories
* Add another search path for installer module
* Fix path handling
* Add SSH key endpoint
* Update dependencies
-- OpenGnsys <opengnsys@opengnsys.com> Tue, 30 Jun 2025 15:20:23 +0000
ogrepository (1.1.1) unstable; urgency=medium
* OgGit - fix repo path handling
-- Tu Nombre <tuemail@example.com> Jue, 5 Jun 2025 08:43:58 +0000
ogrepository (1.1.0) unstable; urgency=medium
* OgGit
-- Tu Nombre <tuemail@example.com> Tue, 11 Mar 2025 04:43:58 +0000
ogrepository (1.0.0+deb-packages20250311-1) unstable; urgency=medium
* First debian version

4
debian/control vendored
View File

@ -8,7 +8,7 @@ Build-Depends: debhelper-compat (= 12)
Package: ogrepository
Architecture: all
Pre-Depends: debian-archive-keyring , debconf (>= 1.5.0),
Depends: ${misc:Depends}, git, python3, python3-pip, python3-flask, python3-paramiko, python3-psutil, python3-flasgger, samba, gunicorn, wakeonlan , lzop , partclone , qemu-utils , udpcast, uftp
Depends: ${misc:Depends}, git, python3, python3-pip, python3-flask, python3-paramiko, python3-psutil, python3-flasgger, samba, gunicorn, wakeonlan , lzop , partclone , qemu-utils , udpcast, uftp, mktorrent, aria2 , opengnsys-opentracker, opengnsys-gitinstaller, opengnsys-forgejo
Description: Ogrepsoitory Package
This package provides Ogrepository service.
X-OG-Release: opengnsys-1.6.0-beta, opengnsys-1.6.1-beta

View File

@ -1,10 +1,29 @@
#!/bin/sh
set -e
set -x
set -x
. /usr/share/debconf/confmodule
restore_config_if_modified() {
local new="$1"
local backup="$1.bak.upgrade_package"
if [ -f "$backup" ]; then
if ! cmp -s "$new" "$backup"; then
echo ">>> Archivo modificado por el usuario detectado en $new"
echo " - Guardando archivo nuevo como ${new}.new"
mv -f "$new" "${new}.new"
echo " - Restaurando archivo anterior desde backup"
mv -f "$backup" "$new"
else
echo ">>> El archivo $new no ha cambiado desde la última versión, eliminando backup"
rm -f "$backup"
fi
fi
}
# Cargar variables de configuración
db_get opengnsys/ogrepository_ogrepoIp
OGREPO_IP="$RET"
@ -19,10 +38,20 @@ SAMBA_PASS="$RET"
USER="opengnsys"
# Provisionar base de datos si es necesario en caso de instalación.
# Provisionar base de datos si es necesario en caso de instalación.
# Detectar si es una instalación nueva o una actualización
# if [ "$1" = "configure" ] && [ -z "$2" ]; then
# systemd-run --no-block /bin/bash -c "
# sleep 10;
# apt update -y;
# for pkg in bittorrent bittornado ctorrent; do
# if ! dpkg -l | grep -qw \"\$pkg\"; then
# apt install -y \"\$pkg\"
# fi
# done
# "
if [ "$1" = "configure" ] && [ -z "$2" ]; then
systemd-run --no-block /bin/bash -c "
sleep 10;
@ -33,7 +62,7 @@ for pkg in bittorrent bittornado ctorrent; do
fi
done
"
sed -i "s/%%OGREPOSITORY_USER%%/$SAMBA_USER/g" /etc/systemd/system/ogrepo-api.service
sed -i "s/%%OGREPOSITORY_USER%%/$SAMBA_USER/g" /etc/samba/ogrepo-smb.conf
@ -49,7 +78,7 @@ done
chmod 700 $OPENGNSYS_HOME/.ssh
chmod 600 $OPENGNSYS_HOME/.ssh/id_ed25519
chmod 644 $OPENGNSYS_HOME/.ssh/id_ed25519.pub
# Genera authorized_keys
# Genera authorized_keys
cat $OPENGNSYS_HOME/.ssh/id_ed25519.pub >> $OPENGNSYS_HOME/.ssh/authorized_keys
chmod 600 $OPENGNSYS_HOME/.ssh/authorized_keys
chown -R opengnsys:opengnsys $OPENGNSYS_HOME/.ssh
@ -62,7 +91,7 @@ done
fi
(echo "$SAMBA_PASS"; echo "$SAMBA_PASS") | smbpasswd -a $SAMBA_USER
fi
systemctl enable ogrepo-api
# Configure Repo
cp /opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg.tmpl /opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg
@ -70,11 +99,11 @@ done
sed -i "s/OGCOREIP/$OGCORE_IP/g" /opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg
# Copiar sudoers file ogrepository/etc/opengnsys-repository
cp /opt/opengnsys/ogrepository/etc/opengnsys-repository /etc/sudoers.d/opengnsys
cp /opt/opengnsys/ogrepository/etc/opengnsys-repository /etc/sudoers.d/opengnsys
elif [ "$1" = "configure" ] && [ -n "$2" ]; then
echo "Actualización desde la versión $2"
# Recopy static files without configuration
cp /opt/opengnsys/ogrepository/etc/opengnsys-repository /etc/sudoers.d/opengnsys-repository
# Recopy static files without configuration
cp /opt/opengnsys/ogrepository/etc/opengnsys-repository /etc/sudoers.d/opengnsys-repository
OPENGNSYS_HOME=$(getent passwd opengnsys | cut -d: -f6)
# Create .ssh directory
mkdir -p $OPENGNSYS_HOME/.ssh
@ -83,13 +112,23 @@ elif [ "$1" = "configure" ] && [ -n "$2" ]; then
cp /opt/opengnsys/ogrepository/etc/opengnsys.pub $OPENGNSYS_HOME/.ssh/id_ed25519.pub
cat $OPENGNSYS_HOME/.ssh/id_ed25519.pub >> $OPENGNSYS_HOME/.ssh/authorized_keys
restore_config_if_modified "/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg"
restore_config_if_modified "/opt/opengnsys/ogrepository/etc/repoinfo.json"
restore_config_if_modified "/opt/opengnsys/ogrepository/etc/trashinfo.json"
restore_config_if_modified "/etc/samba/smb.conf"
restore_config_if_modified "/etc/samba/ogrepo-smb.conf"
restore_config_if_modified "/etc/sudoers.d/opengnsys-repository"
fi
# Cambiar la propiedad de los archivos al usuario especificado
chown opengnsys:www-data /opt/opengnsys/
chown -R opengnsys:www-data /opt/opengnsys/ogrepository
chmod 755 /opt/opengnsys
chmod 755 /opt/opengnsys/ogrepository/bin/*
# Install http server stuff
# Reiniciar servicios si es necesario
# systemctl restart nombre_del_servicio

View File

@ -2,8 +2,18 @@
set -e
KEY_FILE="/usr/share/keyrings/debian-archive-buster-stable.gpg"
REPO_FILE="/etc/apt/sources.list.d/buster.list"
backup_file_if_exists() {
local original="$1"
local backup="$1.bak.upgrade_package"
if [ -e "$original" ]; then
echo " - Guardando backup de $original en $backup"
cp -a "$original" "$backup"
fi
}
# KEY_FILE="/usr/share/keyrings/debian-archive-buster-stable.gpg"
# REPO_FILE="/etc/apt/sources.list.d/buster.list"
# Asegurarse de que el usuario exista
USER="opengnsys"
@ -20,10 +30,17 @@ echo "Añadiendo el repositorio de Debian Buster en $REPO_FILE..."
mkdir -p "$(dirname "$REPO_FILE")"
# Crear el archivo de repositorio si no existe
if [ ! -f "$REPO_FILE" ]; then
echo "deb [signed-by=$KEY_FILE] http://ftp.de.debian.org/debian buster main" > "$REPO_FILE"
else
echo "El repositorio ya está configurado en $REPO_FILE"
fi
# if [ ! -f "$REPO_FILE" ]; then
# echo "deb [signed-by=$KEY_FILE] http://ftp.de.debian.org/debian buster main" > "$REPO_FILE"
# else
# echo "El repositorio ya está configurado en $REPO_FILE"
# fi
backup_file_if_exists "/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg"
backup_file_if_exists "/opt/opengnsys/ogrepository/etc/repoinfo.json"
backup_file_if_exists "/opt/opengnsys/ogrepository/etc/trashinfo.json"
backup_file_if_exists "/etc/samba/smb.conf"
backup_file_if_exists "/etc/samba/ogrepo-smb.conf"
backup_file_if_exists "/etc/sudoers.d/ogrepository"
exit 0

View File

@ -25,5 +25,7 @@ Cmnd_Alias KILL_BT = \
/usr/bin/pkill -9 bttrack, \
/usr/bin/kill -9 *
Cmnd_Alias PYTHON_OGREPO = /usr/bin/python3 /opt/opengnsys/ogrepository/bin/*
# Permitir al usuario opengnsys ejecutar estos comandos sin contraseña
opengnsys ALL=(root) NOPASSWD: MOUNT_RECOVERY, CHROOT_GRUB, LOOP_KPARTX, KILL_BT
opengnsys ALL=(root) NOPASSWD: MOUNT_RECOVERY, CHROOT_GRUB, LOOP_KPARTX, KILL_BT, PYTHON_OGREPO