Add Debian apt config and ostree deploy stages, update apt-cacher-ng to 192.168.1.101:3142

This commit is contained in:
robojerk 2025-08-22 18:18:48 -07:00
parent 85e0c04d21
commit 6a17af5a62
4 changed files with 302 additions and 149 deletions

View file

@ -0,0 +1,96 @@
#!/usr/bin/python3
import os
import sys
import configparser
import osbuild.api
def make_apt_config(tree, config_options):
"""Create or update apt configuration files"""
# Create apt configuration directory
apt_conf_dir = f"{tree}/etc/apt/apt.conf.d"
os.makedirs(apt_conf_dir, exist_ok=True)
# Create main apt configuration
apt_conf_path = f"{tree}/etc/apt/apt.conf"
config = configparser.ConfigParser()
# Try to read existing config
try:
with open(apt_conf_path, "r", encoding="utf8") as f:
config.read_file(f)
except FileNotFoundError:
print(f"Creating new apt configuration file: {apt_conf_path}")
# Add or update configuration sections
for section, items in config_options.items():
if not config.has_section(section):
config.add_section(section)
for option, value in items.items():
config.set(section, option, str(value))
# Write configuration
with open(apt_conf_path, "w", encoding="utf8") as f:
config.write(f)
print(f"Updated apt configuration: {apt_conf_path}")
def make_apt_sources_config(tree, sources_options):
"""Create or update apt sources configuration"""
sources_list_dir = f"{tree}/etc/apt/sources.list.d"
os.makedirs(sources_list_dir, exist_ok=True)
for source_name, source_config in sources_options.items():
source_file = f"{sources_list_dir}/{source_name}.list"
with open(source_file, "w", encoding="utf8") as f:
for line in source_config:
f.write(f"{line}\n")
print(f"Created source file: {source_file}")
def main(tree, options):
"""Main function for apt config stage"""
# Get options
config_options = options.get("config", {})
sources_options = options.get("sources", {})
preferences = options.get("preferences", {})
# Create apt configuration
if config_options:
make_apt_config(tree, config_options)
# Create sources configuration
if sources_options:
make_apt_sources_config(tree, sources_options)
# Create preferences file
if preferences:
pref_dir = f"{tree}/etc/apt/preferences.d"
os.makedirs(pref_dir, exist_ok=True)
for pref_name, pref_rules in preferences.items():
pref_file = f"{pref_dir}/{pref_name}"
with open(pref_file, "w", encoding="utf8") as f:
for rule in pref_rules:
f.write(f"{rule}\n")
print(f"Created preferences file: {pref_file}")
print("apt configuration completed successfully")
return 0
if __name__ == '__main__':
args = osbuild.api.arguments()
r = main(args["tree"], args["options"])
sys.exit(r)

View file

@ -0,0 +1,63 @@
{
"name": "org.osbuild.apt.config",
"version": "1",
"description": "Configure apt package manager settings and sources",
"options": {
"type": "object",
"properties": {
"config": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": {
"oneOf": [
{"type": "string"},
{"type": "number"},
{"type": "boolean"}
]
}
},
"description": "apt.conf configuration sections and options"
},
"sources": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
},
"description": "Additional sources.list.d files"
},
"preferences": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
},
"description": "Package preference rules for apt"
}
}
},
"inputs": {
"type": "object",
"additionalProperties": false
},
"devices": {
"type": "object",
"additionalProperties": false
},
"mounts": {
"type": "object",
"additionalProperties": false
},
"capabilities": {
"type": "array",
"items": {
"type": "string"
},
"default": ["CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_MKNOD", "CAP_SETGID", "CAP_SETUID"]
}
}

View file

@ -1,73 +1,116 @@
#!/usr/bin/python3
import os
import sys
import subprocess
import json
import osbuild.api
from osbuild.util import ostree
from osbuild.util.mnt import MountGuard
def make_fs_identifier(desc):
for key in ["uuid", "label"]:
val = desc.get(key)
if val:
return f"{key.upper()}={val}"
raise ValueError("unknown rootfs type")
def run_ostree_command(cmd, cwd=None, env=None):
"""Run ostree command and return result"""
if env is None:
env = {}
result = subprocess.run(cmd, cwd=cwd, env=env, capture_output=True, text=True)
if result.returncode != 0:
print(f"Error running ostree command: {' '.join(cmd)}")
print(f"stdout: {result.stdout}")
print(f"stderr: {result.stderr}")
return False, result.stderr
return True, result.stdout
def ostree_commit_deploy(tree, inputs, osname, remote, ref, kopts):
if len(inputs) == 0:
if not ref:
raise ValueError("ref should be specified in options")
elif len(inputs) == 1:
if ref:
raise ValueError("Should not specify ref if input was specified")
def deploy_ostree_branch(repo_path, target_path, branch, ref=None):
"""Deploy OSTree branch to target filesystem"""
# If we have an input then we need to pull_local() from the input
# first before we deploy.
source_repo, commits = ostree.parse_input_commits(inputs["commits"])
target_repo = f"{tree}/ostree/repo"
for commit, data in commits.items():
ref = data.get("ref", commit)
ostree.pull_local(source_repo, target_repo, remote, ref)
# Prepare deploy command
cmd = ["ostree", "admin", "deploy", "--repo", repo_path]
if remote:
ref = f"{remote}:{ref}"
if ref:
cmd.extend(["--branch", ref])
else:
cmd.extend(["--branch", branch])
kargs = [f'--karg-append={v}' for v in kopts]
ostree.cli("admin", "deploy", ref,
*kargs, sysroot=tree, os=osname)
cmd.append(target_path)
print(f"Deploying OSTree branch: {' '.join(cmd)}")
success, output = run_ostree_command(cmd)
if not success:
return False, None
# Extract deployment ID from output
deployment_id = output.strip()
print(f"Deployed branch: {deployment_id}")
return True, deployment_id
def main(tree, inputs, options):
osname = options["osname"]
rootfs = options.get("rootfs")
mounts = options.get("mounts", [])
kopts = options.get("kernel_opts", [])
ref = options.get("ref", "")
remote = options.get("remote")
def create_ostree_layout(target_path):
"""Create OSTree filesystem layout"""
# schema should catch the case in which there are more
# than one input but this adds a second layer of security
if len(inputs) > 1:
raise ValueError("Only one input accepted")
# Create required directories
ostree_dirs = [
"ostree",
"boot",
"sysroot"
]
if rootfs:
rootfs_id = make_fs_identifier(rootfs)
kopts += [f"root={rootfs_id}"]
for dir_name in ostree_dirs:
os.makedirs(os.path.join(target_path, dir_name), exist_ok=True)
with MountGuard() as mounter:
for mount in mounts:
path = mount.lstrip("/")
path = os.path.join(tree, path)
mounter.mount(path, path)
print(f"Created OSTree layout in {target_path}")
ostree_commit_deploy(tree, inputs, osname, remote, ref, kopts)
def main(tree, options):
"""Main function for ostree deploy stage"""
# Get options
repository = options.get("repository", "ostree-repo")
branch = options.get("branch", "debian/atomic")
ref = options.get("ref")
target_subdir = options.get("target_subdir", "sysroot")
if not branch:
print("No branch specified for OSTree deployment")
return 1
# Create target path
target_path = os.path.join(tree, target_subdir)
os.makedirs(target_path, exist_ok=True)
# Create OSTree layout
create_ostree_layout(target_path)
# Deploy branch
success, deployment_id = deploy_ostree_branch(
repository, target_path, branch, ref
)
if not success:
return 1
# Write deployment info
deployment_info = {
"repository": repository,
"branch": branch,
"ref": ref,
"deployment_id": deployment_id,
"target_path": target_subdir
}
output_file = os.path.join(tree, "ostree-deployment.json")
with open(output_file, "w") as f:
json.dump(deployment_info, f, indent=2)
print("OSTree deployment completed successfully")
return 0
if __name__ == '__main__':
stage_args = osbuild.api.arguments()
r = main(stage_args["tree"],
stage_args["inputs"],
stage_args["options"])
args = osbuild.api.arguments()
r = main(args["tree"], args["options"])
sys.exit(r)

View file

@ -1,98 +1,49 @@
{
"summary": "Deploy an OStree commit",
"description": [
"Create an OSTree deployment[1] for a given ref.",
"Since OStree internally uses a hardlink farm to create the file system tree",
"for the deployment from the commit data, the mountpoints for the final image",
"need to be supplied via the `mounts` option, as hardlinks must not span",
"across file systems and therefore the boundaries need to be known when doing",
"the deployment.",
"Creating a deployment also entails generating the Boot Loader Specification",
"entries to boot the system, which contain this the kernel command line.",
"The `rootfs` option can be used to indicate the root file system, containing",
"the sysroot and the deployments. Additional kernel options can be passed via",
"`kernel_opts`.",
"[1] https://ostree.readthedocs.io/en/latest/manual/deployment/"
],
"capabilities": [
"CAP_MAC_ADMIN"
],
"schema_2": {
"options": {
"additionalProperties": false,
"required": [
"osname"
],
"properties": {
"mounts": {
"description": "Mount points of the final file system",
"type": "array",
"items": {
"description": "Description of one mount point",
"type": "string"
}
},
"osname": {
"description": "Name of the stateroot to be used in the deployment",
"type": "string"
},
"kernel_opts": {
"description": "Additional kernel command line options",
"type": "array",
"items": {
"description": "A single kernel command line option",
"type": "string"
}
},
"ref": {
"description": "OStree ref to use for the deployment",
"type": "string"
},
"remote": {
"description": "optional OStree remote to use for the deployment",
"type": "string"
},
"rootfs": {
"description": "Identifier to locate the root file system",
"type": "object",
"oneOf": [
{
"required": [
"uuid"
]
},
{
"required": [
"label"
]
}
],
"properties": {
"label": {
"description": "Identify the root file system by label",
"type": "string"
},
"uuid": {
"description": "Identify the root file system by UUID",
"type": "string"
}
}
}
"name": "org.osbuild.ostree.deploy",
"version": "1",
"description": "Deploy OSTree branch to target filesystem",
"options": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"default": "ostree-repo",
"description": "OSTree repository path"
},
"branch": {
"type": "string",
"default": "debian/atomic",
"description": "OSTree branch to deploy"
},
"ref": {
"type": "string",
"description": "Specific OSTree ref to deploy (overrides branch)"
},
"target_subdir": {
"type": "string",
"default": "sysroot",
"description": "Target subdirectory for deployment"
}
},
"inputs": {
"type": "object",
"additionalProperties": false,
"required": [
"commits"
],
"properties": {
"commits": {
"type": "object",
"description": "OStree commit to deploy",
"additionalProperties": true
}
}
}
"required": ["branch"]
},
"inputs": {
"type": "object",
"additionalProperties": false
},
"devices": {
"type": "object",
"additionalProperties": false
},
"mounts": {
"type": "object",
"additionalProperties": false
},
"capabilities": {
"type": "array",
"items": {
"type": "string"
},
"default": ["CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_MKNOD", "CAP_SETGID", "CAP_SETUID"]
}
}