2026-01-18 19:59:58 +11:00
import requests
import os
import sys
import tempfile
import tarfile
2026-01-19 21:06:25 +11:00
import configparser
import shutil
2026-01-18 19:59:58 +11:00
from rich . console import Console
from rich . progress import SpinnerColumn
2026-01-19 07:03:51 +11:00
from util import check_ground_libs_path , check_sudo
2026-01-18 19:59:58 +11:00
console = Console ( )
2026-01-19 21:06:25 +11:00
def install_package ( package_name , version , args , is_dependency : bool = False ) :
2026-01-18 19:59:58 +11:00
retries_left = args . max_retries
with console . status ( " Downloading tarball... " , spinner = " bouncingBall " , spinner_style = " blue " ) as status :
2026-01-19 21:06:25 +11:00
console . print ( f " Installing { package_name } [d]( { version } )[/] " )
2026-01-18 19:59:58 +11:00
while retries_left > 0 :
# grab the tar ball
2026-01-19 06:27:44 +11:00
response = requests . get ( f " https://chookspace.com/api/packages/ground/generic/ { package_name } / { version } /mineral.tar " )
2026-01-18 19:59:58 +11:00
# check response code for errors
if response . status_code == 404 : # package doesn't exist
console . print ( f " [b red]digpkg: mineral \" { package_name } \" was not found. Check to make sure the name and version number are correct.[/] " )
sys . exit ( 1 )
elif response . status_code != 200 :
retries_left - = 1
console . print ( f " [b yellow]digpkg: failed to download mineral \" { package_name } \" : { response . content . decode ( ) } ( { retries_left } retries left)[/] " )
if retries_left == 0 :
console . print ( f " [b red]digpkg: exceeded max retries while downloading mineral \" { package_name } \" [/] " )
sys . exit ( 1 )
continue
2026-01-18 21:26:23 +11:00
response . raise_for_status ( )
2026-01-18 19:59:58 +11:00
break
# create temporary file for tarball
2026-01-18 21:26:23 +11:00
try :
f = tempfile . TemporaryFile ( " wb+ " )
f . write ( response . content )
f . flush ( )
f . seek ( 0 )
console . print ( " [d][:white_check_mark:] Tarball downloaded![/] " )
except KeyboardInterrupt :
console . print ( " [b yellow]digpkg: operation cancelled by user[/] " )
return
2026-01-18 19:59:58 +11:00
# extract the tarball to the GROUND_LIBS folder
status . update ( " Extracting... " )
extract_dir = os . getenv ( " GROUND_LIBS " )
if not os . path . isdir ( extract_dir ) : # gotta ensure the folder exists
os . mkdir ( extract_dir )
tar_file = tarfile . open ( fileobj = f )
tar_file . extractall ( extract_dir )
f . close ( )
2026-01-19 06:32:18 +11:00
console . print ( f " [d][:white_check_mark:] Extracted to { extract_dir } . " )
console . status ( " Finishing up... " )
2026-01-19 07:03:51 +11:00
2026-01-19 21:06:25 +11:00
package_folder = os . path . join ( extract_dir , f " { package_name } / " )
if not os . path . isfile ( os . path . join ( package_folder , " mineral.ini " ) ) :
console . print ( f " [b red]digpkg: failed to install { package_name } : the mineral doesn ' t have a mineral.ini file, please contact the maintainer because they ' ve set up the mineral incorrectly. " )
console . print ( " [d]Cleaning up failed install...[/] " )
shutil . rmtree ( os . path . join ( extract_dir , package_name ) )
sys . exit ( 1 )
config_parser = configparser . ConfigParser ( )
config_parser . read ( os . path . join ( package_folder , " mineral.ini " ) )
dependencies = config_parser [ " dependencies " ]
2026-01-19 07:03:51 +11:00
# create a symlink from the main.so file to the ground libs folder so ground can find it
symlink_path = os . path . join ( extract_dir , f " { package_name } .so " ) # the path where the symlink is
if not os . path . isfile ( symlink_path ) :
os . symlink ( os . path . join ( extract_dir , package_name , " main.so " ) , symlink_path )
2026-01-19 21:06:25 +11:00
if is_dependency :
console . print ( f " [d][:white_check_mark:] Done installing dependency: { package_name } [/] " )
for dependency , dependency_version in dependencies . items ( ) :
install_package ( dependency , dependency_version , args , True )
if not is_dependency :
console . print ( f " \n [b green][:white_check_mark:] Installed { package_name } ![/] " )
2026-01-19 07:29:55 +11:00
def install ( args ) :
check_sudo ( )
check_ground_libs_path ( )
for package in args . names :
# figure out which version to install
package_name = package
2026-01-19 19:16:59 +11:00
version = " 1.0.0 "
2026-01-19 07:29:55 +11:00
if " @ " in package :
split = package . split ( " @ " )
package_name = split [ 0 ]
version = split [ 1 ]
2026-01-19 19:16:59 +11:00
else :
console . print ( f " [b red]digpkg: failed to install package: please specify version to install for { package_name } [/] " )
sys . exit ( 1 )
2026-01-19 07:29:55 +11:00
install_package ( package_name , version , args )