mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-07-24 00:38:16 -07:00
Added ability to fetch new verified releases directly from RSM-embedded release manifest data. Added local release generation and signing with the --local option to rnid.
This commit is contained in:
@@ -50,6 +50,7 @@ from RNS.vendor.configobj import ConfigObj
|
|||||||
from RNS.vendor import umsgpack as mp
|
from RNS.vendor import umsgpack as mp
|
||||||
from RNS.Utilities.rnid import create_rsg, validate_rsg, get_rsg_hash
|
from RNS.Utilities.rnid import create_rsg, validate_rsg, get_rsg_hash
|
||||||
from RNS.Utilities.rnid import rsg_meta_from_str, extract_signed_rsg_data
|
from RNS.Utilities.rnid import rsg_meta_from_str, extract_signed_rsg_data
|
||||||
|
from RNS.Utilities.rnid import check_release_rsm_structure
|
||||||
|
|
||||||
def program_setup(configdir, rnsconfigdir=None, verbosity=0, quietness=0, service=False, interactive=False, print_identity=False, task=None, identity=None, signer=None):
|
def program_setup(configdir, rnsconfigdir=None, verbosity=0, quietness=0, service=False, interactive=False, print_identity=False, task=None, identity=None, signer=None):
|
||||||
targetverbosity = verbosity-quietness
|
targetverbosity = verbosity-quietness
|
||||||
@@ -88,7 +89,7 @@ def program_setup(configdir, rnsconfigdir=None, verbosity=0, quietness=0, servic
|
|||||||
if operation == "list": git_client.list_releases(remote=task["remote"])
|
if operation == "list": git_client.list_releases(remote=task["remote"])
|
||||||
elif operation == "view": git_client.view_release(remote=task["remote"], target=task["target"])
|
elif operation == "view": git_client.view_release(remote=task["remote"], target=task["target"])
|
||||||
elif operation == "fetch": git_client.fetch_release(remote=task["remote"], target=task["target"], signer=task["signer"])
|
elif operation == "fetch": git_client.fetch_release(remote=task["remote"], target=task["target"], signer=task["signer"])
|
||||||
elif operation == "create": git_client.create_release(remote=task["remote"], target=task["target"], signer=task["signer"], name=task["name"])
|
elif operation == "create": git_client.create_release(remote=task["remote"], target=task["target"], signer=task["signer"], name=task["name"], no_upload=task["no_upload"])
|
||||||
elif operation == "delete": git_client.delete_release(remote=task["remote"], target=task["target"])
|
elif operation == "delete": git_client.delete_release(remote=task["remote"], target=task["target"])
|
||||||
elif operation == "latest": git_client.latest_release(remote=task["remote"], target=task["target"])
|
elif operation == "latest": git_client.latest_release(remote=task["remote"], target=task["target"])
|
||||||
else: print("Invalid operation"); exit(1)
|
else: print("Invalid operation"); exit(1)
|
||||||
@@ -162,7 +163,8 @@ def main():
|
|||||||
parser.add_argument("-i", "--identity", action="store", metavar="PATH", default=None, help="path to release identity", type=str)
|
parser.add_argument("-i", "--identity", action="store", metavar="PATH", default=None, help="path to release identity", type=str)
|
||||||
parser.add_argument("-s", "--signer", action="store", metavar="PATH", default=None, help="path to signing identity, if different from release identity", type=str)
|
parser.add_argument("-s", "--signer", action="store", metavar="PATH", default=None, help="path to signing identity, if different from release identity", type=str)
|
||||||
parser.add_argument("-n", "--name", action="store", metavar="name", default=None, help="package name if different from repo name", type=str)
|
parser.add_argument("-n", "--name", action="store", metavar="name", default=None, help="package name if different from repo name", type=str)
|
||||||
parser.add_argument("repository", nargs="?", default=None, help="URL of remote repository", type=str)
|
parser.add_argument('-L', '--local', action='store_true', default=False, help="generate release locally, but don't upload")
|
||||||
|
parser.add_argument("repository", nargs="?", default=None, help="URL of remote repository, or path to RSM manifest", type=str)
|
||||||
parser.add_argument("operation", nargs="?", default=None, help="list, view, fetch, create, latest or delete", type=str)
|
parser.add_argument("operation", nargs="?", default=None, help="list, view, fetch, create, latest or delete", type=str)
|
||||||
parser.add_argument("target", nargs="?", default=None, help="tag and path to release artifacts directory", type=str)
|
parser.add_argument("target", nargs="?", default=None, help="tag and path to release artifacts directory", type=str)
|
||||||
|
|
||||||
@@ -232,7 +234,7 @@ def main():
|
|||||||
elif subcommand == "release":
|
elif subcommand == "release":
|
||||||
if not args.operation: parser.print_help()
|
if not args.operation: parser.print_help()
|
||||||
task = {"command": subcommand, "operation": args.operation, "remote": args.repository, "target": args.target,
|
task = {"command": subcommand, "operation": args.operation, "remote": args.repository, "target": args.target,
|
||||||
"signer": args.signer, "name": args.name}
|
"signer": args.signer, "name": args.name, "no_upload": args.local}
|
||||||
program_setup(configdir=configarg, rnsconfigdir=rnsconfigarg, verbosity=args.verbose, quietness=args.quiet,
|
program_setup(configdir=configarg, rnsconfigdir=rnsconfigarg, verbosity=args.verbose, quietness=args.quiet,
|
||||||
task=task, identity=args.identity, signer=args.signer)
|
task=task, identity=args.identity, signer=args.signer)
|
||||||
|
|
||||||
@@ -857,6 +859,23 @@ class ReticulumGitClient():
|
|||||||
try: signer_hash = bytes.fromhex(signer)
|
try: signer_hash = bytes.fromhex(signer)
|
||||||
except Exception as e: self.abort(f"Invalid required signer identity hash: {e}")
|
except Exception as e: self.abort(f"Invalid required signer identity hash: {e}")
|
||||||
|
|
||||||
|
if remote.endswith(f".{self.MSG_EXT}") and os.path.isfile(os.path.expanduser(remote)):
|
||||||
|
manifest_path = os.path.expanduser(remote)
|
||||||
|
with open(manifest_path, "rb") as fh: rsg = fh.read()
|
||||||
|
rsm_contents = extract_signed_rsg_data(rsg)
|
||||||
|
if not "message" in rsm_contents: self.abort(f"No embedded message in release manifest")
|
||||||
|
valid, signed_data, signing_identity = validate_rsg(rsg, message=rsm_contents["message"], required_signer=signer_hash)
|
||||||
|
if not valid: self.abort(f"Release manifest not signed by {RNS.prettyhexrep(signer_hash)}, aborting") if signer_hash else self.abort("Could not validate release manifest signature")
|
||||||
|
else:
|
||||||
|
print(f"Valid release manifest signature, signed by {signing_identity}")
|
||||||
|
rsm_structure_integrity = check_release_rsm_structure(signed_data)
|
||||||
|
if rsm_structure_integrity != True: self.abort(rsm_structure_integrity)
|
||||||
|
release_meta = signed_data.get("meta", None)
|
||||||
|
release_origin = release_meta.get("origin", None)
|
||||||
|
release_origin_path = release_meta.get("path", None)
|
||||||
|
remote = f"rns://{RNS.hexrep(release_origin, delimit=False)}/{release_origin_path}"
|
||||||
|
print(f"Release origin is {remote}")
|
||||||
|
|
||||||
self.connect_remote(remote)
|
self.connect_remote(remote)
|
||||||
|
|
||||||
timeout = self.link_timeout
|
timeout = self.link_timeout
|
||||||
@@ -876,7 +895,7 @@ class ReticulumGitClient():
|
|||||||
artifact = parts[1]
|
artifact = parts[1]
|
||||||
|
|
||||||
def fetch(name):
|
def fetch(name):
|
||||||
self.transfer_label = name; self.progress_indent = ""
|
self.transfer_label = name
|
||||||
request_data = {self.IDX_REPOSITORY: repo_path, "operation": "fetch", "tag": tag, "artifact": name}
|
request_data = {self.IDX_REPOSITORY: repo_path, "operation": "fetch", "tag": tag, "artifact": name}
|
||||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=30, progress=True)
|
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=30, progress=True)
|
||||||
print("\r \r", end="")
|
print("\r \r", end="")
|
||||||
@@ -901,6 +920,7 @@ class ReticulumGitClient():
|
|||||||
return response.name
|
return response.name
|
||||||
|
|
||||||
# Fetch release manifest
|
# Fetch release manifest
|
||||||
|
self.progress_indent = ""
|
||||||
manifest_path = fetch("manifest.rsm")
|
manifest_path = fetch("manifest.rsm")
|
||||||
with open(manifest_path, "rb") as fh: rsg = fh.read()
|
with open(manifest_path, "rb") as fh: rsg = fh.read()
|
||||||
rsm_contents = extract_signed_rsg_data(rsg)
|
rsm_contents = extract_signed_rsg_data(rsg)
|
||||||
@@ -909,12 +929,11 @@ class ReticulumGitClient():
|
|||||||
if not valid: self.abort(f"Release manifest not signed by {RNS.prettyhexrep(signer_hash)}, aborting") if signer_hash else self.abort("Could not validate release manifest signature")
|
if not valid: self.abort(f"Release manifest not signed by {RNS.prettyhexrep(signer_hash)}, aborting") if signer_hash else self.abort("Could not validate release manifest signature")
|
||||||
else:
|
else:
|
||||||
print(f"Release manifest validated, signed by {signing_identity}")
|
print(f"Release manifest validated, signed by {signing_identity}")
|
||||||
|
rsm_structure_integrity = check_release_rsm_structure(signed_data)
|
||||||
|
if rsm_structure_integrity != True: self.abort(rsm_structure_integrity)
|
||||||
release_meta = signed_data.get("meta", None)
|
release_meta = signed_data.get("meta", None)
|
||||||
if not release_meta: self.abort(f"No release metadata in manifest")
|
|
||||||
release_name = release_meta.get("name", None)
|
release_name = release_meta.get("name", None)
|
||||||
release_version = release_meta.get("version", None)
|
release_version = release_meta.get("version", None)
|
||||||
if not release_name or not release_version: self.abort("Incomplete release data in manifest")
|
|
||||||
if "/" in release_name or "/" in release_version: self.abort("Invalid data in release manifest")
|
|
||||||
manifest_out = os.path.basename(f"{release_name}_{release_version}.{self.MSG_EXT}")
|
manifest_out = os.path.basename(f"{release_name}_{release_version}.{self.MSG_EXT}")
|
||||||
with open(manifest_out, "wb") as fh: fh.write(rsg)
|
with open(manifest_out, "wb") as fh: fh.write(rsg)
|
||||||
|
|
||||||
@@ -927,6 +946,9 @@ class ReticulumGitClient():
|
|||||||
fetch_artifacts = [entry]; break
|
fetch_artifacts = [entry]; break
|
||||||
|
|
||||||
if not fetch_artifacts: self.abort("No available artifacts specified for fetch")
|
if not fetch_artifacts: self.abort("No available artifacts specified for fetch")
|
||||||
|
ms = "" if len(fetch_artifacts) == 1 else "s"
|
||||||
|
print(f"Fetching {len(fetch_artifacts)} artifact{ms}...")
|
||||||
|
self.progress_indent = " "
|
||||||
for artifact in fetch_artifacts:
|
for artifact in fetch_artifacts:
|
||||||
name = os.path.basename(artifact["name"])
|
name = os.path.basename(artifact["name"])
|
||||||
rsg = artifact["rsg"]
|
rsg = artifact["rsg"]
|
||||||
@@ -946,7 +968,7 @@ class ReticulumGitClient():
|
|||||||
finally:
|
finally:
|
||||||
if self.link: self.link.teardown()
|
if self.link: self.link.teardown()
|
||||||
|
|
||||||
def create_release(self, remote=None, target=None, signer=None, name=None):
|
def create_release(self, remote=None, target=None, signer=None, name=None, no_upload=False):
|
||||||
if signer:
|
if signer:
|
||||||
try:
|
try:
|
||||||
identity_path = os.path.expanduser(signer)
|
identity_path = os.path.expanduser(signer)
|
||||||
@@ -958,15 +980,7 @@ class ReticulumGitClient():
|
|||||||
if not remote: self.abort(f"No remote specified")
|
if not remote: self.abort(f"No remote specified")
|
||||||
if not target: self.abort(f"No target specified")
|
if not target: self.abort(f"No target specified")
|
||||||
if not signer: signer = self.identity
|
if not signer: signer = self.identity
|
||||||
self.connect_remote(remote)
|
if no_upload: self.link = None
|
||||||
|
|
||||||
timeout = self.link_timeout
|
|
||||||
while not self.link_ready and not self.link_failed and timeout > 0:
|
|
||||||
time.sleep(self.wait_sleep)
|
|
||||||
timeout -= self.wait_sleep
|
|
||||||
|
|
||||||
if not self.link_ready: self.abort("Failed to establish link")
|
|
||||||
print("\r \r", end="")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
destination_hash, group, repo = self.parse_remote_url(remote)
|
destination_hash, group, repo = self.parse_remote_url(remote)
|
||||||
@@ -992,7 +1006,7 @@ class ReticulumGitClient():
|
|||||||
# Generate manifest
|
# Generate manifest
|
||||||
package_name = name or repo
|
package_name = name or repo
|
||||||
manifest_meta = {"name": package_name ,"version": tag, "released": release_time_iso, "timestamp": release_time,
|
manifest_meta = {"name": package_name ,"version": tag, "released": release_time_iso, "timestamp": release_time,
|
||||||
"origin": destination_hash, "commit": commit_hash, "artifacts": []}
|
"origin": destination_hash, "path": f"{group}/{repo}", "commit": commit_hash, "artifacts": []}
|
||||||
try:
|
try:
|
||||||
manifest_path = artifacts_path+f"/manifest.{self.MSG_EXT}"
|
manifest_path = artifacts_path+f"/manifest.{self.MSG_EXT}"
|
||||||
rsgs = []
|
rsgs = []
|
||||||
@@ -1017,6 +1031,17 @@ class ReticulumGitClient():
|
|||||||
|
|
||||||
except Exception as e: self.abort(f"Release manifest generation failed: {e}")
|
except Exception as e: self.abort(f"Release manifest generation failed: {e}")
|
||||||
|
|
||||||
|
if no_upload: print(f"Local release {package_name}:{tag} generated successfully in {target}"); exit(0)
|
||||||
|
else: self.connect_remote(remote)
|
||||||
|
|
||||||
|
timeout = self.link_timeout
|
||||||
|
while not self.link_ready and not self.link_failed and timeout > 0:
|
||||||
|
time.sleep(self.wait_sleep)
|
||||||
|
timeout -= self.wait_sleep
|
||||||
|
|
||||||
|
if not self.link_ready: self.abort("Failed to establish link")
|
||||||
|
print("\r \r", end="")
|
||||||
|
|
||||||
# Step 1: Initialize release
|
# Step 1: Initialize release
|
||||||
print("Initializing release on remote...")
|
print("Initializing release on remote...")
|
||||||
request_data = { self.IDX_REPOSITORY: repo_path,
|
request_data = { self.IDX_REPOSITORY: repo_path,
|
||||||
@@ -1074,8 +1099,7 @@ class ReticulumGitClient():
|
|||||||
|
|
||||||
print(f"Release {tag} published")
|
print(f"Release {tag} published")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: self.abort(f"Error creating release: {e}")
|
||||||
self.abort(f"Error creating release: {e}")
|
|
||||||
finally:
|
finally:
|
||||||
if self.link: self.link.teardown()
|
if self.link: self.link.teardown()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user