initial commit

This commit is contained in:
2024-11-16 17:13:02 +01:00
commit 1466e4fb23
30 changed files with 2498 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.vscode/

View File

@@ -0,0 +1,9 @@
{
"files": [
[
"lib/arutils/",
"/usr/lib/arutils"
]
],
"config": []
}

9
arutils/arutils.manifest Normal file
View File

@@ -0,0 +1,9 @@
{
["manifestVersion"] = "1.0",
["package"] = "arutils",
["version"] = "1.0.0"
["name"] = "arutils",
["repo"] = "tree/master/arutils",
["description"] = "Misc utilities functions",
["authors"] = "AR2000AR"
}

View File

@@ -0,0 +1,5 @@
package.path = package.path .. ";/usr/lib/?.lua;/usr/lib/?/init.lua"
local utils = {}
utils.parse = require "utils.parse"
return utils

View File

@@ -0,0 +1,32 @@
--https://github.com/MightyPirates/OpenComputers/blob/master-MC1.7.10/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua
local function parse(...)
local params = table.pack(...)
local args = {}
local options = {}
local doneWithOptions = false
for i = 1, params.n do
local param = params[i]
if not doneWithOptions and type(param) == "string" then
if param == "--" then
doneWithOptions = true -- stop processing options at `--`
elseif param:sub(1, 2) == "--" then
local key, value = param:match("%-%-(.-)=(.*)")
if not key then
key, value = param:sub(3), true
end
options[key] = value
elseif param:sub(1, 1) == "-" and param ~= "-" then
for j = 2, utf8.len(param) do
options[string.sub(param, j, j)] = true
end
else
table.insert(args, param)
end
else
table.insert(args, param)
end
end
return args, options
end
return parse

81
pm/README.md Normal file
View File

@@ -0,0 +1,81 @@
# pm Package Manager
Package manager for OpenOS. Use tar archive as package container
## Installation
- run `oppm install pm`
or
Create a installation floppy. Installation and script can be found [here](../pm_installer/).
## Usage
**Install a package :**\
`pm install [--dry-run] [--allow-same-version] package.tar`\
**Uninstall a package**\
`pm uninstall [--purge] [--dry-run] pakageName`\
**List installed packages :**\
`pm list-installed [--include-removed]`\
## Package format
### File tree
A package is a tar archive with the following data structure
```
/
|---DATA
|---CONTROL
|---manifest
```
The `DATA` folder contain all files installed by the package. The `DATA` folder is the `/` of the OS similar of how `install` work.
### Manifest file
The manifest file describe the package. It is a serialization compatible file.
```
{
manifestVersion = "1.0",
package = "example",
dependencies = {
["neededpackage"] = "=1.0"
},
configFiles = {
"/etc/example.conf"
}, --list configurations files that need to be left on update / uninstallation of the package
name = "Example Package",
version = "1.0.0",
description = "Package description",
authors = "AR2000AR",
note = "Extra bit of information",
hidden = false,
repo = "https://github.com/AR2000AR/openComputers_codes"
}
```
### manifestVersion :
The manifest file version. Currently `1.0`
### package :
The package's name. Different from the display name.
### dependencies :
The package's dependencies. The key is a package name, and value a version. Version is in the format `[>=]major.minor.patch`. `minor` and `patch` can be omitted.
### configFiles :
A table of all configurations files. They will not be overridden on update or removed by default during uninstallation.
### name :
The display name
### version :
The package's version. Version is in the format `major.minor.patch`. `minor` and `patch` can be omitted.\
A other valid version number is `"oppm"` for oppm packages without a version number
### description :
Package's description
### note :
Extra information about the package
### authors :
List of authors
### hidden :
Hide the package from package managers's install candidate list
### repo :
URL to the source code
## Packaging a application
### Manually
- Create a folder with the same file structure as describe above
- Write the package manifest's file
- Create a tar archive with the tool of your choice. For example, while being the the folder, do `tar -c -f ../mypackage.tar *`
### From a cloned oppm repo
- Call the [repoPackager.py](tools/repoPackager.py) from the terminal while in the repository. If the default settings don't fit your need, call it with the `-h` option to see what can be changed.

324
pm/bin/pm.lua Normal file
View File

@@ -0,0 +1,324 @@
package.path = package.path .. ";/usr/lib/?.lua;/usr/lib/?/init.lua"
local expect = require "cc.expect"
local checkArg = expect.expect
local tar = require("tar")
local pm = require("libpm")
local parse = require "arutils.parse"
local RM_BLACKLIST = {
["/bin/"] = true,
["/boot/"] = true,
["/dev/"] = true,
["/etc/"] = true,
["/etc/rc.d/"] = true,
["/home/"] = true,
["/lib/"] = true,
["/mnt/"] = true,
["/tmp/"] = true,
["/usr/"] = true,
["/usr/lib/"] = true,
["/usr/bin/"] = true,
}
local args, opts = parse(...) --TODO : parse args
local f = string.format
local function printf(...)
print(f(...))
end
local function printHelp()
printf("pm [opts] <mode> [args]")
printf("mode :")
printf("\tinstall <packageFile>")
printf("\tuninstall <packageName>")
printf("\tinfo <packageName>|<packageFile>")
printf("\tlist-installed")
printf("opts :")
printf("\t--purge : remove configuration files")
printf("\t--dry-run : do not really perform the operation")
printf("\t--include-removed : also list non completly removed packages")
printf("\t--allow-same-version : allow a package of the same version to be used for to update the installed version")
end
local function isConfigPath(configPaths, test)
for path, _ in pairs(configPaths) do
if (string.match(test, "^" .. path)) then
return true
end
end
return false
end
---Extract the package tar and return the extracted path
---@param packagePath string
---@return boolean? ok, string? reason
local function extractPackage(packagePath)
checkArg(1, packagePath, "string")
if (opts["dry-run"]) then return true, nil end
printf("Extracting : %s", packagePath)
return tar.extract(packagePath, "/", false, "DATA/", nil, "DATA/")
end
local function rm(...)
for _, v in pairs({...}) do
if (opts["dry-run"]) then
print("rm " .. v)
else
fs.delete(v)
end
end
end
local rmdir = rm
---Return true if version number a is higher than b. Still return true when equals
---@param a string
---@param b string
---@return boolean
local function compareVersion(a, b)
local aVersion = a:gmatch("%d+")
local bVersion = b:gmatch("%d+")
while true do
local vA = aVersion()
local vB = bVersion()
if vA == nil and vB == nil then return true end
vA = tonumber(vA)
vB = tonumber(vB)
if vA == nil then vA = 0 end
if vB == nil then vB = 0 end
if (vA > vB) then return true elseif (vA < vB) then return false end
end
end
--=============================================================================
local mode = table.remove(args, 1)
if (mode == "info") then
local manifest
if (args[1]:match("%.tar$")) then
args[1] = shell.resolve(args[1])
local r, rr
manifest, r, rr = pm.getManifestFromPackage(args[1])
if (not manifest) then
printf("Error : %s, %s", r, rr or "")
goto exit
end
else
if (pm.isInstalled(args[1])) then
manifest = pm.getManifestFromInstalled(args[1])
else
printf("Could not find package named %q", args[1])
goto exit
end
end
printf("Name : %s (%s)", manifest.name, manifest.package)
printf("Version : %s", manifest.version)
if (manifest.description) then printf("Description : \n%s", manifest.description) end
if (manifest.note) then printf("Note :\n%s", manifest.note) end
if (manifest.authors) then printf("Authors : %s", manifest.authors) end
if (manifest.dependencies) then
print("Dependencies :")
for name, version in pairs(manifest.dependencies) do
printf("%s (%s)", name, version)
end
end
goto exit
elseif (mode == "list-installed") then
for name, manifest in pairs(pm.getInstalled(opts["include-removed"])) do
if (opts["include-removed"]) then
local installed, removed = pm.isInstalled(name)
local label = "installed"
if (not installed and removed) then label = "config remaining" end
printf("%s (%s) [%s]", name, manifest.version, label)
else
printf("%s (%s)", name, manifest.version)
end
end
elseif (mode == "install") then
--get the pacakge file absolute path
args[1] = shell.resolve(args[1])
--get the manifest from the package
local manifest, r, rr = pm.getManifestFromPackage(args[1])
if (not manifest) then
printf("Invalid package")
goto exit
end
--check if the package is already installed
if (pm.isInstalled(manifest.package)) then
local currentManifest = pm.getManifestFromInstalled(manifest.package)
if (manifest.version == "oppm" or currentManifest.version == "oppm") then
--do nothing. We force reinstallation for oppm since there is no version number
elseif (manifest.version == currentManifest.version and not opts["allow-same-version"]) then
printf("Package %q is already installed", manifest.package)
goto exit
elseif (compareVersion(currentManifest.version, manifest.version) and not opts["allow-same-version"]) then --downgrade
printf("Cannot downgrade package %q", manifest.package)
goto exit
end
end
--check the pacakge's dependencies
if (manifest.dependencies) then
for dep, version in pairs(manifest.dependencies) do
if (not pm.isInstalled(dep)) then
printf("Missing dependencie : %s (%s)", dep, version)
goto exit
end
local compType = version:match("^[<>=]") or ">"
version = version:match("%d.*$")
local installedVersion = pm.getManifestFromInstalled(dep).version
if (version) then
if (installedVersion == "oppm") then
printf("Warning : %s is using a oppm version. Cannot determine real installed version", dep)
else
if (compType == "=") then
if (not version == installedVersion) then
printf("Package %s require %s version %s but %s is installed", manifest.package, dep, version, installedVersion)
goto exit
end
elseif (compType == "<") then
if (compareVersion(version, installedVersion)) then
printf("Package %s require %s version %s but %s is installed", manifest.package, dep, version, installedVersion)
goto exit
end
else
if (compareVersion(version, installedVersion) and version ~= installedVersion) then
printf("Package %s require %s version %s but %s is installed", manifest.package, dep, version, installedVersion)
goto exit
end
end
end
end
end
end
--make the values the keys for easier test later
local configFiles = {}
if (manifest.configFiles) then
for _, file in pairs(manifest.configFiles) do
configFiles[file] = true
end
end
local installedFiles = {}
if (pm.isInstalled(manifest.package)) then
local installedFileListFile = assert(io.open(f("/etc/pm/info/%s.files", manifest.package)))
for file in installedFileListFile:lines() do
installedFiles[file] = true
end
installedFileListFile:close()
end
--check that no file not from the package get overwriten
for _, header in pairs(assert(tar.list(args[1]))) do
if (header.name:match("^DATA/") and header.typeflag == "file") then
local destination = header.name:sub(#("DATA/"))
if (not installedFiles[destination] and (fs.exists(destination) and not isConfigPath(configFiles, destination))) then
printf("\27[37mFile already exists %s\27[m", destination)
goto exit
end
end
end
if (pm.isInstalled(manifest.package)) then
local currentManifest = pm.getManifestFromInstalled(manifest.package)
printf("Installing %s (%s) over %s (%s)", manifest.package, manifest.version, currentManifest.package, currentManifest.version)
else
printf("Installing : %s (%s)", manifest.package, manifest.version)
end
--uninstall old version. It's easier than to check wich file need to be deleted
if (pm.isInstalled(manifest.package)) then
--print("Unistalling currently installed version")
shell.run(f("pm uninstall %q --no-dependencies-check", manifest.package))
end
--extract the files in the correct path
local extracted, reason = extractPackage(args[1])
if (not extracted) then
printf("\27[37m%s\27[m", reason or "Unkown error")
--TODO : revert installation
goto exit
end
--save the package info and file list
if (not opts["dry-run"]) then
fs.makeDir("/etc/pm/info/")
local listFile = assert(io.open(f("/etc/pm/info/%s.files", manifest.package), "w"))
for _, header in pairs(assert(tar.list(args[1]))) do
if (header.name:match("^DATA")) then
local name = header.name:match("DATA(.*)")
if (name and name ~= "") then listFile:write(name .. "\n") end
end
end
listFile:close()
assert(tar.extract(args[1], "/tmp/pm/", true, "CONTROL/manifest", nil, "CONTROL/"))
fs.delete(f("/etc/pm/info/%s.manifest", manifest.package))
fs.move("/tmp/pm/manifest", f("/etc/pm/info/%s.manifest", manifest.package))
end
--remove the tmp folder
--cleanup(extracted)
elseif (mode == "uninstall") then
--check if the package exists
if (not pm.isInstalled(args[1])) then
printf("Package %q is not installed", args[1])
goto exit
end
local manifest = pm.getManifestFromInstalled(args[1])
--check dep
if (not opts["no-dependencies-check"]) then
local cantUninstall, dep = pm.checkDependant(args[1])
if (cantUninstall) then
printf("Cannot uninstall %s. One or more package (%s) depend on it.", args[1], dep)
goto exit
end
end
printf("Uninstalling : %s", args[1])
--make the values the keys for easier test later
local configFiles = {}
if (manifest.configFiles) then
for _, file in pairs(manifest.configFiles) do
configFiles[file] = true
end
end
local fileListFile = assert(io.open(f("/etc/pm/info/%s.files", args[1])))
local dirs = {}
--delete the files
for path in fileListFile:lines() do
if (not isConfigPath(configFiles, path) or opts.purge) then
if (not fs.isDir(path)) then
rm(f("%q", path))
else
if (path and path ~= "/" and path ~= "") then
table.insert(dirs, 1, path)
end
end
end
end
fileListFile:close()
--delete empty directory left behind
for _, dir in pairs(dirs) do
if (not RM_BLACKLIST[dir]) then
rmdir(f("%q", dir))
end
end
rm(f("%q", f("/etc/pm/info/%s.files", args[1])))
if (opts.purge) then rm(f("%q", f("/etc/pm/info/%s.manifest", args[1]))) end
else
printHelp()
goto exit
end
::exit::

BIN
pm/example.tar Normal file

Binary file not shown.

View File

@@ -0,0 +1,17 @@
{
manifestVersion = "1.0",
package = "example",
dependencies = {
["neededpackage"] = "=1.0"
},
configFiles = {
"/etc/example.conf"
}, --list configurations files that need to be left on update / uninstallation of the package
name = "Example Package",
version = "1.0.0",
description = "Package description",
authors = "AR2000AR",
note = "Extra bit of information",
hidden = false,
repo = "https://github.com/AR2000AR/openComputers_codes"
}

View File

@@ -0,0 +1 @@
This is a example package

124
pm/lib/libpm.lua Normal file
View File

@@ -0,0 +1,124 @@
package.path = package.path .. ";/usr/lib/?.lua;/usr/lib/?/init.lua"
local expect = require "cc.expect"
local checkArg = expect.expect
local tar = require("tar")
---@class manifest
---@field package string
---@field dependencies table<string,string>
---@field configFiles table<number,string>
---@field name string
---@field version string
---@field description string
---@field authors string
---@field note string
---@field hidden string
---@field repo string
---@field archiveName string
local function cleanup(path)
fs.delete(path)
end
local f = string.format
local function printf(...)
print(f(...))
end
---@class libpm
local pm = {}
---@return manifest
function pm.getManifestFromInstalled(package)
local file = assert(io.open(f("/etc/pm/info/%s.manifest", package)))
---@type manifest
local currentManifest = textutils.unserialize(file:read("a"))
file:close()
return currentManifest
end
---Get the manifest from the package
---@param packagePath string
---@return manifest? manifest
---@return string? reason
---@return string? parserError
function pm.getManifestFromPackage(packagePath)
checkArg(1, packagePath, "string")
local tmpPath = "/tmp/pm/"
if (not fs.exists(packagePath)) then return nil, "Invalid path" end
fs.makeDir("/tmp/pm/")
repeat
tmpPath = "/tmp/pm/" .. math.floor(math.random() * 10E9)
until not fs.isDir(tmpPath)
fs.makeDir(tmpPath)
local ok, reason = tar.extract(packagePath, tmpPath, true, "CONTROL/manifest", nil, "CONTROL/")
local filepath = tmpPath .. "/manifest"
if (not filepath) then
return nil, f("Invalid package format")
end
local manifestFile = assert(io.open(filepath, "r"))
local manifest
manifest, reason = textutils.unserialize(manifestFile:read("a"))
if (not manifest) then
return nil, f("Invalid package manifest. Could not parse"), reason
end
cleanup(tmpPath)
return manifest
end
---get the list of installed packages
---@param includeNonPurged? boolean
---@return table<string,manifest>
function pm.getInstalled(includeNonPurged)
checkArg(1, includeNonPurged, 'boolean', 'nil')
local prefix = "%.files$"
if (includeNonPurged) then prefix = "%.manifest$" end
local installed = {}
if (not fs.exists("/etc/pm/info")) then fs.makeDir("/etc/pm/info") end
for _, file in pairs(fs.list("/etc/pm/info/")) do
local packageName = file:match("(.+)" .. prefix)
if (packageName) then
installed[packageName] = pm.getManifestFromInstalled(packageName)
end
end
return installed
end
---@param package string
---@return boolean installed, boolean notPurged
function pm.isInstalled(package)
local installed = fs.exists(f("/etc/pm/info/%s.files", package))
local notPurged = fs.exists(f("/etc/pm/info/%s.manifest", package))
return installed and notPurged, notPurged
end
---check if a installed package depend of the package
---@return boolean,string?
function pm.checkDependant(package)
printf("Checking for package dependant of %s", package)
for pkg, manifest in pairs(pm.getInstalled(false)) do
---@cast pkg string
---@cast manifest manifest
if (manifest.dependencies and manifest.dependencies[package]) then
return true, pkg
end
end
return false
end
---Get the list of package that depend on the provided package
---@param package string
---@return table
function pm.getDependantOf(package)
local dep = {}
for installedPackageName, installedPackageManifest in pairs(pm.getInstalled(false)) do
if (installedPackageManifest.dependencies and installedPackageManifest.dependencies[package]) then
table.insert(dep, installedPackageName)
end
end
return dep
end
return pm

13
pm/pm.files.json Normal file
View File

@@ -0,0 +1,13 @@
{
"files": [
[
"bin/pm.lua",
"/usr/bin/"
],
[
"lib/libpm.lua",
"/usr/lib/"
]
],
"config": []
}

13
pm/pm.manifest Normal file
View File

@@ -0,0 +1,13 @@
{
["manifestVersion"] = "1.0",
["package"] = "pm",
["version"] = "1.0.0"
["name"] = "pm Package Manager",
["repo"] = "tree/master/pm",
["description"] = "Package manager for OpenOS",
["authors"] = "AR2000AR",
["dependencies"] = {
["libtar"] = "1.0.0",
["usrpath"] = "1.0.0",
}
}

261
pm/tools/oppmPackager.py Executable file
View File

@@ -0,0 +1,261 @@
#!/bin/python3
import contextlib
import re
import sys
import tarfile
from datetime import datetime
from io import BytesIO
from os import environ
from pathlib import Path
from pprint import pprint
from tempfile import TemporaryDirectory
import github
from github.ContentFile import ContentFile
import pause
import requests
from slpp import slpp as lua
from tqdm import tqdm
from tqdm.contrib import DummyTqdmFile
# ==============================================================================
OPPM_REPOS_LIST_URL = "https://raw.githubusercontent.com/OpenPrograms/openprograms.github.io/master/repos.cfg"
OUT_DIR = Path(Path.home(), "tmp/oppmPackages")
OUT_DIR.mkdir(exist_ok=True)
# ==============================================================================
class MalformedPackage(Exception):
"""Error raised when a oppm package's manifest look malformed"""
pass
class UnsuportedFileURI(MalformedPackage):
pass
class UnsuportedDependanceName(MalformedPackage):
pass
@contextlib.contextmanager
def std_out_err_redirect_tqdm():
"""Trick to allow multipes progress bars and text output"""
orig_out_err = sys.stdout, sys.stderr
try:
sys.stdout, sys.stderr = map(DummyTqdmFile, orig_out_err)
yield orig_out_err[0]
# Relay exceptions
except Exception as exc:
raise exc
# Always restore sys.stdout/err if necessary
finally:
sys.stdout, sys.stderr = orig_out_err
# ==============================================================================
def filterReposIfRepo(pair):
"""Remove repos with no "repo" field"""
name, repo = pair
return "repo" in repo
def filterReposIfPrograms(pair):
"""Remove repos with no "programs" field"""
name, repo = pair
return "programs" in repo
def rateLimitPause(githubInstance: github.Github):
"""Wait until github's api rate limit reset"""
if (githubInstance.rate_limiting[0] == 0):
until = datetime.fromtimestamp(githubInstance.rate_limiting_resettime)
print(f"Hit rate limit. Waiting until {until.time()}")
pause.until(githubInstance.rate_limiting_resettime)
def removeMetadata(tarObject: tarfile.TarInfo):
"""Remove mtime from the tarObject"""
tarObject.mtime = 0
return tarObject
def getGithubFileAsString(fileObj: ContentFile) -> str:
"""Get the string content of the github file"""
if (fileObj.content):
return fileObj.content
else:
with requests.get(fileObj.download_url) as response:
return response.text
def makePackage(packageName: str, packageInfo: dict, repoPath: str, outputDirectory: str | Path = './packages/'):
with TemporaryDirectory(prefix="packager.") as tmpDir:
repo = githubAPI.get_repo(repoPath)
Path(tmpDir+"/CONTROL/").mkdir(exist_ok=True)
Path(tmpDir+"/DATA/").mkdir(exist_ok=True)
# build the manifest
manifest = {}
manifest["manifestVersion"] = "1.0"
manifest["package"] = packageName
if "version" in packageInfo:
manifest["version"] = packageInfo["version"]
else:
manifest["version"] = "oppm"
if "name" in packageInfo:
manifest["name"] = packageInfo["name"]
if "repo" in packageInfo:
manifest["repo"] = packageInfo["repo"]
if "description" in packageInfo:
manifest["description"] = packageInfo["description"]
if "note" in packageInfo:
manifest["note"] = packageInfo["note"]
if "authors" in packageInfo:
manifest["authors"] = packageInfo["authors"]
if "dependencies" in packageInfo:
for dep in packageInfo["dependencies"]:
if not "dependencies" in manifest:
manifest["dependencies"] = {}
if (Path(dep).parts[0][-1] == ":"):
# cannot handle protocols like http https
raise UnsuportedDependanceName(
f"Package {packageName} from {repoPath} is malformed")
manifest["dependencies"][dep] = "oppm"
if "repo" in packageInfo:
url = packageInfo["repo"]
manifest["url"] = f"https://github.com/{repoOwnerAndName}/{url}"
files = {}
# copy the required files
if "files" in packageInfo:
for fileInfo, destination in packageInfo["files"].items():
if (type(fileInfo) != str):
continue
if re.match("//", destination):
destination = destination[1:]
else:
destination = "/usr"+destination
if destination[-1] != "/":
destination = destination+"/"
prefix = fileInfo[0]
filePath = Path(*Path(fileInfo).parts[1:])
ref = Path(fileInfo).parts[0]
if (ref[-1] == ":"):
# cannot handle protocols like http https
raise UnsuportedFileURI(
f"Package {packageName} from {repoPath} is malformed : Invalid file")
# check the prefix
if (ref[0] in (":", "?")):
ref = ref[1:]
if (prefix == "?"): # add it to the config file list
if not "configFiles" in manifest:
manifest["configFiles"] = []
configFile = Path(
*Path(fileInfo).parts[2:])
manifest["configFiles"].append("/"+str(configFile))
if (prefix == ":"): # folder
rateLimitPause(githubAPI)
try:
contents = repo.get_contents(str(filePath), ref)
except github.GithubException as e:
raise MalformedPackage(e)
while contents:
file_content = contents.pop(0)
if file_content.type == "dir":
rateLimitPause(githubAPI)
try:
contents.extend(repo.get_contents(file_content.path, ref))
except github.GithubException as e:
raise MalformedPackage(e)
else:
files[file_content.path] = getGithubFileAsString(file_content)
else: # normal file
rateLimitPause(githubAPI)
try:
files[filePath] = getGithubFileAsString(repo.get_contents(str(filePath), ref))
except github.GithubException as e:
raise MalformedPackage(e)
with open(tmpDir+"/CONTROL/manifest", 'w') as file:
file.write(lua.encode(manifest))
version = manifest["version"]
# manifest["archiveName"] = f"{packageName}_({version}).tar"
manifest["archiveName"] = f"{packageName}.tar"
with tarfile.open(Path(outputDirectory, manifest["archiveName"]), 'w') as tar:
tar.add(tmpDir+"/CONTROL", arcname="CONTROL",filter=removeMetadata)
for filePath, data in files.items():
data = data.encode()
tinfo = tarfile.TarInfo(filePath)
tinfo.size = len(data)
tinfo.name = f"DATA/{filePath}"
tinfo.mtime = 0
tar.addfile(tinfo, BytesIO(data))
return manifest
# ==============================================================================
if (environ.get("GITHUB_TOKEN") == None):
print("\033[33mNo github token found\033[m")
else:
print("\033[32mFound github token\033[m")
githubAPI = github.Github(login_or_token=environ.get("GITHUB_TOKEN"))
# ==============================================================================
with std_out_err_redirect_tqdm() as orig_stdout:
reposList = None
with requests.get(OPPM_REPOS_LIST_URL) as response:
if (response.status_code == 200):
reposList = lua.decode(response.text)
reposList = dict(filter(filterReposIfRepo, reposList.items()))
for repoName, info in tqdm(reposList.items(), unit="repo", desc="Fetching repos", file=orig_stdout):
if ("repo" in info):
repoGitRepo = info["repo"]
with requests.get(f"https://raw.githubusercontent.com/{repoGitRepo}/master/programs.cfg") as response:
if (response.status_code == 200):
if (type(lua.decode(response.text)) == dict):
reposList[repoName]["programs"] = lua.decode(
response.text)
reposList = dict(filter(filterReposIfPrograms, reposList.items()))
nbPackages = 0
for repoDisplayName, info in reposList.items():
if ("programs" in info):
nbPackages += len(info["programs"])
rateLimitPause(githubAPI)
reposManifest = {}
with tqdm(total=nbPackages, unit="package", desc="Building package", file=orig_stdout, position=1) as progressBar:
for repoDisplayName, info in reposList.items():
if ("programs" in info):
programs = info["programs"]
repoOwnerAndName = info["repo"]
for package, packageInfo in tqdm(programs.items(), unit="package", desc=repoDisplayName, file=orig_stdout, position=0, leave=False):
progressBar.set_postfix_str(f"{repoDisplayName}:{package}")
with TemporaryDirectory(prefix="oppm-") as tmpDir:
if (not repoOwnerAndName in reposManifest):
reposManifest[repoOwnerAndName] = {}
Path(OUT_DIR, repoOwnerAndName).mkdir(exist_ok=True, parents=True)
try:
reposManifest[repoOwnerAndName][package] = makePackage(package, packageInfo, repoOwnerAndName, Path(OUT_DIR, repoOwnerAndName))
except MalformedPackage as e:
print(
f"\033[31m{repoOwnerAndName} : {package} : {e.__class__.__name__} : {e}\033[m")
finally:
progressBar.update(1)
for repoOwnerAndName, repoManifest in reposManifest.items():
with open(Path(OUT_DIR, repoOwnerAndName, "manifest"), "w") as repoManifestFile:
repoManifestFile.write(lua.encode(repoManifest))

142
pm/tools/pmUtils.py Executable file
View File

@@ -0,0 +1,142 @@
#!/bin/python3
import getopt
import getopt
import os
import pathlib
import re
import shutil
import sys
import tarfile
import tempfile
import json
from glob import glob
from slpp import slpp as lua
RED = '\33[31m'
RESET = '\33[0m'
def printError(msg):
print(f"{RED}{msg}{RESET}", file=sys.stderr)
def printUsage():
print(
f"{sys.argv[0]} [-d|--destination <path>] [-s|--strip-comments]")
print("\t-d|--destination <path> : path to output the pacakges. Default is \"./packages/\"")
print("\t-s|--strip-comments : remove the comments from lua files before adding them to the archive. Line number are not affected")
def removeMetadata(tarObject: tarfile.TarInfo):
tarObject.mtime = 0
return tarObject
def makePackage(projectDir: pathlib.Path, manifestPath: pathlib.Path, filesListJsonPath: pathlib.Path, outputDirectory=pathlib.Path('./packages/')):
global opts
try:
with tempfile.TemporaryDirectory(prefix="packager.") as tmpDir:
# parse the files
manifest = {}
with open(manifestPath, 'r') as manifestFile:
manifest = lua.decode(manifestFile.read())
parsedFilesList = {}
with open(filesListJsonPath, 'r') as filesListFile:
parsedFilesList = json.load(filesListFile)
os.mkdir(tmpDir+"/CONTROL/")
os.mkdir(tmpDir+"/DATA/")
if not projectDir:
projectDir = os.getcwd()
# copy the required files
if "files" in parsedFilesList:
for (fileInfo, destination) in parsedFilesList["files"]:
addFileToPackage(tmpDir, projectDir, fileInfo, destination)
if "config" in parsedFilesList:
for (fileInfo, destination) in parsedFilesList["config"]:
addFileToPackage(tmpDir, projectDir, fileInfo, destination)
if not "configFiles" in manifest:
manifest["configFiles"] = []
manifest["configFiles"].append(str(destination))
# write the package's manifest file
with open(tmpDir+"/CONTROL/manifest", 'w') as file:
file.write(lua.encode(manifest))
if any(item in ['-s', '--strip-comments'] for item, v in opts):
for luaFile in glob(root_dir=tmpDir+"/DATA/", pathname="**/*.lua", recursive=True):
os.system(f'sed -i s/^--.*// {tmpDir+"/DATA/"+luaFile}')
manifest["archiveName"] = f"{manifest['package']}.tar"
with tarfile.open(pathlib.Path(outputDirectory, manifest["archiveName"]), 'w') as tar:
tar.add(tmpDir+"/CONTROL", arcname="CONTROL",
filter=removeMetadata)
tar.add(tmpDir+'/DATA/', arcname="DATA", filter=removeMetadata)
return manifest
except Exception as e:
printError(
f'Failed to buid package in {projectDir} from manifest {manifestPath}')
printError('\t'+str(e))
def addFileToPackage(tmpDir, source, fileInfo, destination):
filePath = pathlib.Path(source, fileInfo)
destination = pathlib.Path(destination)
if (destination.is_absolute()):
destination = destination.relative_to("/")
destination = pathlib.Path(tmpDir, 'DATA', destination)
if (filePath.is_dir()):
shutil.copytree(filePath, destination)
else:
pathlib.Path(destination).mkdir(
parents=True, exist_ok=True)
shutil.copy(filePath, destination)
if __name__ == '__main__':
# parse arguments
# TODO : repo manifest update mode
global opts, args
try:
opts, args = getopt.gnu_getopt(sys.argv[1:], 'hd:s', [
'destination=', 'strip-comments'])
except getopt.GetoptError as e:
printError(e.msg)
exit(1)
# get the output dir absolute path
outputDirectory = pathlib.Path(os.getcwd(), "packages/").absolute()
# check and parse cmd line arguments
for option, value in opts:
if option in ("-h", "--help"):
printUsage()
exit(0)
elif option in ("-d", "--destination"):
outputDirectory = pathlib.Path(value).absolute()
# make sure the output directory exists
# TODO : arg to create the dir if doesnt' exists
pathlib.Path(outputDirectory).mkdir(parents=True, exist_ok=True)
# look for manifest files
repoManifest = {}
for pmManifestPath in glob("**/*.manifest"):
manifestPath = pathlib.Path(pmManifestPath)
projectDir = manifestPath.parent
projectName = manifestPath.stem
filesJson = pathlib.Path(projectDir, f"{projectName}.files.json")
if not filesJson.exists():
continue
packageManifest = makePackage(
projectDir, manifestPath, filesJson, outputDirectory)
if (packageManifest):
repoManifest[packageManifest["package"]] = packageManifest
with open(pathlib.Path(outputDirectory, "manifest"), "w") as repoManifestFile:
repoManifestFile.write(lua.encode(repoManifest))

200
pm/tools/repoConvert.py Executable file
View File

@@ -0,0 +1,200 @@
#!/bin/python3
from slpp import slpp as lua
import pathlib
import json
import re
from getopt import gnu_getopt as getopt
from getopt import GetoptError
import colorama
import sys
from glob import glob
def printe(msg):
print(f"{colorama.Fore.RED}{msg}{colorama.Fore.RESET}", file=sys.stderr)
def printw(msg):
print(f"{colorama.Fore.YELLOW}{msg}{colorama.Fore.RESET}")
def printHelp():
print(f"{sys.argv[0]} [-f|--from <name>] [-t|--to <name>]")
print('\t<name> : one of "oppm" or "pm"')
def manifestOppmToPm(packageName,packageInfo):
manifest = {}
manifest["manifestVersion"] = "1.0"
manifest["package"] = packageName
if "version" in packageInfo:
manifest["version"] = packageInfo["version"]
else:
manifest["version"] = "oppm"
if "name" in packageInfo:
manifest["name"] = packageInfo["name"]
if "repo" in packageInfo:
manifest["repo"] = packageInfo["repo"]
if "description" in packageInfo:
manifest["description"] = packageInfo["description"]
if "note" in packageInfo:
manifest["note"] = packageInfo["note"]
if "authors" in packageInfo:
manifest["authors"] = packageInfo["authors"]
if "dependencies" in packageInfo:
for dep in packageInfo["dependencies"]:
if not "dependencies" in manifest:
manifest["dependencies"] = {}
manifest["dependencies"][dep] = "oppm"
return manifest
def fileListOppmToPm(packageInfo,root=""):
out = {"files":[],"config":[]}
if "files" in packageInfo:
for fileInfo,destination in packageInfo["files"].items():
if re.match("//", destination):
destination = destination[1:]
else:
destination = "/usr"+destination
if destination[-1] != "/":
destination = destination+"/"
prefix = fileInfo[0]
fileInfo=pathlib.Path(*pathlib.Path(fileInfo).parts[1:])
if(fileInfo.is_relative_to(root)):
fileInfo=fileInfo.relative_to(root)
out["config" if prefix=="?" else "files"].append((str(fileInfo),destination))
return out
def doOppmToPm():
with open('programs.cfg') as file:
oppmManifest = lua.decode(file.read())
default=pathlib.Path("manifest/")
for pName,pInfo in oppmManifest.items():
manifest=manifestOppmToPm(pName,pInfo)
sourceDir = pathlib.Path(manifest["repo"]).relative_to("tree/master")
if(sourceDir.exists() and sourceDir.is_dir()):
out=sourceDir
else:
out = default
files=fileListOppmToPm(pInfo,out)
with open(pathlib.Path(out,f"{pName}.manifest"),"w") as f:
f.write(lua.encode(manifest))
with open(pathlib.Path(out,f"{pName}.files.json"),"w") as f:
json.dump(files,f,indent="\t")
def recurseConfig(folderPath:pathlib.Path,fileDst,origin=None):
out = dict()
origin = origin or folderPath
dirFiles = glob(f"{str(folderPath)}/**",recursive=True)
for configPath in dirFiles:
configPath=pathlib.Path(configPath)
if(configPath.is_dir()):
out |= recurseConfig(configPath,fileDst,origin)
else:
dst = pathlib.Path(fileDst,folderPath)
if(fileDst.is_relative_to("/usr")):
dst = f"/{dst.relative_to('/usr')}"
else:
dst = f"/{dst}"
out[f"?master{str(configPath)}"] = dst
return out
def doPmToOppm():
manifestFiles = glob("*/*.manifest")
manifestFiles = [pathlib.Path(path) for path in manifestFiles]
oppmData = dict()
for path in manifestFiles:
manifest = None
with path.open() as file:
manifest = lua.decode(file.read())
pName=manifest['package']
fileListPath = pathlib.Path(path.parent,f"{pName}.files.json")
if(not fileListPath.is_file()):
printe(f"Could not find file list for {pName} in {str(path.parent)}")
continue
files=None
with fileListPath.open() as file:
files=json.load(file)
oppmData[pName] = manifest
oppmData[pName].pop('package')
oppmData[pName].pop('manifestVersion')
oppmData[pName]["files"] = dict()
for filePaths in files["files"]:
filePath,fileDst = filePaths[0],filePaths[1]
filePath = pathlib.Path(path.parent,filePath)
fileDst = pathlib.Path(fileDst)
if(not filePath.exists()):
printw(f"Error in the file list for {pName} : {str(filePath)} does not exists")
if(fileDst.is_relative_to("/usr")):
fileDst = f"/{fileDst.relative_to('/usr')}"
else:
fileDst = f"/{fileDst}"
if(filePath.is_dir()):
oppmData[pName]["files"][f":master/{str(filePath)}"] = fileDst
else:
oppmData[pName]["files"][f"master/{str(filePath)}"] = fileDst
for filePaths in files["config"]:
filePath,fileDst = filePaths[0],filePaths[1]
filePath = pathlib.Path(path.parent,filePath)
fileDst = pathlib.Path(fileDst)
if(not filePath.exists()):
printw(f"Error in the file list for {pName} : {str(filePath)} does not exists")
if(fileDst.is_relative_to("/usr")):
fileDst = f"/{fileDst.relative_to('/usr')}"
else:
fileDst = f"/{fileDst}"
if(filePath.is_dir()):
oppmData[pName]["files"] |= recurseConfig(filePath,fileDst)
else:
oppmData[pName]["files"][f"?master/{str(filePath)}"] = fileDst
if("dependencies" in oppmData[pName]):
for k in oppmData[pName]["dependencies"].keys():
oppmData[pName]["dependencies"][k]="/"
fields = ("version","name","repo","description","authors","dependencies","files","note")
data = lua.encode(oppmData)
for field in fields:
data=re.sub(re.escape(f'["{field}"]'),field,data)
with open("programs.cfg","w") as file:
file.write(data)
if __name__ == '__main__':
global opts,args
try:
r=re.compile("-*(.*)")
opts = dict()
optsList, args = getopt(sys.argv[1:], 'hf:t:', ['from=', 'to=','strip-comments'])
for k,v in optsList:
k=r.findall(k)[0]
opts[k]=v
except GetoptError as e:
printe(e.msg)
exit(1)
if("h" in opts):
printHelp()
exit()
if(not ("from" in opts or "f" in opts)):
printHelp()
exit(1)
if(not ("to" in opts or "t" in opts)):
printHelp()
exit(1)
opts["from"] = opts["from"] if "from" in opts else opts["f"]
opts["to"] = opts["to"] if "to" in opts else opts["t"]
if(opts["from"] == "oppm"):
if(opts["to"] == "pm"):
doOppmToPm()
if(opts["from"] == "pm"):
if(opts["to"] == "oppm"):
doPmToOppm()

150
pm/tools/repoPackager.py Executable file
View File

@@ -0,0 +1,150 @@
#!/bin/python3
import getopt
import os
import pathlib
import re
import shutil
import sys
import tarfile
import tempfile
from glob import glob
from slpp import slpp as lua
RED = '\33[31m'
RESET = '\33[0m'
def printError(msg):
print(f"{RED}{msg}{RESET}", file=sys.stderr)
def printUsage():
print(f"{sys.argv[0]} [-p|--programs <file>] [-d|--destination <path>] [-s|--strip-comments] [*packageName]")
print("\t-p|--programs <file> : oppm's programs.cfg file. Default is \"./programs.cfg\"")
print("\t-d|--destination <path> : path to output the pacakges. Default is \"./packages/\"")
print("\t-s|--strip-comments : remove the comments from lua files before adding them to the archive. Line number are not affected")
def removeMetadata(tarObject):
tarObject.mtime = 0
return tarObject
def makePackage(packageInfo, source=None, outputDirectory='./packages/'):
global opts
with tempfile.TemporaryDirectory(prefix="packager.") as tmpDir:
os.mkdir(tmpDir+"/CONTROL/")
os.mkdir(tmpDir+"/DATA/")
if not source:
source = os.getcwd()
# build the manifest
manifest = {}
manifest["manifestVersion"] = "1.0"
manifest["package"] = packageName
if "version" in packageInfo:
manifest["version"] = packageInfo["version"]
else:
manifest["version"] = "oppm"
if "name" in packageInfo:
manifest["name"] = packageInfo["name"]
if "repo" in packageInfo:
manifest["repo"] = packageInfo["repo"]
if "description" in packageInfo:
manifest["description"] = packageInfo["description"]
if "note" in packageInfo:
manifest["note"] = packageInfo["note"]
if "authors" in packageInfo:
manifest["authors"] = packageInfo["authors"]
if "dependencies" in packageInfo:
for dep in packageInfo["dependencies"]:
if not "dependencies" in manifest:
manifest["dependencies"] = {}
manifest["dependencies"][dep] = "oppm"
# copy the required files
if "files" in packageInfo:
for fileInfo, destination in packageInfo["files"].items():
if re.match("//", destination):
destination = destination[1:]
else:
destination = "/usr"+destination
if destination[-1] != "/":
destination = destination+"/"
prefix = fileInfo[0]
filePath = pathlib.Path(*pathlib.Path(fileInfo).parts[1:])
filePath = pathlib.Path(source, filePath)
if (prefix == "?"): # add it to the config file list
if not "configFiles" in manifest:
manifest["configFiles"] = []
configFile = pathlib.Path(*pathlib.Path(fileInfo).parts[2:])
manifest["configFiles"].append("/"+str(configFile))
destination = tmpDir+"/DATA"+destination
if (prefix == ":"):
shutil.copytree(filePath, destination)
else:
pathlib.Path(destination).mkdir(
parents=True, exist_ok=True)
shutil.copy(filePath, destination)
#write the package's manifest file
with open(tmpDir+"/CONTROL/manifest", 'w') as file:
file.write(lua.encode(manifest))
if any(item in ['-s','--strip-comments'] for item,v in opts):
for luaFile in glob(root_dir=tmpDir+"/DATA/",pathname="**/*.lua",recursive=True):
os.system(f'sed -i s/^--.*// {tmpDir+"/DATA/"+luaFile}')
version = manifest["version"]
# manifest["archiveName"] = f"{packageName}_({version}).tar"
manifest["archiveName"] = f"{packageName}.tar"
with tarfile.open(pathlib.Path(outputDirectory, manifest["archiveName"]), 'w') as tar:
tar.add(tmpDir+"/CONTROL", arcname="CONTROL",filter=removeMetadata)
tar.add(tmpDir+'/DATA/', arcname="DATA", filter=removeMetadata)
return manifest
if __name__ == '__main__':
global opts,args
try:
opts, args = getopt.gnu_getopt(sys.argv[1:], 'hp:d:s', ['programs=', 'destination=','strip-comments'])
except getopt.GetoptError as e:
printError(e.msg)
exit(1)
outputDirectory = pathlib.Path(os.getcwd(), "packages/").absolute()
packageInfoFile = pathlib.Path(os.getcwd(), "programs.cfg").absolute()
for option, value in opts:
if option in ("-h", "--help"):
printUsage()
exit(0)
elif option in ("-p", "--programs"):
packageInfoFile = pathlib.Path(value).absolute()
elif option in ("-d", "--destination"):
outputDirectory = pathlib.Path(value).absolute()
pathlib.Path(outputDirectory).mkdir(parents=True, exist_ok=True)
if not os.path.isfile(packageInfoFile):
printError(f"{packageInfoFile} not found")
exit(1)
raw = None
with open(packageInfoFile, 'r') as file:
raw = file.read()
data = lua.decode(raw)
repoManifest = {}
for packageName, packageInfo in data.items():
if len(args) == 0 or packageName in args:
packageManifest = makePackage(packageInfo, outputDirectory=outputDirectory)
repoManifest[packageManifest["package"]] = packageManifest
with open(pathlib.Path(outputDirectory, "manifest"), "w") as repoManifestFile:
repoManifestFile.write(lua.encode(repoManifest))

4
pm/tools/requirement.txt Normal file
View File

@@ -0,0 +1,4 @@
pause>=0.3
SLPP>=1.2.3
PyGithub>=1.58.1
tqdm>=4.65.0

55
pm_get/README.md Normal file
View File

@@ -0,0 +1,55 @@
# pm_get
Download and install pm packages from a repository on internet.
## Installation
- run `oppm install pm`
or
- Create a installation floppy. Installation and script can be found [here](../pm_installer/).
## Usage :
- `pm-get install <package>` : install the package
- `pm-get uninstall <package> [--autoremove] [--purge]` : uninstall the package. Optionally remove configurations files and/or old dependance no longer needed
- `pm-get autoremove` : remove no longer required dependance.
- `pm-get update` : update the local package cache
- `pm-get upgrade` : apply all upgrades possible"
- `pm-get sources list` : list configured source repository
- `pm-get sources add <url>` : add a source repository url to `/etc/pm/sources.list.d/custom.list`
- `pm-get list` : list available packages
- `pm-get info <package>` : get the infos about the package
## Files :
- `/etc/pm/sources.list` : the main repository list
- `/etc/pm/sources.list.d/*.list` : additional repository lists
- `/etc/pm/autoInstalled` : list dependance installed automatically
## Repository :
A repository is a collection of packages and a manifest file for the repository, accessible via http or https.
### File structure :
The repository owner if free to structure it however they want. The only restriction is that the manifest file `manifest` is placed in the repository's root folder.
### Manifest file :
The manifest file is a aggregation of the packages's manifest files in a table. Inside each package's manifest is added a extra field that point to the package file. See [manifest](../packages/manifest) for a example.\
Example :
```
{
["pm_get"] = {
["manifestVersion"] = "1.0",
["package"] = "pm_get",
["version"] = "1.2.0",
["name"] = "pm get",
["repo"] = "tree/master/pm",
["description"] = "Download and install package for pm",
["authors"] = "AR2000AR",
["dependencies"] = {
["pm"] = "oppm"
},
["configFiles"] = {
"/etc/pm/sources.list"
},
["archiveName"] = "pm_get.tar"
}
}
```

567
pm_get/bin/pm-get.lua Normal file
View File

@@ -0,0 +1,567 @@
package.path = package.path .. ";/usr/lib/?.lua;/usr/lib/?/init.lua"
local pm = require("libpm")
local parse = require "arutils.parse"
--=============================================================================
local DOWNLOAD_DIR = "/home/.pm-get/" --can't use /tmp as some archive can be bigger than the tmpfs
local CONFIG_DIR = "/etc/pm/" --share the config directory with the rest of the package manager
local SOURCE_FILE = CONFIG_DIR .. "sources.list"
local SOURCE_DIR = SOURCE_FILE .. ".d/"
local REPO_MANIFEST_CACHE = CONFIG_DIR .. "manifests.cache"
local AUTO_INSTALLED = CONFIG_DIR .. "autoInstalled"
--=============================================================================
---@type table<string>,table<progOpts>
local args, opts = parse(...)
local mode = table.remove(args, 1)
--=============================================================================
local reposRuntimeCache
--=============================================================================
---@alias progOpts
---| 'autoremove'
---| 'purge'
---| 'allow-same-version'
--=============================================================================
local f = string.format
local function printf(...)
print(f(...))
end
local function printferr(...)
io.stderr:write(f("%s\n", f(...)))
end
---Compare a with b
---@return -1|0|1 cmp `-1 a<b, 0 a=b, 1 a>b`
local function compareVersion(a, b)
local aVersion = a:gmatch("%d+")
local bVersion = b:gmatch("%d+")
while true do
local vA = aVersion()
local vB = bVersion()
if vA == nil and vB == nil then return 0 end
vA = tonumber(vA)
vB = tonumber(vB)
if vA == nil then vA = 0 end
if vB == nil then vB = 0 end
if (vA > vB) then return 1 elseif (vA < vB) then return -1 end
end
end
local function confirm(prompt, default)
if (opts["y"]) then return true end
while true do
local y = default and "Y" or "y"
local n = default and "n" or "N"
term.write(f("%s [%s/%s] ", prompt, y, n))
local op = read()
if op == false or op == nil then return false end
if op == "" and default then return true end
if op == "" and not default then return false end
if op == "y" or op == "Y" then return true end
if op == "n" or op == "N" then return false end
end
end
---Return the sources list
---@param raw? boolean
---@return table
local function getSources(raw)
local sources = {}
local file
if (fs.exists(SOURCE_FILE) and not fs.isDir(SOURCE_FILE)) then
file = assert(io.open(SOURCE_FILE))
for url in file:lines() do
if (not url:sub(1, 1) ~= "#") then
table.insert(sources, url)
end
end
file:close()
end
if (fs.isDir(SOURCE_DIR)) then
for _, fileName in pairs(fs.list(SOURCE_DIR)) do
if (fileName:match("%.list$")) then
file = assert(io.open(SOURCE_DIR .. fileName))
for url in file:lines() do
if (not url:sub(1, 1) ~= "#") then
table.insert(sources, 1, url)
end
end
file:close()
end
end
end
if (not raw) then
for i, url in pairs(sources) do
if (url:match("^https://github.com/")) then
sources[i] = url:gsub("https://github.com/", "https://raw.githubusercontent.com/"):gsub("/tree/", "/"):gsub("/blob/", "/")
end
end
end
return sources
end
---@return table<string,table<string,manifest>>
local function getCachedPackageList()
if (not reposRuntimeCache) then
if (not fs.exists(REPO_MANIFEST_CACHE)) then
printferr("No data. Run `pm-get upddate` or add repositorys")
return {}
end
local cache = assert(io.open(REPO_MANIFEST_CACHE))
reposRuntimeCache = textutils.unserialize(cache:read("a"))
cache:close()
end
return reposRuntimeCache
end
---Get a packet manifest from the cache
---@param name string
---@param targetRepo? string
---@return manifest? manifest, string? originRepo
local function getCachedPackageManifest(name, targetRepo)
for repoName, repo in pairs(getCachedPackageList()) do
if (not targetRepo or repoName == targetRepo) then
if (repo[name]) then
return repo[name], repoName
end
end
end
return nil, nil
end
---@param package string
---@param dep? table<number,table> Current dependance list.
---@param cleanup? boolean cleanup the dep list. Default `true`
---@return table<number,table>? dep ordered list of dependances
---@return string? error
local function buildDepList(package, dep, cleanup)
if not dep then dep = {} end
assert(dep)
if cleanup == nil then cleanup = true end
local packageManifest = getCachedPackageManifest(package)
if (not packageManifest) then return nil, string.format("Package %q cannot be found", package) end
if (packageManifest.dependencies) then
for dependance, requiredVersion in pairs(packageManifest.dependencies) do
table.insert(dep, {dependance, requiredVersion})
buildDepList(dependance, dep, false)
end
end
if (cleanup) then
local hash = {}
local rm = {}
for k, v in ipairs(dep) do
if not hash[v[1]] then
table.insert(hash, v[1])
else
table.insert(rm, 1, k)
end
end
for _, v in ipairs(rm) do
table.remove(dep, v)
end
end
return dep
end
---Download from url and return it
---@param url any
---@return string? data, string? reason
local function wget(url)
local request, reason = http.get(url)
if (not request) then
return nil, reason
end
local data = request.readAll()
request.close()
return data, nil
end
local function isAuto(package)
local auto = false
if (not fs.exists(AUTO_INSTALLED)) then return false end
for line in io.lines(AUTO_INSTALLED) do
if (line == package) then
auto = true
end
end
return auto
end
local function markManual(package)
local auto = {}
if (not fs.exists(AUTO_INSTALLED)) then return end
for line in io.lines(AUTO_INSTALLED) do
if (line ~= package) then table.insert(auto, line) end
end
local autoFile = assert(io.open(AUTO_INSTALLED, 'w'))
for _, pk in pairs(auto) do
autoFile:write(pk .. "\n")
end
autoFile:close()
end
local function needUpgrade(pkg)
local remoteManifest = getCachedPackageManifest(pkg)
if not remoteManifest then return false end
local localManifest = pm.getManifestFromInstalled(pkg)
if not localManifest then return false end
if remoteManifest.version == "oppm" or localManifest == "oppm" then return true end
if compareVersion(remoteManifest.version, localManifest.version) > 0 then return true end
if compareVersion(remoteManifest.version, localManifest.version) == 0 and opts["allow-same-version"] then return true end
return false
end
--=============================================================================
---Install a package
---@param package string
---@param markAuto boolean
---@return any? code
---@return string? errorReason
local function doInstall(package, markAuto)
local targetManifest, repoName = getCachedPackageManifest(package)
if (not targetManifest) then
return nil, string.format("Cannot find package : %s", package)
end
--install the package
fs.makeDir(DOWNLOAD_DIR)
--download
printf("Downloading : %s", package)
local data, reason = wget(f("%s/%s", repoName, targetManifest.archiveName))
if (not data) then
printferr("Failed to download %s", package)
printferr(reason)
return -1, reason
end
--write downloaded archive in download dir
io.open(f("%s/%s", DOWNLOAD_DIR, targetManifest.archiveName), "w"):write(data):close()
--build opts for pm
local pmOptions = ""
if (opts["allow-same-version"]) then
pmOptions = "--allow-same-version"
end
--run pm
local _, code = shell.run(f("pm install %s %s", pmOptions, f("%s/%s", DOWNLOAD_DIR, targetManifest.archiveName)))
--cleanup
fs.delete(f("%s/%s", DOWNLOAD_DIR, targetManifest.archiveName))
--mark the pacakge as auto if asked for
if (markAuto) then
io.open(AUTO_INSTALLED, "a"):write(targetManifest.package .. "\n"):close()
end
return code
end
---@param packages table|string
---@param markAuto? boolean
---@param buildDepTree? boolean
local function install(packages, markAuto, buildDepTree)
if (buildDepTree == nil) then buildDepTree = true end
if (type(packages) == "string") then packages = {packages} end
---get the dependance list
local depList = {}
for _, package in pairs(packages) do
local targetManifest, repoName = getCachedPackageManifest(package)
--check that the packet exists
if (not targetManifest) then
printferr("Package %s not found", package)
error("", 0) --TODO : exit() fix that. dirty
end
if (buildDepTree) then
buildDepList(package, depList)
end
end
local toInstall = {}
local toUpgrade = {}
for _, dep in pairs(depList) do
if (not pm.isInstalled(dep[1])) then
local exists = getCachedPackageManifest(dep[1])
if (not exists) then
printferr("Cannot fuffil dependency : %s (%s)", dep[1], dep[2])
return -1
end
table.insert(toInstall, dep[1])
else
--TODO : check version
end
end
local display = {}
for _, v in pairs(toInstall) do table.insert(display, v) end
for _, v in pairs(packages) do table.insert(display, v) end
if (#display > 0) then printf("Will be installed :\n %s", table.concat(display, ', ')) end
if (#toUpgrade > 0) then printf("Will be updated :\n %s", table.concat(toUpgrade, ', ')) end
printf("%d upgraded, %d newly installed", #toUpgrade, #display)
if (#display == 0 and #toUpgrade == 0) then return end
if not confirm("Proceed") then return end
if (not opts['dry-run']) then
for _, dep in pairs(toUpgrade) do
--TODO : upgrade
printf("Updating dependency : %s", dep)
error("UNIMPLEMENTED")
end
for _, dep in pairs(toInstall) do
printf("Installing dependency : %s", dep)
local code, errorReason = doInstall(dep, true)
if (errorReason) then
printferr("Failed to install %q. Abording", dep)
return -1
end
end
for _, package in pairs(packages) do
printf("Installing : %s", package)
local code, errorReason = doInstall(package, false)
if (errorReason) then
printferr("Failed to install %q. Abording", package)
return -1
end
end
end
return 0
end
local function uninstall(packages)
local toUninstall = {}
for _, package in pairs(packages) do
table.insert(toUninstall, package)
end
printf("Will be uninstalled :\n %s", table.concat(toUninstall, ', '))
if not confirm("Proceed") then return end
--uninstallation
local options = ""
if (opts.purge) then options = options .. "--purge" end
shell.run(f("pm uninstall %s %s", options, args[1]))
for _, pkg in pairs(toUninstall) do
shell.run(f("pm uninstall %s %s", options, pkg))
markManual(pkg)
end
if (opts["autoremove"]) then
shell.run(f("pm-get autoremove -y %s", options))
end
end
local function update()
local repos = getSources()
local manifests = {}
for _, repoURL in pairs(repos) do
local data, reason = wget(repoURL .. "/manifest")
if (not data) then
printferr("Could not get manifest from %s\ns", repoURL, reason)
else
printf("Found repository : %s", repoURL)
local pcalled
pcalled, data = pcall(textutils.unserialize, data)
if (pcalled == false) then
printferr("Invalid manifest for %s", repoURL)
else
manifests[repoURL] = data
end
end
end
io.open(REPO_MANIFEST_CACHE, "w"):write(textutils.serialize(manifests)):close()
--check if packages need updating
local canBeUpgraded = 0
local remotePackages = getCachedPackageList()
local installedPackages = pm.getInstalled()
for pkgName in pairs(installedPackages) do
if (needUpgrade(pkgName)) then
canBeUpgraded = canBeUpgraded + 1
end
end
printf("%s package(s) can be upgraded", canBeUpgraded)
end
local function printHelp()
print("pm-get [opts] <mode> [args]")
print("mode :")
print("\tinstall <packageFile>")
print("\tuninstall <packageName>")
print("\tpruge <packageName>")
print("\tautoremove")
print("\tupdate : update the package cache")
print("\tupgrade : apply all upgrades possible")
print("\tinfo <packageName>|<packageFile>")
print("\tlist")
print("\tsources list|add [new source url]")
print("opts :")
print("\t--autoremove : also remove dependencies non longer required")
print("\t--purge : purge removed packages")
print("\t--allow-same-version : allow the same package version to be installed over the currently installed one")
print("\t--installed : only list installed packages")
end
--=============================================================================
if (not http and (mode == "update" or mode == "install")) then
printferr("Http is not enabled on this world")
goto exit
end
--Remove uninstalled files from autoInstalled file
if (fs.exists(AUTO_INSTALLED)) then
do
local tokeep = {}
for pkg in io.lines(AUTO_INSTALLED) do
if (pm.isInstalled(pkg)) then table.insert(tokeep, pkg) end
end
local file = assert(io.open(AUTO_INSTALLED, 'w'))
for _, pkg in pairs(tokeep) do file:write(f("%s\n", pkg)) end
file:close()
end
end
if (mode == "purge") then
mode = "uninstall"
opts["purge"] = true
end
if (mode == "update") then
print("Updating repository cache")
update()
elseif (mode == "list") then
args[1] = args[1] or ".*"
for repoName, repo in pairs(getCachedPackageList()) do
local sortedTable = {}
for package, _ in pairs(repo) do
table.insert(sortedTable, package)
end
table.sort(sortedTable, function(a, b) return string.lower(a) < string.lower(b) end)
for i, package in pairs(sortedTable) do
local manifest = repo[package]
if (package:match("^" .. args[1])) then
local installed, notpurged = pm.isInstalled(package)
if (not opts['installed'] or installed) then
local lb = ""
if (installed) then
lb = '[installed]'
if (isAuto(package)) then
lb = '[installed, auto]'
end
elseif (notpurged) then
lb = '[config]'
end
printf("%s (%s) %s", package, manifest.version, lb)
end
end
end
end
elseif (mode == "info") then
local manifest, repoName = getCachedPackageManifest(args[1])
if (not manifest) then
printferr("Package %s not found", args[1])
goto exit
end
printf("Name : %s (%s)", manifest.name, manifest.package)
printf("Version : %s", manifest.version)
if (manifest.description) then printf("Description : \n%s", manifest.description) end
if (manifest.note) then printf("Note :\n%s", manifest.note) end
if (manifest.authors) then printf("Authors : %s", manifest.authors) end
if (manifest.dependencies) then
print("Dependencies :")
for name, version in pairs(manifest.dependencies) do
printf("%s (%s)", name, version)
end
end
printf("Repo : %s", repoName)
goto exit
elseif (mode == "install") then
install(args)
for _, p in pairs(args) do
markManual(p)
end
goto exit
elseif (mode == "uninstall") then
uninstall(args)
elseif (mode == "autoremove") then
local oldDep = {}
if (fs.exists(AUTO_INSTALLED)) then
for package in io.lines(AUTO_INSTALLED) do
if (#pm.getDependantOf(package) == 0) then
table.insert(oldDep, package)
end
end
end
printf("Will be uninstalled :\n %s", table.concat(oldDep, ', '))
if not confirm("Proceed") then return end
--uninstallation
local options = ""
if (opts.purge) then options = options .. " --purge" end
for _, dep in pairs(oldDep) do
shell.run(f("pm uninstall %s %s", options, dep))
end
markManual(args[1])
elseif (mode == "upgrade") then
local installed = pm.getInstalled(false)
local toUpgrade = {}
if (args[1]) then
if (pm.isInstalled(args[1])) then
table.insert(toUpgrade, args[1])
local manifest = assert(pm.getManifestFromInstalled(args[1]))
if (manifest.dependencies) then
for dep, ver in pairs(manifest.dependencies) do
if (needUpgrade(dep)) then
table.insert(toUpgrade, dep)
end
end
end
else
--TODO : pkg not installed
end
else
for pkg, manifest in pairs(installed) do
if (manifest.version == "oppm") then
printf("Found oppm version for %q.", pkg)
table.insert(toUpgrade, pkg)
else
if (needUpgrade(pkg)) then
table.insert(toUpgrade, pkg)
end
end
end
end
install(toUpgrade, false)
elseif (mode == "sources") then
if (args[1] == "list") then
local sources = getSources()
for _, s in pairs(sources) do
print(s)
end
elseif (args[1] == "add" and args[2]) then
local sources = getSources(true)
local exists = false
for _, url in pairs(sources) do
if (url == args[2]) then
exists = true
printf("Found %q in the source list. It will not be added again.", args[2])
break
end
end
if (not exists) then
fs.makeDir(SOURCE_DIR)
assert(io.open(SOURCE_DIR .. "/custom.list", "a")):write(args[2] .. "\n"):close()
printf("Added %q to the source list", args[2])
end
else
print("pm-get sources add|list")
end
else
printHelp()
end
::exit::

View File

@@ -0,0 +1 @@
https://gitea.ar2000.me/AR2000/CC_monorepo/raw/branch/main/packages/

14
pm_get/pm_get.files.json Normal file
View File

@@ -0,0 +1,14 @@
{
"files": [
[
"bin/pm-get.lua",
"/usr/bin/"
]
],
"config": [
[
"etc/pm/sources.list",
"/etc/pm/"
]
]
}

13
pm_get/pm_get.manifest Normal file
View File

@@ -0,0 +1,13 @@
{
["manifestVersion"] = "1.0",
["package"] = "pm_get",
["version"] = "1.0.0"
["name"] = "pm get",
["repo"] = "tree/master/pm_get",
["description"] = "Download and install package for pm",
["authors"] = "AR2000AR",
["dependencies"] = {
["pm"] = "1.0.0",
["usrpath"] = "1.0.0",
}
}

64
pm_installer/install.lua Normal file
View File

@@ -0,0 +1,64 @@
local baseURL = "http://127.0.0.1:8888"
--wget run http://127.0.0.1:8888/pm_installer/install.lua
local function concatPath(...) return table.concat({...}, '/') end
---@type function
local mkdir = fs.makeDir
local function wget(src, dst)
local r, rr = http.get(src)
if (not r) then
error(rr, 0)
end
io.open(dst, 'w'):write(r.readAll()):close()
r.close()
end
local path = "/tmp/" .. math.floor(math.random() * 10E9) .. "/"
mkdir("/tmp")
mkdir(path)
mkdir(concatPath(path, 'bin'))
mkdir(concatPath(path, 'lib'))
mkdir(concatPath(path, 'lib/arutils'))
local files = {
['pm_get/bin/pm-get.lua'] = 'bin/pm-get.lua',
['pm/bin/pm.lua'] = 'bin/pm.lua',
['pm/lib/libpm.lua'] = 'lib/libpm.lua',
['tar/lib/tar.lua'] = 'lib/tar.lua',
['arutils/lib/arutils/init.lua'] = 'lib/arutils/init.lua',
['arutils/lib/arutils/parse.lua'] = 'lib/arutils/parse.lua',
}
local packages = {'pm.tar', 'pm_get.tar', 'libtar.tar', 'arutils.tar', 'usrpath.tar',}
for src, dst in pairs(files) do
print(string.format("Downloading %q as %q", path, dst))
wget(concatPath(baseURL, src), concatPath(path, dst))
end
for _, pkg in pairs(packages) do
print(string.format("Downloading %q as %q", path, pkg))
wget(concatPath(baseURL, "packages", pkg), concatPath(path, pkg))
end
sleep(3)
print("Installing ...")
--init path
local oldPackagePath = package.path
package.path = table.concat({oldPackagePath, string.format("%s/lib/?.lua;%s/lib/?/init.lua", path, path)}, ";")
local pm = loadfile(path .. "bin/pm.lua", "bt", _ENV)
--install
if (pm) then
pm("install", "--allow-same-version", path .. "libtar.tar")
pm("install", "--allow-same-version", path .. "arutils.tar")
pm("install", "--allow-same-version", path .. "usrpath.tar")
pm("install", "--allow-same-version", path .. "pm.tar")
pm("install", "--allow-same-version", path .. "pm_get.tar")
package.path = oldPackagePath
shell.setPath(shell.path() .. ":/usr/bin")
shell.run("pm-get update")
shell.run("pm-get upgrade")
end
fs.delete(path)

361
tar/lib/tar.lua Normal file
View File

@@ -0,0 +1,361 @@
local expect = require "cc.expect"
local checkArg = expect.expect
---@class tarHeader
---@field name any
---@field mode any
---@field uid any
---@field gid any
---@field size any
---@field mtime any
---@field chksum any
---@field typeflag any
---@field linkname any
---@field magic any
---@field version any
---@field uname any
---@field gname any
---@field devmajor any
---@field devminor any
---@field prefix any
---https://github.com/luarocks/luarocks/blob/master/src/luarocks/core/dir.lua
local function unquote(c)
local first, last = c:sub(1, 1), c:sub(-1)
if (first == '"' and last == '"') or
(first == "'" and last == "'") then
return c:sub(2, -2)
end
return c
end
local dir = {}
function dir.path(...)
local t = {...}
while t[1] == "" do
table.remove(t, 1)
end
for i, c in ipairs(t) do
t[i] = unquote(c)
end
return (table.concat(t, "/"):gsub("([^:])/+", "%1/"):gsub("^/+", "/"):gsub("/*$", ""))
end
---https://github.com/luarocks/luarocks/blob/master/src/luarocks/tools/tar.lua#L56
--- A pure-Lua implementation of untar (unpacking .tar archives)
local tar = {}
local blocksize = 512
local function get_typeflag(flag)
if flag == "0" or flag == "\0" then
return "file"
elseif flag == "1" then
return "link"
elseif flag == "2" then
return "symlink" -- "reserved" in POSIX, "symlink" in GNU
elseif flag == "3" then
return "character"
elseif flag == "4" then
return "block"
elseif flag == "5" then
return "directory"
elseif flag == "6" then
return "fifo"
elseif flag == "7" then
return "contiguous" -- "reserved" in POSIX, "contiguous" in GNU
elseif flag == "x" then
return "next file"
elseif flag == "g" then
return "global extended header"
elseif flag == "L" then
return "long name"
elseif flag == "K" then
return "long link name"
end
return "unknown"
end
local function octal_to_number(octal)
local exp = 0
local number = 0
octal = octal:gsub("%s", "")
for i = #octal, 1, -1 do
local digit = tonumber(octal:sub(i, i))
if not digit then
break
end
number = number + (digit * 8 ^ exp)
exp = exp + 1
end
return number
end
local function checksum_header(block)
local sum = 256
for i = 1, 148 do
local b = block:byte(i) or 0
sum = sum + b
end
for i = 157, 500 do
local b = block:byte(i) or 0
sum = sum + b
end
return sum
end
local function nullterm(s)
return s:match("^[^%z]*")
end
local function read_header_block(block)
local header = {}
header.name = nullterm(block:sub(1, 100))
header.mode = nullterm(block:sub(101, 108)):gsub(" ", "")
header.uid = octal_to_number(nullterm(block:sub(109, 116)))
header.gid = octal_to_number(nullterm(block:sub(117, 124)))
header.size = octal_to_number(nullterm(block:sub(125, 136)))
header.mtime = octal_to_number(nullterm(block:sub(137, 148)))
header.chksum = octal_to_number(nullterm(block:sub(149, 156)))
header.typeflag = get_typeflag(block:sub(157, 157))
header.linkname = nullterm(block:sub(158, 257))
header.magic = block:sub(258, 263)
header.version = block:sub(264, 265)
header.uname = nullterm(block:sub(266, 297))
header.gname = nullterm(block:sub(298, 329))
header.devmajor = octal_to_number(nullterm(block:sub(330, 337)))
header.devminor = octal_to_number(nullterm(block:sub(338, 345)))
header.prefix = block:sub(346, 500)
if checksum_header(block) ~= header.chksum then
return false, "Failed header checksum"
end
return header
end
function tar.untar(filename, destdir)
checkArg(1, filename, "string")
checkArg(2, destdir, "string")
local tar_handle = io.open(filename, "rb")
if not tar_handle then return nil, "Error opening file " .. filename end
local long_name, long_link_name
local ok, err
local make_dir = fs.makeDir
while true do
local block
repeat
block = tar_handle:read(blocksize)
until (not block) or checksum_header(block) > 256
if not block then break end
if #block < blocksize then
ok, err = nil, "Invalid block size -- corrupted file?"
break
end
local header
header, err = read_header_block(block)
if not header then
ok = false
break
end
local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1, header.size)
if header.typeflag == "long name" then
long_name = nullterm(file_data)
elseif header.typeflag == "long link name" then
long_link_name = nullterm(file_data)
else
if long_name then
header.name = long_name
long_name = nil
end
if long_link_name then
header.name = long_link_name
long_link_name = nil
end
end
local pathname = dir.path(destdir, header.name)
if header.typeflag == "directory" then
make_dir(pathname)
elseif header.typeflag == "file" then
local dirname = fs.getDir(pathname)
if dirname ~= "" then
make_dir(dirname)
end
local file_handle
file_handle, err = io.open(pathname, "wb")
if not file_handle then
ok = nil
break
end
file_handle:write(file_data):close()
end
end
tar_handle:close()
return ok, err
end
--END OF luarocks original code
---Retrun the headers
---@param filename string
---@return table<number,tarHeader>?, string? reason
function tar.list(filename)
checkArg(1, filename, 'string')
local tar_handle = io.open(filename, "rb")
if not tar_handle then return nil, "Error opening file " .. filename end
local ok, err
local long_name, long_link_name
local files = {}
while true do
local block
repeat
block = tar_handle:read(blocksize)
until (not block) or checksum_header(block) > 256
if not block then break end
if #block < blocksize then
ok, err = nil, "Invalid block size -- corrupted file?"
break
end
local header
header, err = read_header_block(block)
if not header then
ok = false
break
end
local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1, header.size)
if header.typeflag == "long name" then
long_name = nullterm(file_data)
elseif header.typeflag == "long link name" then
long_link_name = nullterm(file_data)
else
if long_name then
header.name = long_name
long_name = nil
end
if long_link_name then
header.name = long_link_name
long_link_name = nil
end
end
if (header.typeflag == "file" or header.typeflag == "directory") then
table.insert(files, header)
end
end
tar_handle:close()
return files, err
end
---Extract files that match filename `fineNameInArchive:match("^"..filename)
---@param tarname string Tar archive to work with
---@param filename? string|table file name patern to extract
---@param overwrite boolean overwrite existing files.
---@param ignore? string|table file name patern to ignore
---@param destdir string
---@param newRoot? string
function tar.extract(tarname, destdir, overwrite, filename, ignore, newRoot)
checkArg(1, tarname, "string")
checkArg(2, destdir, "string")
checkArg(3, overwrite, "boolean")
checkArg(4, filename, "string", "table", 'nil')
checkArg(5, ignore, "string", "table", 'nil')
checkArg(6, destdir, "string")
checkArg(7, newRoot, "string", "nil")
---@param name string
---@return boolean
local function shouldExtractFile(name)
local ok = true
if (filename and type(filename) == "string") then
if (not name:match("^" .. filename)) then ok = false end
end
if (filename and type(filename) == "table") then
for _, fname in pairs(filename) do
if (not name:match("^" .. fname)) then ok = false end
end
end
if (ignore and type(ignore) == "string") then
if (name:match("^" .. ignore)) then ok = false end
end
if (ignore and type(ignore) == "table") then
for _, fname in pairs(ignore) do
if (name:match("^" .. fname)) then ok = false end
end
end
if (name == newRoot) then ok = false end
return ok
end
local tar_handle = io.open(tarname, "rb")
if not tar_handle then return nil, "Error opening file " .. tarname end
local long_name, long_link_name
---@type boolean?, string?
local ok, err = true, nil
local make_dir = fs.makeDir
while true do
local block
repeat
block = tar_handle:read(blocksize)
until (not block) or checksum_header(block) > 256
if not block then break end
if #block < blocksize then
ok, err = nil, "Invalid block size -- corrupted file?"
break
end
local header
header, err = read_header_block(block)
if not header then
ok = false
break
end
local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1, header.size)
if header.typeflag == "long name" then
long_name = nullterm(file_data)
elseif header.typeflag == "long link name" then
long_link_name = nullterm(file_data)
else
if long_name then
header.name = long_name
long_name = nil
end
if long_link_name then
header.name = long_link_name
long_link_name = nil
end
end
if (shouldExtractFile(header.name)) then
local pathname = dir.path(destdir, header.name)
if (newRoot) then --change the archive root
pathname = pathname:gsub(newRoot, "")
end
if header.typeflag == "directory" then
if (pathname ~= newRoot) then make_dir(pathname) end
elseif header.typeflag == "file" then
local dirname = fs.getDir(pathname)
if dirname ~= "" then
make_dir(dirname)
end
if (overwrite or not (fs.exists(pathname) and not fs.isDir(pathname))) then
local file_handle
file_handle, err = io.open(pathname, "wb")
if not file_handle then
ok = nil
break
end
file_handle:write(file_data):close()
end
end
end
end
tar_handle:close()
return ok, err
end
return tar

9
tar/libtar.files.json Normal file
View File

@@ -0,0 +1,9 @@
{
"files": [
[
"lib/tar.lua",
"/usr/lib/"
]
],
"config": []
}

9
tar/libtar.manifest Normal file
View File

@@ -0,0 +1,9 @@
{
["manifestVersion"] = "1.0",
["package"] = "libtar",
["version"] = "1.0.0"
["name"] = "libtar",
["repo"] = "tree/master/tar",
["description"] = "Librairy to extract and list the content of tar archive",
["authors"] = "AR2000AR, luarock"
}

View File

@@ -0,0 +1 @@
shell.setPath(shell.path() .. ":/usr/bin")

View File

@@ -0,0 +1,9 @@
{
"files": [
[
"startup/usrpath.lua",
"/startup/"
]
],
"config": []
}

9
usrpath/usrpath.manifest Normal file
View File

@@ -0,0 +1,9 @@
{
["manifestVersion"] = "1.0",
["package"] = "usrpath",
["version"] = "1.0.0"
["name"] = "usrpath",
["repo"] = "tree/master/usrpath",
["description"] = "Add /usr/bin to shell.path",
["authors"] = "AR2000AR"
}