Distribution and installed CLI
This page describes the supported source and container installation paths and
the remaining boundary before lidarslam_ros2 can publish ROS buildfarm
packages.
Supported source install
Humble on Ubuntu 22.04 and Jazzy on Ubuntu 24.04 are the supported source-build
targets. Install the matching ROS distribution first. The source quickstart
selects its /opt/ros setup, prepares pinned submodules and repository-only
dependencies, verifies the exact six-package source inventory, builds only that
explicit package list, then runs the verified demo. It may ask for your sudo
password. Inventory drift fails before rosdep or compilation rather than
silently adding an experimental package to the beginner path.
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
git clone --recursive https://github.com/rsasaki0109/lidar_slam_ros2.git
cd lidar_slam_ros2
bash scripts/source_quickstart.sh
Use --dry-run for a write- and network-free plan, or add --json for the
versioned source-quickstart-plan-v1 schema.
The JSON form is valid only with --dry-run, writes stdout only, and reports
the exact command arrays without network, APT, submodule checkout, build, demo,
or filesystem writes. Use --build-only to skip the 517 MB demo, or
--viewer none on a headless host. The installation build skips
test-target generation and does not build unrelated workspace packages. After
it completes, use the absolute installed lidarslam-map path printed by the
helper from any directory, including a fresh terminal. That direct launcher
auto-activates the matching aggregate setup.bash for its own process and
children; it does not modify the caller's shell. Use
source ~/ros2_ws/install/setup.bash when you want the short command name and
the rest of the workspace's ROS tools. Use the full build and checks in
Operator Workflows when developing or validating a change.
The local direct-launcher evidence
passes this fresh-terminal behavior on both Humble and Jazzy while keeping the
complete cold-source and package-manager gates separate.
The installed own-bag command is then available from any working directory:
lidarslam-map doctor # install/readiness check
lidarslam-map demo "$PWD" # fixed public first map
lidarslam-map start /path/to/rosbag2 --editable
lidarslam-map doctor /path/to/rosbag2
lidarslam-map setup /path/to/rosbag2
lidarslam-map run /path/to/rosbag2 --output-dir "$PWD/output/my_map" --editable
lidarslam-map inspect "$PWD/output/my_map"
lidarslam-map view "$PWD/output/my_map" # offline 3D preview
lidarslam-map edit "$PWD/output/my_map" --plan plan.json --output-dir "$PWD/output/my_map_edited"
lidarslam-map merge "$PWD/output/day1" "$PWD/output/day2" --output-dir "$PWD/output/site_project"
lidarslam-map view "$PWD/output/my_map" --viewer foxglove # optional live viewer
Run doctor without a bag immediately after installation to verify the
curated helper set, matching prefix, supported ROS environment, bag reader, and
fixed-demo storage without network access or writes. Add a rosbag2 directory to
the same command when you are ready to inspect sensor compatibility.
The published container exposes the same installed CLI. For a Linux own-bag run without a ROS workspace, use the repository's host launcher:
bash scripts/docker_map_bag.sh /absolute/path/to/rosbag2
It mounts the whole bag read-only, disables external container networking,
runs as the host UID/GID, keeps every write under one new host output
directory, and invokes lidarslam-map start rather than a separate container
workflow. Before creating output, it requires the selected image to expose
start --help and binds the run to the resulting local immutable image ID.
--dry-run prints the exact expansion
without Docker, network, or writes; --ros-distro jazzy selects the Jazzy
image, and --image <tag-or-digest> pins an explicit image. Add --json to
--dry-run for the versioned docker-map-bag-plan-v1 stdout contract; it also
performs no Docker call, network access, filesystem write, or output-directory
creation. The JSON contains local paths and leaves image identity/contract
preflight deferred until a live run. Sensor setup options follow --. The
helper is source/release-bundle delivery tooling, not an additional installed
CLI command.
Standalone launcher asset for the next release
The release workflow now prepares lidarslam-map-docker as a direct,
attested asset beginning with v0.9.1. The builder replaces development markers
with the exact release tag and 40-character source revision, validates shell
syntax, and refuses to overwrite an existing output. A release-built launcher
defaults to ghcr.io/rsasaki0109/lidar_slam_ros2:v<VERSION>-<distro> rather
than a moving image tag. The published-release audit downloads but never
executes the remote shell payload; it checks its bounded UTF-8 form, exact
release identity, version-pinned image behavior, read-only input, network
isolation, image-capability preflight, and required-result gate.
The historical v0.9.0 release remains valid with its original six recovery assets. Releases from v0.9.1 require the seventh launcher asset, and the release job attests it together with the deterministic release bundle. The asset is not available until a future finalized release actually publishes and passes that seven-asset audit; see Getting Started for the guarded download route.
Enable command and option completion in Bash:
source "$(ros2 pkg prefix lidarslam)/share/lidarslam/product/completions/lidarslam-map.bash"
Use an absolute output path when it matters where artifacts are written. An installed CLI defaults relative output to the current working directory; it never writes into the read-only package share.
The launcher enforces that boundary for Python as well: delegated processes
run with bytecode writes disabled, package installation excludes development
__pycache__/.pyc/.pyo artifacts, and the installed-product gate compares
Python cache state across the complete prefix before and after its workflows.
The dual-distro all-six-source check is recorded in
the local installation evidence.
Names and compatibility
The package has historically installed a C++ ROS node named lidarslam. That
name remains unchanged:
ros2 run lidarslam lidarslam
The product CLI deliberately uses two non-conflicting installed spellings:
lidarslam-map --help
ros2 run lidarslam lidarslam-cli --help
Both spellings dispatch the same demo, start, doctor, setup, run
(including --guided),
inspect, view, non-destructive edit, and multi-session merge contract.
The ros2 run form is a compatibility shim, not a separate product workflow.
Inside a source checkout,
./scripts/lidarslam exposes the same contract.
Calling the absolute installed lidarslam-map path directly finds the curated
CLI resource in that prefix and activates its nearest aggregate setup.bash
before delegation. The repo-local wrapper does the same only when the candidate
workspace contains a matching installed CLI resource. It never scans arbitrary
locations or sources an unrelated parent workspace. This setup is private to
the command process; normal shell activation remains explicit.
What the installation contains
The lidarslam package installs a curated runtime set:
- launch files, parameter presets, and RViz configuration;
- the historical C++ node;
lidarslam-mapand thelidarslam-cliROS shim;- the product runner, preflight, diagnosis, verification, conversion, and viewer helpers required transitively by maintained profiles;
- the repository product version.
Research trainers, sweep tools, generated benchmark output, and repository media are not copied into the product-script directory.
Every Humble/Jazzy default CI job creates a fresh, non-symlinked install prefix
and checks all curated resources from an unrelated working directory. The gate
also runs --version, a public-demo dry-run, doctor, an own-bag dry run,
inspect, and the non-launching view validation path. It validates session history, comparison,
and a privacy-bounded support ZIP from an installed session, and confirms that
ros2 run lidarslam lidarslam was not replaced.
The Docker image is likewise built without --symlink-install and verifies
lidarslam-map --version before its build tree is removed.
Source install upgrade contract
The separate install-upgrade gate starts with the immutable v0.6.0
lidarslam package, installs the candidate into that same non-symlinked merge
prefix, and compares it with a fresh candidate prefix:
source /opt/ros/$ROS_DISTRO/setup.bash
python3 scripts/check_install_upgrade.py \
--baseline-ref v0.6.0 \
--evidence-dir output/install-upgrade/$ROS_DISTRO \
--hardware-label my-amd64-$ROS_DISTRO-host
The gate rejects stale or missing package-owned paths, executable-bit drift, and changed text resources after normalizing the install prefix. It then runs the complete installed-CLI checker against upgraded and fresh prefixes, including the historical ROS node. Binary content hashes are not compared because independent build directories can change compiler build IDs.
The named Humble/Jazzy execution and machine-readable reports are in clean-prefix upgrade evidence. This covers source-built prefix upgrades; Debian/ROS buildfarm package-manager upgrades remain a separate release boundary.
ROS apt install and upgrade gate
The manual package-manager install and upgrade workflow exercises that
separate boundary on clean Humble and Jazzy containers. It installs the exact
four product Debian versions, then verifies:
- all four packages have the requested upstream version;
ndt_omp_ros2 >= 0.1.0andrko_lio >= 0.3.2resolved from ROS apt;- every required command, runtime resource, schema, completion file, and
compatibility shim passes the installed CLI contract under
/opt/ros/<distro>; - an in-place upgrade starts from a passing older main-channel installation, confirms its installed CLI identity, increases the product version, and leaves no path whose package ownership was removed;
- the package-manager-installed CLI produces and validates the pinned public MID-360 map under the same bounded real-data contract as the other distribution gates.
The read-only verifier is
scripts/check_package_manager_install.py; its report follows
package-manager-install-v1.schema.json.
It queries dpkg, package ownership, and the installed prefix, but never runs
apt. Package installation is isolated to the disposable workflow container.
The workflow is manual while no lidarslam buildfarm package exists, so normal CI does not create a permanently failing network gate. Run clean-install against ROS testing as soon as the candidate builds. While the older release is still in main, run the main-to-testing upgrade before the next sync removes that two-version window. Finally rerun clean-install against main after sync. Only the last two named-distro artifacts together prove the normal apt path; the implemented workflow alone is not evidence that unpublished packages exist. Each dispatch exposes its exact source ref, product version, apt channel, and mode in the immutable workflow run name. After the main-channel run completes, verify its public identity and both matrix jobs without installing packages locally:
python3 scripts/check_package_manager_release_readiness.py \
--version "$(tr -d '\n' < VERSION)" \
--require-ready
The audit uses an explicit GITHUB_TOKEN when provided, otherwise it
non-interactively reuses the active gh auth credential. If neither is
available, it keeps the anonymous read path and fails closed when that quota
is insufficient. The credential is sent only to the exact
https://api.github.com origin, the audit never writes to GitHub, and the
credential is never included in JSON output.
The resulting JSON contract is
package-manager-release-readiness-v1.schema.json.
The audit first resolves the exact v<VERSION> ref, including annotated tags,
to its public commit and requires the workflow head_sha to match that commit.
It inspects successful, failed, and still-running dispatches instead of hiding
non-successful attempts. A missing immutable source ref reports
SOURCE_REF_MISSING; no matching attempt reports NOT_RUN; an active attempt
reports RUNNING; and a completed unsuccessful matrix reports FAILED.
GitHub API failures or untrusted run identity report BLOCKED. Only READY
can satisfy live v1 readiness, and the checker never creates the missing tag or
dispatches the workflow.
Before dispatching the expensive real-data workflow, inspect the two binary dependencies in disposable Humble and Jazzy containers:
python3 scripts/check_ros_apt_dependency_readiness.py --require testing
The checker reads both public main and testing channels, requires
ndt_omp_ros2 >= 0.1.0 and rko_lio >= 0.3.2 in both named distributions,
and writes a
ros-apt-dependency-readiness-v1
report. IN_PROGRESS means at least one testing binary is not ready;
TESTING_READY authorizes the testing-channel package-manager E2E;
MAIN_READY means the dependency half of the normal-channel gate has synced.
Docker, network, apt, or schema failures are BLOCKED, never
not-published. This preflight does not replace the installed product E2E:
it prevents a workflow dispatch that is guaranteed to fail before installation.
The package-manager release audit additionally refuses to print a dispatch
command until the exact public source tag resolves. When both prerequisites
pass, its action contains the complete gh workflow run command with source
ref, product version, channel, and mode rather than requiring operators to
reconstruct those inputs.
The
2026-08-12 public-channel snapshot
records the current asymmetric state: Humble RKO-LIO 0.3.2 is ready in both
main and testing; Jazzy main still exposes 0.2.0 while testing exposes 0.3.2;
and ndt_omp_ros2 remains unpublished in both channels for both distributions.
Installed source identity
Every official install includes
share/lidarslam/product/product-build-info.json. The map runner uses this
file to populate software.git_commit and software.git_dirty in
run_manifest.json, even when the installed command runs outside its source
checkout.
A normal Git clone records its current 40-character commit and tracked dirty state at build time. Untracked files are excluded, matching the runtime manifest policy. The file contains no build timestamp or host path, so two builds with the same source identity produce identical metadata.
Docker excludes .git from its build context. The Humble/Jazzy image
workflows therefore pass the checked-out revision and dirty=false explicitly
and reject an image whose installed revision differs from the checkout.
Packagers building from a Git-free source archive must provide the same
identity:
colcon build --cmake-args \
-DCMAKE_BUILD_TYPE=Release \
-DLIDARSLAM_SOURCE_REVISION:STRING=<40-character-commit> \
-DLIDARSLAM_SOURCE_DIRTY:STRING=false
Without Git metadata or an explicit revision, the generated file records
revision: null and dirty: null rather than inventing provenance. Official
clean-prefix checks reject that incomplete identity.
Container install
GHCR is the supported prebuilt amd64 delivery path for both ROS distributions.
The moving convenience tags track develop and are useful for evaluation:
docker pull ghcr.io/rsasaki0109/lidar_slam_ros2:humble
docker run --rm ghcr.io/rsasaki0109/lidar_slam_ros2:humble \
lidarslam-map --version
docker pull ghcr.io/rsasaki0109/lidar_slam_ros2:jazzy
docker run --rm ghcr.io/rsasaki0109/lidar_slam_ros2:jazzy \
lidarslam-map --version
latest remains an alias of the Humble convenience image for compatibility.
It is not a release identifier.
Only a repository push event on develop can update these moving tags. The
Docker workflow gives pull-request and manual workflow_dispatch jobs a
contents-read token, builds into the disposable runner with push: false, and
smoke-tests the installed CLI without logging in to GHCR. Those verification
runs cannot publish a package, attestation, candidate identity, or moving tag.
Manually dispatching the convenience workflow is not a candidate-publication
route.
Immutable matrix candidate gate (E2 only)
.github/workflows/candidate-image.yml is the separate digest-publication
gate. Pull requests execute only its contents-read contract job. Candidate
publication has no workflow_dispatch trigger and therefore no selectable
workflow ref: an explicit e2-publish-candidate-image repository_dispatch
event starts the workflow definition on the repository default branch. The
read-only authorization job then requires all of the following before the
matrix job can receive packages: write:
- the workflow ref and repository default branch are both exactly
develop; - the requester has the
maintainoradminrepository role and supplied the literal approvalE2_IMMUTABLE_DIGEST_ONLY; - the requested commit is the exact mergeable head of an open, same-repository
pull request targeting
develop, and itsVERSIONmatches the requested product version; - all nine required exact-head checks succeeded, no check is active or failed, and only the named non-publication jobs may be skipped; and
- the
candidate-imagesenvironment already has one to six required reviewers, Prevent self-review, and exactly one custom deployment branch policy nameddevelop, with no unknown protection rule.
An absent or unprotected environment is a hard failure, not an implicit environment setup. Configure that environment in repository settings before making an E2 decision. The dispatch and deployment approval must remain separate maintainer actions. Audit the live settings with GET requests only:
python3 scripts/check_candidate_environment.py --json --require-ready
The schema-backed result distinguishes ABSENT, MISCONFIGURED, and
BLOCKED; a 404 by itself is never treated as proof that an environment is
absent. READY means only READY_FOR_SEPARATE_E2_REVIEW. The command cannot
write repository settings, authorize a dispatch, or publish an artifact.
Without --json, the same audit prints a status-specific operator handoff:
ABSENT gives the trusted repository-settings URL and the five exact creation
and independent-review steps; MISCONFIGURED gives the bounded repair
checklist; BLOCKED asks only for read-access recovery and explicitly forbids
settings changes from incomplete evidence; and READY points to the separate
exact-head E2 review. Every card ends with the copy-ready read-only verification
command and Environment writes performed: no. The handoff describes an
external administrator action; it never performs or authorizes that action.
After a separate E2 approval, the exact event shape is:
CANDIDATE_PR='<OPEN_SAME_REPOSITORY_PR_NUMBER>'
CANDIDATE_COMMIT='<EXACT_40_CHARACTER_PR_HEAD_SHA>'
CANDIDATE_VERSION='<MATCHING_X.Y.Z_VERSION>'
gh api --method POST \
repos/rsasaki0109/lidar_slam_ros2/dispatches \
-f event_type=e2-publish-candidate-image \
-F "client_payload[pull_request]=${CANDIDATE_PR}" \
-f "client_payload[source_commit]=${CANDIDATE_COMMIT}" \
-f "client_payload[product_version]=${CANDIDATE_VERSION}" \
-f 'client_payload[approval]=E2_IMMUTABLE_DIGEST_ONLY'
The POST above is the E2 registry-write boundary. Do not run it merely because
the workflow or PR checks are green. A successful run publishes Humble and
Jazzy manifests by digest only (push-by-digest=true), creates no version,
humble, jazzy, or latest tag, smoke-tests the exact digest with container
networking disabled, verifies SBOM, provenance, and GitHub attestation, and
uploads these schema-backed artifacts for 30 days:
candidate-image-request.json— exact PR, actor, environment, CI authorization, and immutable default-branch gate commit;candidate-image-humble.jsonandcandidate-image-jazzy.json— exact source/digest records with an emptytags_createdarray; andcandidate-image-set.json— a complete, distinct, same-source pair.
The records deliberately say
registry_retention_status: REQUIRES_REMOTE_AUDIT. On a prepared disposable
row host, inspect that same request locally before any download, build, host
mutation, or evidence write:
python3 scripts/start_candidate_trial.py \
--workflow-run-url \
https://github.com/rsasaki0109/lidar_slam_ros2/actions/runs/<RUN_ID> \
--row <docker-humble|docker-jazzy|source-humble|source-jazzy> \
--output-dir /evidence/<NEW_SESSION_DIRECTORY> \
--human-measurements prompt \
--acknowledge-dedicated-trial-host \
--check-readiness
The schema-backed readiness card performs local reads only. It separates a
machine BLOCKED result, a missing human CONFIRMATION_REQUIRED, and a
runnable but READY_NONCOMPARABLE measurement plan from READY, then prints
one shell-quoted next command. The acknowledgement must be supplied only after
the documented disposable-host, filesystem, network, and route-mutation
claims are true; no checker can infer them. Automation can consume
candidate-trial-readiness-v1
with --json.
After READY, the printed command starts from the exact Actions run URL and
finishes one selected row in one command:
python3 scripts/start_candidate_trial.py \
--workflow-run-url \
https://github.com/rsasaki0109/lidar_slam_ros2/actions/runs/<RUN_ID> \
--row <docker-humble|docker-jazzy|source-humble|source-jazzy> \
--output-dir /evidence/<NEW_SESSION_DIRECTORY> \
--human-measurements prompt \
--acknowledge-dedicated-trial-host
Its atomic session contains handoff/, execution/, and schema-backed
session.json. The handoff
is independently downloaded and audited before the row's live preflight. For a
Docker row, the wrapper derives a local observer tag from the reviewed
Dockerfile SHA-256, builds it only when absent and before timing, and validates
its exact contract, Ubuntu, recipe labels, and immutable local image ID. A
blocked preflight, valid PASS/FAIL, or harness failure is retained as a bounded
terminal session; no remote mutation is performed.
Use the split path when the authenticated handoff must be reviewed or moved to a different disposable host. First prepare it:
python3 scripts/prepare_candidate_trial.py \
--workflow-run-url \
https://github.com/rsasaki0109/lidar_slam_ros2/actions/runs/<RUN_ID> \
--output-dir /evidence/candidate-image-run-<RUN_ID>
Authenticate gh for public Actions artifact and attestation reads, and ensure
Docker Buildx is available. The output parent must already exist; the requested
directory must not. The command downloads all four artifacts, validates their
cross-file derivation, and downloads all four again into a temporary directory.
That independent pass byte-compares all four SHA-256 values, verifies both
registry manifests and attestations, and generates the JSON and Markdown
observer packet. It publishes the directory
atomically only after REMOTE_AUDIT_PASS; on any failure the requested output
remains absent. The schema-backed
preparation.json names
the exact bundle/set hashes, expiry, relative outputs, and no-write authority.
The resulting layout is:
candidate-image-run-<RUN_ID>/
├── artifacts/ # exactly the four canonical JSON records
├── candidate-audit.json # REMOTE_AUDIT_PASS
├── observer-packet.json
├── observer-packet.md
└── preparation.json # READY_FOR_OBSERVER
Use artifacts/ as --candidate-evidence-dir when independently rerunning
python3 scripts/audit_candidate_image_set.py --remote --json or regenerating
a packet. Those primitives remain available for review, but manual artifact
loops are no longer the normal path. Require
REMOTE_AUDIT_PASS before using either identity in an onboarding row. The
preparation command grants no GitHub or registry write authority and does not
run a trial. Do not invent release tags: a candidate digest is not a Git tag,
GitHub Release, stable image, or E4 approval.
On each dedicated candidate-trial VM, consume that complete handoff without copying identities or packet commands by hand:
python3 scripts/run_candidate_trial.py \
--handoff-dir /evidence/candidate-image-run-<RUN_ID> \
--row <docker-humble|docker-jazzy|source-humble|source-jazzy> \
--output-dir /evidence/<NEW_TRIAL_DIRECTORY> \
--acknowledge-dedicated-trial-host
The row runner locally re-derives the handoff and reruns the selected Docker
remote audit or source public preflight before invoking the maintained probe.
Docker rows also perform the same content-addressed observer-image bootstrap,
so the normal candidate path no longer needs a separate docker build command.
Its atomic schema-backed
execution.json
distinguishes a blocked preflight, a schema-valid PASS/FAIL trial, and a
harness error. A validated product PASS also retains the exact privacy-bounded
first-map-validation-receipt.json; execution.json lists it as a bounded
output, and the comparability checker verifies its bytes against the trial
record rather than trusting a copied digest. Interactive auto mode prompts for the
two human observations; non-interactive mode leaves them unknown and the trial
non-comparable. It performs network reads and local trial writes, including
the acknowledged source-host or privileged-container work, but no GitHub,
registry, release, issue, or community mutation.
The reported candidate_bundle_sha256 is reproducible: hash the UTF-8
concatenation of one <canonical-filename>\t<file-sha256>\n line for each
file, in request, Humble, Jazzy, set order. The report retains each component
hash as well, so a reviewer never has to infer which bytes a bundle hash names.
Every tagged release publishes exact
ghcr.io/rsasaki0109/lidar_slam_ros2:v<VERSION>-<distro> images only after
the repository tag matches VERSION. For example:
IMAGE=ghcr.io/rsasaki0109/lidar_slam_ros2:v0.9.0-jazzy
docker pull "$IMAGE"
docker run --rm "$IMAGE" lidarslam-map --version
Use an exact digest for deployment or rollback. Each GitHub Release attaches
release-image-humble.json and release-image-jazzy.json; they record the
tested tag, digest, tag commit, platform, product version, and observed CLI
version. The release workflow also rejects a runtime whose installed source
revision differs from that tag commit. It attaches the schema-validated
rollback plan generated from each record:
lidarslam-map rollback-plan release-image-jazzy.json
Run the printed pull, gh attestation verify, and CLI smoke commands, then
substitute the printed ghcr.io/...@sha256:... reference in the normal Docker
invocation. Keep the previous release-image JSON with deployment evidence so
the same last-known-good digest remains recoverable. Never implement rollback
by retagging an old image or moving a convenience/versioned tag; map outputs
are likewise immutable, so a post-rollback mapping run uses a new output
directory.
Pre-promotion compressed-size gate
Measure a local single-platform OCI export with
scripts/measure_oci_archive.py before public promotion. The checker verifies
the complete descriptor and diffID graph, the exact linux/amd64 platform,
gzip media types, revision/version labels, and archive closure before applying
the reduction threshold. A baseline byte count is accepted only together with
its immutable tag, index digest, and platform-manifest digest. Its output uses
the
oci-image-measurement-v1 schema.
The exact command and current Humble/Jazzy evidence are in the
runtime-image slimming record.
This measurement export disables provenance only to keep its OCI index to one runnable platform manifest. It is not a substitute for the SBOM, maximum-mode provenance, attestation, and digest-bound smoke checks below; the final release candidate must satisfy both contracts.
The release workflow builds from the tagged recursive checkout and initially pushes each candidate by digest without a version tag. It requires the installed CLI version and source-revision label to match, verifies the OCI SBOM, maximum-mode BuildKit provenance, and signed GitHub attestation, and then preflights the Humble/Jazzy pair together. Only tested digests are promoted to version tags. A matching tag is safely reused during recovery; a tag that already resolves to a different digest aborts the release instead of moving it. The GitHub Release is created only after both distro images pass and immutable promotion succeeds.
The deterministic release bundle embeds
release-bundle-manifest-v1.json, which records the tag commit and SHA-256 of
every included product document, evidence record, schema, contract, and media
file. release-promotion.json records which version tags were created or
reused and always declares moving_tag_mutated: false. Verify the GitHub
provenance with:
gh attestation verify \
oci://ghcr.io/rsasaki0109/lidar_slam_ros2@sha256:<digest> \
-R rsasaki0109/lidar_slam_ros2
The version examples illustrate the tag contract; use a tag listed on the GitHub Releases page. Convenience tags are intentionally moving, so recording their current digest is mandatory when they are used in evaluation evidence.
Bind-mounted output ownership
The default demo starts as container root so it can use the prebuilt workspace and its internal dataset cache. On Linux, pass the invoking UID and GID to return the dedicated output mount to the host user when the demo exits:
mkdir -p "$PWD/lidarslam_output"
docker run --rm \
-e LIDARSLAM_HOST_UID="$(id -u)" \
-e LIDARSLAM_HOST_GID="$(id -g)" \
-v "$PWD/lidarslam_output:/lidarslam_ws/output" \
ghcr.io/rsasaki0109/lidar_slam_ros2:humble
Both variables are required together and must be numeric. The demo changes the
owner of the dedicated output mount, the requested run directory and any
failed .partial or post-processing lock sidecar; unrelated sibling contents
are not changed.
The own-bag command above instead starts the complete process as the host
UID/GID, so no root-owned output is created. HOME and ROS_LOG_DIR are moved
to writable container/output locations because the image's /root is not
writable to that numeric user.
Profile-specific extras
The flagship PointCloud2 + IMU profile is complete after the recursive source
install above. The pointcloud_gnss_smoke and packet_applanix_smoke
evaluation profiles additionally use the PyPI rosbags package to inspect raw
bag records. It has no Ubuntu 22.04/24.04 rosdep key, so keep it isolated in a
virtual environment:
python3 -m venv --system-site-packages ~/.venvs/lidarslam
source ~/.venvs/lidarslam/bin/activate
python3 -m pip install rosbags
source ~/ros2_ws/install/setup.bash
Activate that environment before invoking either GNSS profile. The workflow fails before starting ROS processes and points back to this section when the module is absent. This extra is not required by the default RKO-LIO path.
Support matrix
| Delivery | Humble amd64 | Jazzy amd64 | arm64 / Jetson | Flagship RKO-LIO path |
|---|---|---|---|---|
Recursive source checkout + colcon |
Tested in CI | Tested in CI | Evaluation; use the Jetson runbook | Included from the maintained submodule |
| GHCR image | Moving and versioned amd64 images | Moving and versioned amd64 images | Not yet published | Included |
| ROS buildfarm / apt | Not released | Not released | Not released | Official RKO-LIO 0.3.2 passed the Humble/Jazzy product gate |
amd64 is the tested product target. Jetson/MID-360 workflows have real-device
evidence, but arm64 installation and image publication are still an evaluation
tier rather than a release guarantee.
Binary-release boundary
There is currently no supported
sudo apt install ros-<distro>-lidarslam golden path. Two packaging gates
remain:
ndt_omp_ros2, which is a declared build dependency, has a source tag and generated Bloom PRs, but an unanswered review exposed that it installs the sameinclude/pclomp/*andlibndt_omp.sofiles as Humble's releasedndt_omp. Resolve the review through upstream convergence or a fully namespaced replacement before any rosdistro merge. The preferred upstream API patch, parent PCL-pointer modernization, and final canonical dependency switch have been prepared and verified locally; see the 2026-08-12 convergence evidence.- Official PRBonn
rko_lio 0.3.2-1passed the clean installed golden-path E2E on Humble and Jazzy.lidarslamnow declaresrko_lio >= 0.3.2. Humble main satisfies that minimum; Jazzy must still sync 0.3.2 from ROS testing to main before normal Jazzy apt dependency resolution can succeed.
See the official binary evidence and rosdistro release runbook for the verified boundary and remaining maintainer prerequisites.
Versioned Humble/Jazzy GHCR tags, release-image SBOM/provenance, digest smoke tests, and attached installation evidence are automated for the next tagged release. ROS buildfarm packages remain blocked by the NDT convergence review and the Jazzy RKO-LIO main-sync gate above. The binary package-manager clean-install/upgrade gate is implemented but cannot produce release evidence until those packages exist; arm64 image publication also remains Phase 2 work.