API Reference

scitex_container

scitex-container: Unified container management for Apptainer and Docker.

scitex_container.build(def_name='scitex-cloud-shared-v0.1.0', output_dir=None, force=False, sandbox=False, *, def_path=None, image_name=None, use_sudo=False, fakeroot=None)[source]

Build Apptainer/Singularity SIF or sandbox from .def file.

Parameters:
  • def_name (str) – Name of the .def file (without extension) when looking it up in the discovered containers dir. Ignored if def_path is given.

  • output_dir (str or Path, optional) – Directory under which the per-image subdir is created (i.e. the artifact lands at <output_dir>/<image_name>/<image_name>.sif). Defaults to the directory containing the resolved .def file.

  • force (bool) – Force rebuild even if .def is unchanged.

  • sandbox (bool) – If True, build a sandbox directory instead of a SIF image. Uses: apptainer build –sandbox –fakeroot output-sandbox/ input.def

  • def_path (str or Path, optional) – Explicit path to the .def file. Lets out-of-tree callers (e.g. scitex-agent-container, whose recipes ship inside its own wheel) bypass find_containers_dir. When given, def_name is ignored for lookup and image_name defaults to the .def’s stem.

  • image_name (str, optional) – Name of the per-image subdir + artifact stem (i.e. the artifact lands at <output_dir>/<image_name>/<image_name>.sif). Defaults to def_name (or the .def stem when def_path is given). Use this when the on-disk recipe is named differently from the image you want to produce — e.g. sac’s apptainer-base.defsac-base/sac-base.sif.

  • use_sudo (bool)

  • fakeroot (bool | None)

Returns:

Path to the built .sif file or sandbox directory.

Return type:

Path

Raises:
scitex_container.build_reproducible(layer, root, *, def_path=None, def_name=None, verify=True, keep=False, config=None, force=False)[source]

Run the reproducible round-trip and manage the artifact store.

Steps 1-3 (rough build, freeze lock, generate locked def) always run synchronously — the rough SIF + lock + locked def are the kept, immediately-usable artifacts. Steps 4-5 (verify rebuild + compare) run inline when verify=True.

The operator design specifies steps 4-5 run BACKGROUND-by-default in the CLI; this function exposes the synchronous primitive that a caller (CLI/MCP) backgrounds. verify=False skips them entirely (leaving the build unmarked) so a caller can schedule the verify rebuild as a detached job and call verify_roundtrip later.

Parameters:
  • layer (str) – Layer name (artifact stem, e.g. sac-base).

  • root (str or Path) – The containers/ directory (path injection).

  • def_path (str or Path, optional) – Explicit path to the rough .def. Either this or def_name.

  • def_name (str, optional) – Name of the .def to look up via find_containers_dir.

  • verify (bool) – Run steps 4-5 inline. False = skip (build stays unmarked).

  • keep (bool) – Write the .keep prune-protect marker on the build.

  • config (ImageConfig, optional) – Resolved config (retain). Loaded from root when None.

  • force (bool) – Force the rough rebuild even when the recipe hash is unchanged.

Returns:

The kept artifact paths + verify outcome.

Return type:

RoundTripResult

scitex_container.check_verified(sif_path, *, require_verified=None, root=None, config=None)[source]

Check a built image’s reproducibility marker — NOISY on every use.

The use-time gate consumers call on every image use. Looks beside the SIF for the .verified / .unverified marker (resolving a latest symlink first):

  • .verified present → state="verified" (silent OK).

  • .unverified present → WARN by default (“reproducibility unverified: <drift>”); under require_verified → raise VerifyError.

  • no marker → state="unknown" → WARN it’s unverified; under require_verified → raise.

Parameters:
  • sif_path (str or Path) – Path to the image being used (may be the latest symlink).

  • require_verified (bool, optional) – Strict mode. When None, resolved from config / load_config(root) (images.require_verified).

  • root (str or Path, optional) – Output root for config resolution (when require_verified and config are both None).

  • config (ImageConfig, optional) – Pre-resolved config.

Returns:

The marker state + detail.

Return type:

VerifyStatus

Raises:

VerifyError – When the image is not verified and strict mode is on.

scitex_container.cleanup(containers_dir, keep=3)[source]

Remove old scitex-v*.sif files, keeping the N most recent.

Never removes the active version (current.sif target) or any scitex-base-v*.sif base images.

Parameters:
  • containers_dir (Path) – Directory containing SIF files.

  • keep (int) – Number of most-recent versioned SIFs to keep (default: 3).

Returns:

Paths of removed SIF files.

Return type:

list[Path]

scitex_container.deploy(source_dir, target_dir=PosixPath('/opt/scitex/singularity'))[source]

Copy active SIF and base SIF to target directory.

Copies the currently active scitex-v*.sif and the latest scitex-base-v*.sif to target_dir, then recreates the current.sif symlink there. Uses sudo for the copy.

Parameters:
  • source_dir (Path) – Directory containing the built SIF files.

  • target_dir (Path) – Deployment target directory (default: /opt/scitex/singularity).

Raises:
Return type:

None

scitex_container.docker_rebuild(env='dev', project_dir=None)

Rebuild Docker containers without using the cache.

Runs: docker compose -f <compose_file> build --no-cache

Parameters:
  • env (str) – Environment name used to locate the compose file.

  • project_dir (str or Path or None) – Explicit directory containing the compose file. When None, the function walks upward from the current working directory.

Returns:

Exit code of the docker compose command (0 = success).

Return type:

int

scitex_container.docker_restart(env='dev', project_dir=None)

Restart Docker containers (down then up).

Runs: docker compose -f <compose_file> down then: docker compose -f <compose_file> up -d

Parameters:
  • env (str) – Environment name used to locate the compose file.

  • project_dir (str or Path or None) – Explicit directory containing the compose file.

Returns:

Exit code of the final up -d command (0 = success). If down fails its exit code is returned instead.

Return type:

int

scitex_container.env_snapshot(containers_dir=None, dev_repos=None)[source]

Capture a lightweight JSON-serializable environment snapshot.

Gracefully degrades — never raises, just omits fields that cannot be determined.

Parameters:
  • containers_dir (str or Path, optional) – Path to the containers directory. Auto-detected via find_containers_dir() when None.

  • dev_repos (list of str or Path, optional) – Paths to git repositories to include in dev_repos section.

Returns:

JSON-serializable snapshot with keys: schema_version, timestamp, container, host, dev_repos, lock_files.

Return type:

dict

scitex_container.host_check()

Check which host packages are installed.

Returns:

Structured status per package group:

{
    "texlive": {
        "installed": True,
        "version": "pdfTeX 3.141592653...",
        "binaries": ["pdflatex", "bibtex", ...],
    },
    "imagemagick": {
        "installed": True,
        "version": "Version: ImageMagick 6.9...",
        "binaries": ["convert", "identify"],
    },
}

Return type:

dict

scitex_container.host_install(texlive=False, imagemagick=False, all=False, check_only=False)

Install host packages by calling the shell script.

Parameters:
  • texlive (bool) – Install TeXLive packages.

  • imagemagick (bool) – Install ImageMagick.

  • all (bool) – Install all packages (overrides texlive/imagemagick flags).

  • check_only (bool) – Run the script in –check mode without installing anything.

Returns:

Status per package group:

{
    "texlive": {"status": "installed", "returncode": 0},
    "imagemagick": {"status": "skipped", "returncode": None},
    "script": "/abs/path/to/install-host-packages.sh",
}

Return type:

dict

Raises:

FileNotFoundError – If the install script cannot be found.

scitex_container.list_builds(root, layer)[source]

List the timestamped builds of layer under root.

Parameters:
  • root (str or Path) – The containers/ directory.

  • layer (str) – Layer name (e.g. sac-base).

Returns:

One dict per build with keys ts, sif, lock, def, verified (bool|None — None when no marker yet), keep (bool), active (bool — the current latest symlink target). Sorted newest ts first.

Return type:

list[dict]

scitex_container.list_versions(containers_dir)[source]

List all versioned SIFs with metadata.

Parameters:

containers_dir (Path) – Directory containing SIF files.

Returns:

Each dict contains keys: version, path, size, date, active. Sorted by modification time (newest first).

Return type:

list[dict]

scitex_container.rollback(containers_dir, use_sudo=False)[source]

Switch to the version before the current one (by modification time).

Parameters:
  • containers_dir (Path) – Directory containing SIF files.

  • use_sudo (bool) – If True, run symlink commands via sudo.

Returns:

Version string that is now active after rollback.

Return type:

str

Raises:

RuntimeError – If there is no current version or no previous version to roll back to.

scitex_container.sandbox_create(source, containers_dir=None, *, output_dir=None)

Build a sandbox directory from a SIF image or .def file.

Creates a timestamped sandbox (sandbox-YYYYMMDD_HHMMSS/) and updates the current-sandbox symlink to point to it.

Parameters:
  • source (str or Path) – Path to the source .sif file or .def file.

  • containers_dir (str or Path, optional) – Parent directory for sandbox output and symlink. Defaults to source file’s parent directory.

  • output_dir (str or Path, optional) – Explicit output path (overrides timestamped naming).

Returns:

Path to the created sandbox directory.

Return type:

Path

Raises:
scitex_container.status(containers_dir=None)[source]

List available containers and their status.

Parameters:

containers_dir (str or Path, optional) – Directory containing .def files. Auto-detected if not given.

Returns:

List of container info dicts with keys: name, def_path, sif_path, sif_size, sif_date, hash_current, hash_stored, needs_rebuild.

Return type:

list[dict]

scitex_container.switch(version, containers_dir, use_sudo=False)

Atomically switch current.sif symlink to scitex-v{version}.sif.

Uses ln -sf to create a temporary symlink, then mv -Tf for an atomic rename on the same filesystem.

Parameters:
  • version (str) – Target version string (e.g. “2.19.5”).

  • containers_dir (Path) – Directory containing SIF files.

  • use_sudo (bool) – If True, run ln/mv via sudo (needed for /opt/scitex paths).

Raises:
Return type:

None

scitex_container.verify(sif_path, def_path=None, lock_dir=None)[source]

Verify container integrity.

Checks:

  1. SIF exists and computes its SHA256

  2. If def_path given, compares .def hash against stored .def-hash

  3. If lock files exist, runs pip freeze / dpkg-query inside the SIF and compares against stored lock files

Parameters:
  • sif_path (str or Path) – Path to the .sif file to verify.

  • def_path (str or Path, optional) – Path to the .def file that should have produced this SIF.

  • lock_dir (str or Path, optional) – Directory containing lock files (requirements-lock.txt, dpkg-lock.txt). Defaults to same directory as the SIF.

Returns:

Verification results:

{
    "sif": {"path": "...", "sha256": "...", "exists": True},
    "def_origin": {"status": "pass|fail|skip", "detail": "..."},
    "pip_lock": {"status": "pass|fail|skip", "detail": "...", "diff_count": 0},
    "dpkg_lock": {"status": "pass|fail|skip", "detail": "...", "diff_count": 0},
    "overall": "pass|fail"
}

Return type:

dict

scitex_container.verify_roundtrip(layer, root, ts)[source]

Rebuild from the locked def, compare version sets, mark the build.

This is steps 4-5 split out so a caller can run it in the background (the operator default) after the rough build returns. It:

  1. rebuilds from <layer>-<ts>.def into a throwaway verify SIF,

  2. captures the rebuild’s lock (a throwaway .verify.lock),

  3. compares against the rough lock,

  4. marks .verified (identical) or .unverified (drift, loud),

  5. deletes the throwaway verify SIF + its scratch dir + the .verify.lock.

Parameters:
  • layer (str) – Layer name.

  • root (str or Path) – The containers/ directory.

  • ts (str) – Timestamp of the rough build to verify.

Returns:

The round-trip comparison. identical is the gate.

Return type:

LockDiff

scitex_container.apptainer

Apptainer container management: build, sandbox, versioning, command building.

class scitex_container.apptainer.ImageConfig(retain=10, require_verified=False)[source]

Bases: object

Resolved reproducible-image config.

Parameters:
  • retain (int)

  • require_verified (bool)

require_verified: bool = False
retain: int = 10
class scitex_container.apptainer.Lock(pip=<factory>, dpkg=<factory>, node='')[source]

Bases: object

Parsed version sets captured from a SIF.

pip / dpkg are {name: version} maps; node is the raw npm list -g --json text (kept verbatim — the JSON shape is the version set for node globals).

Parameters:
dpkg: dict[str, str]
node: str = ''
pip: dict[str, str]
version_set()[source]

Flat {qualified_name: version} map for set comparison.

node packages are flattened from the npm JSON into node:<name> -> <version> entries so a node-global drift is caught by the same comparison as pip/dpkg.

Return type:

dict[str, str]

class scitex_container.apptainer.LockDiff(identical, changed=<factory>, only_in_a=<factory>, only_in_b=<factory>)[source]

Bases: object

Result of comparing two locks’ version sets.

Parameters:
changed: dict[str, tuple[str, str]]
identical: bool
only_in_a: dict[str, str]
only_in_b: dict[str, str]
summary()[source]

One-paragraph human summary of the drift, or ‘identical’.

Return type:

str

class scitex_container.apptainer.RoundTripResult(layer, ts, sif, lock, locked_def, verified, diff=None)[source]

Bases: object

Outcome of a reproducible round-trip build.

Parameters:
diff: LockDiff | None = None
layer: str
lock: Path
locked_def: Path
property marker: Path
sif: Path
ts: str
verified: bool | None
exception scitex_container.apptainer.VerifyError[source]

Bases: RuntimeError

Raised by the use-time gate when an image is unverified under strict mode.

class scitex_container.apptainer.VerifyStatus(state, sif, detail='')[source]

Bases: object

Result of a use-time verify check.

Parameters:
detail: str = ''
property is_verified: bool
sif: Path
state: str
scitex_container.apptainer.build(def_name='scitex-cloud-shared-v0.1.0', output_dir=None, force=False, sandbox=False, *, def_path=None, image_name=None, use_sudo=False, fakeroot=None)[source]

Build Apptainer/Singularity SIF or sandbox from .def file.

Parameters:
  • def_name (str) – Name of the .def file (without extension) when looking it up in the discovered containers dir. Ignored if def_path is given.

  • output_dir (str or Path, optional) – Directory under which the per-image subdir is created (i.e. the artifact lands at <output_dir>/<image_name>/<image_name>.sif). Defaults to the directory containing the resolved .def file.

  • force (bool) – Force rebuild even if .def is unchanged.

  • sandbox (bool) – If True, build a sandbox directory instead of a SIF image. Uses: apptainer build –sandbox –fakeroot output-sandbox/ input.def

  • def_path (str or Path, optional) – Explicit path to the .def file. Lets out-of-tree callers (e.g. scitex-agent-container, whose recipes ship inside its own wheel) bypass find_containers_dir. When given, def_name is ignored for lookup and image_name defaults to the .def’s stem.

  • image_name (str, optional) – Name of the per-image subdir + artifact stem (i.e. the artifact lands at <output_dir>/<image_name>/<image_name>.sif). Defaults to def_name (or the .def stem when def_path is given). Use this when the on-disk recipe is named differently from the image you want to produce — e.g. sac’s apptainer-base.defsac-base/sac-base.sif.

  • use_sudo (bool)

  • fakeroot (bool | None)

Returns:

Path to the built .sif file or sandbox directory.

Return type:

Path

Raises:
scitex_container.apptainer.build_dev_pythonpath(dev_repos)[source]

Build a PYTHONPATH string that prepends /opt/dev/{name}/src for each dev repo.

Packages in dev repos are assumed to follow the src-layout convention (i.e. repo_root/src/<package>), so we add /opt/dev/{name}/src.

Parameters:

dev_repos (list[dict]) – List of dev repo dicts, each with a name key.

Returns:

Colon-separated PYTHONPATH string, or empty string if no repos.

Return type:

str

scitex_container.apptainer.build_exec_args(container_path, username, host_user_dir, host_project_dir, project_slug, dev_repos=None, host_mounts=None, texlive_prefix='')[source]

Build the apptainer exec argument list.

Handles:

  • Sandbox vs SIF detection. Both use --writable-tmpfs for user sessions so each user gets a clean per-session tmpfs overlay.

  • For SIF images, --containall is added to prevent host mounts leaking in.

  • Dev repo bind mounts.

  • PYTHONPATH injection for dev repos (src-layout).

  • Host package bind mounts (TeX Live, etc.).

  • Standard --env, --home, --bind args.

Parameters:
  • container_path (str) – Path to the SIF file or sandbox directory.

  • username (str) – Username for the session (used for home dir and env vars).

  • host_user_dir (Path) – Host path to the user’s home directory.

  • host_project_dir (Path) – Host path to the project directory.

  • project_slug (str) – Project identifier (e.g. “my-project”).

  • dev_repos (list[dict], optional) – Dev repo dicts with name and host_path keys.

  • host_mounts (list[dict], optional) – Generic host mount dicts with host_path, container_path, mode.

  • texlive_prefix (str) – Host prefix for TeX Live (e.g. /usr).

Returns:

Flat list starting with ["apptainer", "exec", ...].

Return type:

list[str]

scitex_container.apptainer.build_host_mount_binds(host_mounts=None, texlive_prefix='')[source]

Build --bind argument pairs for host package mounts.

Parameters:
  • host_mounts (list[dict], optional) – Generic list of {host_path, container_path, mode} dicts.

  • texlive_prefix (str) – Host prefix for TeX Live installation (e.g. /usr). When set, auto-generates bind entries for TeX Live share directories and binaries. Example: /usr generates --bind entries for /usr/share/texlive, /usr/share/texmf-dist, /usr/bin/pdflatex (and all _TEXLIVE_BINS), each :ro.

Returns:

Flat list of alternating "--bind" / "<spec>" strings ready to be inserted into the apptainer argv list.

Return type:

list[str]

scitex_container.apptainer.build_instance_start_script(container_path, username, host_user_dir, host_project_dir, project_slug, instance_name, dev_repos=None, host_mounts=None, texlive_prefix='')[source]

Build a bash script that starts an apptainer instance and keeps it alive.

This script is designed to be submitted via sbatch. It:

  1. Starts an apptainer instance with --writable-tmpfs (shared overlay).

  2. Prints INSTANCE_READY on success or INSTANCE_FAILED on failure.

  3. Sleeps in a loop while the instance is alive (sbatch keeps the allocation open).

Parameters:
  • container_path (str) – Path to the SIF file or sandbox directory.

  • username (str) – Username for the session.

  • host_user_dir (Path) – Host path to the user’s home directory.

  • host_project_dir (Path) – Host path to the project directory.

  • project_slug (str) – Project identifier.

  • instance_name (str) – Name for the apptainer instance (e.g. scitex-user-project).

  • dev_repos (list[dict], optional) – Dev repo dicts with name and host_path keys.

  • host_mounts (list[dict], optional) – Generic host mount dicts.

  • texlive_prefix (str) – Host prefix for TeX Live.

Returns:

Complete bash script content.

Return type:

str

scitex_container.apptainer.build_reproducible(layer, root, *, def_path=None, def_name=None, verify=True, keep=False, config=None, force=False)[source]

Run the reproducible round-trip and manage the artifact store.

Steps 1-3 (rough build, freeze lock, generate locked def) always run synchronously — the rough SIF + lock + locked def are the kept, immediately-usable artifacts. Steps 4-5 (verify rebuild + compare) run inline when verify=True.

The operator design specifies steps 4-5 run BACKGROUND-by-default in the CLI; this function exposes the synchronous primitive that a caller (CLI/MCP) backgrounds. verify=False skips them entirely (leaving the build unmarked) so a caller can schedule the verify rebuild as a detached job and call verify_roundtrip later.

Parameters:
  • layer (str) – Layer name (artifact stem, e.g. sac-base).

  • root (str or Path) – The containers/ directory (path injection).

  • def_path (str or Path, optional) – Explicit path to the rough .def. Either this or def_name.

  • def_name (str, optional) – Name of the .def to look up via find_containers_dir.

  • verify (bool) – Run steps 4-5 inline. False = skip (build stays unmarked).

  • keep (bool) – Write the .keep prune-protect marker on the build.

  • config (ImageConfig, optional) – Resolved config (retain). Loaded from root when None.

  • force (bool) – Force the rough rebuild even when the recipe hash is unchanged.

Returns:

The kept artifact paths + verify outcome.

Return type:

RoundTripResult

scitex_container.apptainer.build_sbatch_command(instance_name, script_path, slurm_partition='compute', slurm_time_limit='8:00:00', slurm_cpus=4, slurm_memory_gb=16, username='', project_slug='')[source]

Build sbatch command to submit an allocation script.

Parameters:
  • instance_name (str) – Used to derive the SLURM job name.

  • script_path (str) – Path to the bash script (from build_instance_start_script).

  • slurm_partition (str) – SLURM partition name.

  • slurm_time_limit (str) – SLURM time limit (e.g. “8:00:00”).

  • slurm_cpus (int) – Number of CPUs per task.

  • slurm_memory_gb (int) – Memory in GB.

  • username (str) – Username (for job name).

  • project_slug (str) – Project slug (for job name).

Returns:

Command list ready for subprocess.run().

Return type:

list[str]

scitex_container.apptainer.build_shell_in_allocation_command(job_id, instance_name, username='')[source]

Build srun --overlap command to attach a shell inside an existing allocation.

Parameters:
  • job_id (str) – SLURM job ID of the running allocation.

  • instance_name (str) – Name of the apptainer instance to exec into.

  • username (str) – Username for the shell session (used for user identity setup).

Returns:

Command list ready for os.execvpe or pty.fork.

Return type:

list[str]

scitex_container.apptainer.build_srun_command(container_path, username, host_user_dir, host_project_dir, project_slug, dev_repos=None, host_mounts=None, texlive_prefix='', slurm_partition='compute', slurm_time_limit='8:00:00', slurm_cpus=4, slurm_memory_gb=16, screen_session='scitex-0')[source]

Build the complete srun + apptainer command list.

Combines the SLURM resource flags with the apptainer exec arguments produced by build_exec_args(), launching a login bash shell directly (no screen).

Parameters:
  • container_path (str) – Path to the SIF file or sandbox directory.

  • username (str) – Username for the session.

  • host_user_dir (Path) – Host path to the user’s home directory.

  • host_project_dir (Path) – Host path to the project directory.

  • project_slug (str) – Project identifier.

  • dev_repos (list[dict], optional) – Dev repo dicts with name and host_path keys.

  • host_mounts (list[dict], optional) – Generic host mount dicts.

  • texlive_prefix (str) – Host prefix for TeX Live.

  • slurm_partition (str) – SLURM partition name.

  • slurm_time_limit (str) – SLURM time limit (e.g. “8:00:00”).

  • slurm_cpus (int) – Number of CPUs per task.

  • slurm_memory_gb (int) – Memory in GB.

  • screen_session (str) – Deprecated — ignored. Kept for backward compatibility.

Returns:

Flat list ready to be passed to os.execvpe or subprocess.Popen.

Return type:

list[str]

scitex_container.apptainer.capture_lock(sif_path, lock_path)[source]

Introspect a built SIF and write a combined .lock file.

Parameters:
  • sif_path (str or Path) – Path to the built .sif.

  • lock_path (str or Path) – Destination for the combined lock file (<layer>-<ts>.lock).

Returns:

The captured version sets.

Return type:

Lock

Raises:

FileNotFoundError – If the SIF or the container command is missing.

scitex_container.apptainer.check_verified(sif_path, *, require_verified=None, root=None, config=None)[source]

Check a built image’s reproducibility marker — NOISY on every use.

The use-time gate consumers call on every image use. Looks beside the SIF for the .verified / .unverified marker (resolving a latest symlink first):

  • .verified present → state="verified" (silent OK).

  • .unverified present → WARN by default (“reproducibility unverified: <drift>”); under require_verified → raise VerifyError.

  • no marker → state="unknown" → WARN it’s unverified; under require_verified → raise.

Parameters:
  • sif_path (str or Path) – Path to the image being used (may be the latest symlink).

  • require_verified (bool, optional) – Strict mode. When None, resolved from config / load_config(root) (images.require_verified).

  • root (str or Path, optional) – Output root for config resolution (when require_verified and config are both None).

  • config (ImageConfig, optional) – Pre-resolved config.

Returns:

The marker state + detail.

Return type:

VerifyStatus

Raises:

VerifyError – When the image is not verified and strict mode is on.

scitex_container.apptainer.cleanup(containers_dir, keep=3)[source]

Remove old scitex-v*.sif files, keeping the N most recent.

Never removes the active version (current.sif target) or any scitex-base-v*.sif base images.

Parameters:
  • containers_dir (Path) – Directory containing SIF files.

  • keep (int) – Number of most-recent versioned SIFs to keep (default: 3).

Returns:

Paths of removed SIF files.

Return type:

list[Path]

scitex_container.apptainer.cleanup_sandboxes(containers_dir, keep=5)[source]

Remove old sandbox directories, keeping the N most recent.

Never removes the active sandbox (current-sandbox symlink target).

Parameters:
  • containers_dir (Path) – Directory containing sandbox directories.

  • keep (int) – Number of most-recent sandboxes to keep (default: 5).

Returns:

Paths of removed sandbox directories.

Return type:

list[Path]

scitex_container.apptainer.cleanup_sifs(containers_dir, keep=0)[source]

Remove SIF files and related artifacts.

Removes *.sif, *.sif.old, *.sif.backup.* files and the current.sif symlink.

Parameters:
  • containers_dir (Path) – Directory containing SIF files.

  • keep (int) – Number of most-recent versioned SIFs to keep (default: 0).

Returns:

Paths of removed files.

Return type:

list[Path]

scitex_container.apptainer.compare_locks(lock_a, lock_b)[source]

Compare two locks’ flat version sets — the round-trip gate.

Parameters:
  • lock_a (Lock) – The rough-build lock and the rebuild lock.

  • lock_b (Lock) – The rough-build lock and the rebuild lock.

Returns:

identical=True when every package name maps to the same version in both; otherwise the structured diff names every changed / added / missing package.

Return type:

LockDiff

scitex_container.apptainer.deploy(source_dir, target_dir=PosixPath('/opt/scitex/singularity'))[source]

Copy active SIF and base SIF to target directory.

Copies the currently active scitex-v*.sif and the latest scitex-base-v*.sif to target_dir, then recreates the current.sif symlink there. Uses sudo for the copy.

Parameters:
  • source_dir (Path) – Directory containing the built SIF files.

  • target_dir (Path) – Deployment target directory (default: /opt/scitex/singularity).

Raises:
Return type:

None

scitex_container.apptainer.detect_container_cmd()[source]

Detect apptainer or singularity command.

Returns:

The container command name (‘apptainer’ or ‘singularity’).

Return type:

str

Raises:

FileNotFoundError – If neither command is found.

scitex_container.apptainer.find_containers_dir()[source]

Find the containers directory.

Search order: 1. ./containers/ (current working directory) 2. Package-relative containers/ (scitex-container source tree) 3. ~/.scitex/containers/ (user-managed)

Returns:

Path to the containers directory.

Return type:

Path

Raises:

FileNotFoundError – If no containers directory is found.

scitex_container.apptainer.freeze(sif_path, output_dir=None)[source]

Extract pinned versions from a built SIF.

Parameters:
  • sif_path (str or Path) – Path to the .sif file.

  • output_dir (str or Path, optional) – Directory for lock files. Defaults to same dir as .sif.

Returns:

Mapping of lock file type to path: {pip, dpkg, node}.

Return type:

dict[str, Path]

Raises:

FileNotFoundError – If SIF file or container command not found.

scitex_container.apptainer.generate_locked_def(rough_def, lock, out_def)[source]

Emit a version-pinned .def from a rough .def + a lock.

The locked def is the rough def with a pinned-pip %post stanza appended: pip install pkg==ver ... for every frozen pip package. Re-installing already-installed packages at their exact versions is a no-op in the happy path and forces the pin in the drift path — so a rebuild from this def reproduces the captured pip version set.

apt/node version sets are recorded in the lock and compared by the round-trip, but apt-version injection into the def is deferred (operator step 3 — pkg=ver from dpkg or snapshot.ubuntu.com).

Parameters:
  • rough_def (str or Path) – The loosely-pinned recipe the rough build used.

  • lock (Lock) – Captured versions from the rough build.

  • out_def (str or Path) – Destination for the generated locked .def.

Returns:

Path to the written locked .def.

Return type:

Path

scitex_container.apptainer.get_active_sandbox(containers_dir)[source]

Read current-sandbox symlink to determine active sandbox version.

Parameters:

containers_dir (Path) – Directory containing the current-sandbox symlink.

Returns:

Timestamp string of the active sandbox, or None if no symlink.

Return type:

str or None

scitex_container.apptainer.get_active_version(containers_dir)[source]

Read current.sif symlink to determine active version.

Parameters:

containers_dir (Path) – Directory containing the current.sif symlink.

Returns:

Version string of the active SIF, or None if no symlink exists or it does not point to a valid versioned SIF.

Return type:

str or None

scitex_container.apptainer.is_sandbox(path)[source]

Check if path is a sandbox directory (not a SIF image).

A path ending in .sif is treated as a SIF image; anything else (including bare directory names or paths ending in -sandbox) is treated as a sandbox directory.

Parameters:

path (str or Path) – Path to check.

Returns:

True if path is a sandbox directory, False if it is a SIF image.

Return type:

bool

scitex_container.apptainer.list_builds(root, layer)[source]

List the timestamped builds of layer under root.

Parameters:
  • root (str or Path) – The containers/ directory.

  • layer (str) – Layer name (e.g. sac-base).

Returns:

One dict per build with keys ts, sif, lock, def, verified (bool|None — None when no marker yet), keep (bool), active (bool — the current latest symlink target). Sorted newest ts first.

Return type:

list[dict]

scitex_container.apptainer.list_sandboxes(containers_dir)[source]

List all versioned sandbox directories with metadata.

Parameters:

containers_dir (Path) – Directory containing sandbox directories.

Returns:

Each dict has keys: version, path, date, active. Sorted by modification time (newest first).

Return type:

list[dict]

scitex_container.apptainer.list_versions(containers_dir)[source]

List all versioned SIFs with metadata.

Parameters:

containers_dir (Path) – Directory containing SIF files.

Returns:

Each dict contains keys: version, path, size, date, active. Sorted by modification time (newest first).

Return type:

list[dict]

scitex_container.apptainer.load_config(root=None)[source]

Load the reproducible-image config.

Resolution order (highest priority first):

  1. Project-scope override (<project>/.scitex/container/config.yaml).

  2. The <root>/config.yaml file (user-scope by default).

  3. Built-in defaults.

Always returns a valid ImageConfig — a missing or unreadable config falls back to defaults rather than raising, because the config is an optional tuning knob, not a build precondition.

Parameters:

root (str or Path, optional) – Output base directory. None → standalone default ~/.scitex/container/.

Returns:

The resolved config.

Return type:

ImageConfig

scitex_container.apptainer.mark_unverified(root, layer, ts, reason='')[source]

Write the .unverified marker for a build (clears .verified).

The marker body carries the drift reason so a later use-time check can surface what drifted, not just that it drifted.

Parameters:
Return type:

Path

scitex_container.apptainer.mark_verified(root, layer, ts)[source]

Write the .verified marker for a build (clears .unverified).

Parameters:
Return type:

Path

scitex_container.apptainer.point_latest(root, layer, ts)[source]

Point the layer latest symlink at the (layer, ts) build.

Writes <root>/<layer>.sif -> <layer>/<layer>-<ts>.sif (a relative target so the store stays relocatable). Atomically replaces any existing symlink/file.

Returns:

The symlink path.

Return type:

Path

Parameters:
scitex_container.apptainer.protect(root, layer, ts)[source]

Write the .keep prune-protect marker for a build.

Parameters:
Return type:

Path

scitex_container.apptainer.prune(root, layer, retain)[source]

Prune old builds of layer, keeping the retain most recent.

Never removes: - the retain most-recent builds (by <ts>), - the active build (current latest symlink target), - any build carrying a .keep marker.

Parameters:
  • root (str or Path) – The containers/ directory.

  • layer (str) – Layer name.

  • retain (int) – Number of most-recent builds to keep.

Returns:

The <ts> values that were pruned.

Return type:

list[str]

scitex_container.apptainer.read_lock(lock_path)[source]

Parse a combined .lock file back into a Lock.

Parameters:

lock_path (str | Path)

Return type:

Lock

scitex_container.apptainer.rollback(containers_dir, use_sudo=False)[source]

Switch to the version before the current one (by modification time).

Parameters:
  • containers_dir (Path) – Directory containing SIF files.

  • use_sudo (bool) – If True, run symlink commands via sudo.

Returns:

Version string that is now active after rollback.

Return type:

str

Raises:

RuntimeError – If there is no current version or no previous version to roll back to.

scitex_container.apptainer.rollback_sandbox(containers_dir, use_sudo=False)[source]

Switch to the sandbox before the current one (by modification time).

Parameters:
  • containers_dir (Path) – Directory containing sandbox directories.

  • use_sudo (bool) – If True, run symlink commands via sudo.

Returns:

Timestamp string of the now-active sandbox.

Return type:

str

Raises:

RuntimeError – If there is no current sandbox or no previous one to roll back to.

scitex_container.apptainer.sandbox_configure_ps1(sandbox_dir, default_ps1='\\\\W $ ')

Set PS1 prompt in a sandbox’s environment script.

Writes a shell snippet that reads SCITEX_CLOUD_APPTAINER_PS1 at runtime, falling back to default_ps1. Users override by passing --env SCITEX_CLOUD_APPTAINER_PS1='(mylab) \\W $ ' to apptainer.

Apptainer’s 99-base.sh defaults to PS1="Apptainer> " only when PS1 is unset. Setting PS1 in 90-environment.sh (the %environment section) runs first and prevents that.

Parameters:
  • sandbox_dir (str or Path) – Path to the sandbox directory.

  • default_ps1 (str) – Default PS1 when SCITEX_CLOUD_APPTAINER_PS1 is not set.

Return type:

None

scitex_container.apptainer.sandbox_create(source, containers_dir=None, *, output_dir=None)

Build a sandbox directory from a SIF image or .def file.

Creates a timestamped sandbox (sandbox-YYYYMMDD_HHMMSS/) and updates the current-sandbox symlink to point to it.

Parameters:
  • source (str or Path) – Path to the source .sif file or .def file.

  • containers_dir (str or Path, optional) – Parent directory for sandbox output and symlink. Defaults to source file’s parent directory.

  • output_dir (str or Path, optional) – Explicit output path (overrides timestamped naming).

Returns:

Path to the created sandbox directory.

Return type:

Path

Raises:
scitex_container.apptainer.sandbox_maintain(sandbox_dir, command)

Run a command inside a sandbox with –writable –fakeroot flags.

Intended for admin maintenance tasks (installing packages, etc.). For user sessions, use –writable-tmpfs instead.

Parameters:
  • sandbox_dir (str or Path) – Path to the sandbox directory.

  • command (list[str]) – Command to execute inside the sandbox.

Returns:

Return code of the executed command.

Return type:

int

Raises:

FileNotFoundError – If the sandbox directory does not exist or apptainer is not found.

scitex_container.apptainer.sandbox_to_sif(sandbox_dir, output_sif)

Convert a sandbox directory back to a SIF image.

Parameters:
  • sandbox_dir (str or Path) – Path to the source sandbox directory.

  • output_sif (str or Path) – Path for the output .sif file.

Returns:

Path to the created .sif file.

Return type:

Path

Raises:
scitex_container.apptainer.sandbox_update(sandbox_dir, *, proj_root=None, packages=None, install_deps=False)

Incrementally update ecosystem packages inside an existing sandbox.

Runs pip install for each package from local repos, avoiding a full sandbox rebuild. Ideal for active development.

Parameters:
  • sandbox_dir (str or Path) – Path to the sandbox directory (or current-sandbox symlink).

  • proj_root (str or Path, optional) – Directory containing all ecosystem repos. Defaults to ~/proj.

  • packages (tuple[str, ...], optional) – Package names to install. Defaults to all ecosystem packages.

  • install_deps (bool) – If True, install dependencies too. If False (default), uses --no-deps for faster installs.

Returns:

Mapping of package name to result: “ok”, “failed”, or “skipped”.

Return type:

dict[str, str]

scitex_container.apptainer.status(containers_dir=None)[source]

List available containers and their status.

Parameters:

containers_dir (str or Path, optional) – Directory containing .def files. Auto-detected if not given.

Returns:

List of container info dicts with keys: name, def_path, sif_path, sif_size, sif_date, hash_current, hash_stored, needs_rebuild.

Return type:

list[dict]

scitex_container.apptainer.switch_sandbox(version, containers_dir, use_sudo=False)[source]

Atomically switch current-sandbox symlink to sandbox-{version}/.

Uses ln -sfn to create a temporary symlink, then mv -Tf for an atomic rename on the same filesystem.

Parameters:
  • version (str) – Target timestamp string (e.g. “20260225_173700”).

  • containers_dir (Path) – Directory containing sandbox directories.

  • use_sudo (bool) – If True, run ln/mv via sudo.

Raises:
Return type:

None

scitex_container.apptainer.switch_version(version, containers_dir, use_sudo=False)[source]

Atomically switch current.sif symlink to scitex-v{version}.sif.

Uses ln -sf to create a temporary symlink, then mv -Tf for an atomic rename on the same filesystem.

Parameters:
  • version (str) – Target version string (e.g. “2.19.5”).

  • containers_dir (Path) – Directory containing SIF files.

  • use_sudo (bool) – If True, run ln/mv via sudo (needed for /opt/scitex paths).

Raises:
Return type:

None

scitex_container.apptainer.verify(sif_path, def_path=None, lock_dir=None)[source]

Verify container integrity.

Checks:

  1. SIF exists and computes its SHA256

  2. If def_path given, compares .def hash against stored .def-hash

  3. If lock files exist, runs pip freeze / dpkg-query inside the SIF and compares against stored lock files

Parameters:
  • sif_path (str or Path) – Path to the .sif file to verify.

  • def_path (str or Path, optional) – Path to the .def file that should have produced this SIF.

  • lock_dir (str or Path, optional) – Directory containing lock files (requirements-lock.txt, dpkg-lock.txt). Defaults to same directory as the SIF.

Returns:

Verification results:

{
    "sif": {"path": "...", "sha256": "...", "exists": True},
    "def_origin": {"status": "pass|fail|skip", "detail": "..."},
    "pip_lock": {"status": "pass|fail|skip", "detail": "...", "diff_count": 0},
    "dpkg_lock": {"status": "pass|fail|skip", "detail": "...", "diff_count": 0},
    "overall": "pass|fail"
}

Return type:

dict

scitex_container.apptainer.verify_roundtrip(layer, root, ts)[source]

Rebuild from the locked def, compare version sets, mark the build.

This is steps 4-5 split out so a caller can run it in the background (the operator default) after the rough build returns. It:

  1. rebuilds from <layer>-<ts>.def into a throwaway verify SIF,

  2. captures the rebuild’s lock (a throwaway .verify.lock),

  3. compares against the rough lock,

  4. marks .verified (identical) or .unverified (drift, loud),

  5. deletes the throwaway verify SIF + its scratch dir + the .verify.lock.

Parameters:
  • layer (str) – Layer name.

  • root (str or Path) – The containers/ directory.

  • ts (str) – Timestamp of the rough build to verify.

Returns:

The round-trip comparison. identical is the gate.

Return type:

LockDiff

scitex_container.apptainer.write_lock(lock, lock_path)[source]

Serialize a Lock to the combined .lock text format.

Parameters:
Return type:

Path

scitex_container.docker

Docker container management — rebuild, restart, status, and mount helpers.

scitex_container.docker.get_dev_mounts(repos)[source]

Generate Docker volume mount strings for development repositories.

Each entry in repos should have at minimum a "host" key with the host-side path, and a "container" key with the container-side path. An optional "mode" key ("ro" or "rw") defaults to "ro".

Parameters:

repos (list[dict]) –

Repository mount specifications:

[
    {"host": "../../scitex-python", "container": "/scitex-python"},
    {"host": "/abs/path/to/myrepo", "container": "/myrepo", "mode": "rw"},
]

"host" paths may be absolute or relative; relative paths are returned as-is so that Docker Compose can resolve them relative to the compose file location.

Returns:

Volume strings suitable for use in a Docker Compose volumes list or as -v arguments to docker run:

[
    "./../../scitex-python:/scitex-python:ro",
    "/abs/path/to/myrepo:/myrepo:rw",
]

Return type:

list[str]

scitex_container.docker.rebuild(env='dev', project_dir=None)[source]

Rebuild Docker containers without using the cache.

Runs: docker compose -f <compose_file> build --no-cache

Parameters:
  • env (str) – Environment name used to locate the compose file.

  • project_dir (str or Path or None) – Explicit directory containing the compose file. When None, the function walks upward from the current working directory.

Returns:

Exit code of the docker compose command (0 = success).

Return type:

int

scitex_container.docker.restart(env='dev', project_dir=None)[source]

Restart Docker containers (down then up).

Runs: docker compose -f <compose_file> down then: docker compose -f <compose_file> up -d

Parameters:
  • env (str) – Environment name used to locate the compose file.

  • project_dir (str or Path or None) – Explicit directory containing the compose file.

Returns:

Exit code of the final up -d command (0 = success). If down fails its exit code is returned instead.

Return type:

int

scitex_container.docker.status(env='dev', project_dir=None)[source]

Get Docker container status for the given compose environment.

Runs: docker compose -f <compose_file> ps --format json

Parameters:
  • env (str) – Environment name used to locate the compose file.

  • project_dir (str or Path or None) – Explicit directory containing the compose file.

Returns:

Status information:

{
    "compose_file": "/path/to/docker-compose.yml",
    "containers": [
        {
            "name": "myapp_web_1",
            "state": "running",
            "image": "myapp:latest",
            "raw": {...},   # original JSON from docker compose ps
        },
        ...
    ],
    "returncode": 0,
}

Return type:

dict

scitex_container.host

Host package management — install, verify, and mount host-level tools.

scitex_container.host.check_packages()[source]

Check which host packages are installed.

Returns:

Structured status per package group:

{
    "texlive": {
        "installed": True,
        "version": "pdfTeX 3.141592653...",
        "binaries": ["pdflatex", "bibtex", ...],
    },
    "imagemagick": {
        "installed": True,
        "version": "Version: ImageMagick 6.9...",
        "binaries": ["convert", "identify"],
    },
}

Return type:

dict

scitex_container.host.get_mount_config(texlive_prefix='', host_mounts_raw='')[source]

Parse mount configuration and return structured bind mount info.

Parameters:
  • texlive_prefix (str) – Installation prefix for TeXLive (e.g. /usr). When empty, defaults to /usr if TeXLive binaries are found there, otherwise skips TeXLive mounts.

  • host_mounts_raw (str) – Colon-separated raw bind specs in host:container or host:container:mode format, separated by commas or newlines. Example: "/data:/data:ro,/scratch:/scratch"

Returns:

Structured mount information:

{
    "bind_args": ["--bind", "/usr/share/texlive:/usr/share/texlive:ro", ...],
    "path_additions": ["/usr/bin"],
    "mounts": [{"host": "...", "container": "...", "mode": "ro"}, ...],
}

Return type:

dict

scitex_container.host.get_texlive_binds(prefix='/usr')[source]

Generate bind mount entries for TeXLive.

Parameters:

prefix (str) – Installation prefix (typically /usr for system-wide apt installs).

Returns:

Each entry has keys host, container, and mode:

[
    {"host": "/usr/share/texlive", "container": "/usr/share/texlive", "mode": "ro"},
    {"host": "/usr/bin/pdflatex",  "container": "/usr/bin/pdflatex",  "mode": "ro"},
    ...
]

Return type:

list[dict]

scitex_container.host.install_packages(texlive=False, imagemagick=False, all=False, check_only=False)[source]

Install host packages by calling the shell script.

Parameters:
  • texlive (bool) – Install TeXLive packages.

  • imagemagick (bool) – Install ImageMagick.

  • all (bool) – Install all packages (overrides texlive/imagemagick flags).

  • check_only (bool) – Run the script in –check mode without installing anything.

Returns:

Status per package group:

{
    "texlive": {"status": "installed", "returncode": 0},
    "imagemagick": {"status": "skipped", "returncode": None},
    "script": "/abs/path/to/install-host-packages.sh",
}

Return type:

dict

Raises:

FileNotFoundError – If the install script cannot be found.