Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions terrain-intelligence-generator/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ RUN ARCH_DIR="x86-64-linx" && \
echo "export VICSYS=DEVELOPMENT" >> /usr/local/bin/$cmdname; \
echo "export LD_LIBRARY_PATH=$VICAR_LIB_PATH:\$LD_LIBRARY_PATH" >> /usr/local/bin/$cmdname; \
echo "export PATH=${V2TOP}/gui/lib/$ARCH_DIR:${V2TOP}/mars/lib/$ARCH_DIR:${V2TOP}/p2/lib/$ARCH_DIR:\$PATH" >> /usr/local/bin/$cmdname; \
echo "# X11 app-defaults (.xres) so Motif widget labels resolve instead of showing placeholder resource names" >> /usr/local/bin/$cmdname; \
echo "export XFILESEARCHPATH=\"${V2TOP}/gui/lib/$ARCH_DIR/%N%S.xres:${V2TOP}/p2/lib/$ARCH_DIR/%N%S.xres:${V2TOP}/mars/lib/$ARCH_DIR/%N%S.xres:/usr/lib/X11/%L/%T/%N%C%S:/usr/lib/X11/%l/%T/%N%C%S:/usr/lib/X11/%T/%N%C%S:/usr/lib/X11/%L/%T/%N%S:/usr/lib/X11/%l/%T/%N%S:/usr/lib/X11/%T/%N%S\${XFILESEARCHPATH:+:\$XFILESEARCHPATH}\"" >> /usr/local/bin/$cmdname; \
echo "export XBMLANGPATH=\"%D:${V2TOP}/p2/lib/$ARCH_DIR/%B\"" >> /usr/local/bin/$cmdname; \
echo "$cmd \"\$@\"" >> /usr/local/bin/$cmdname; \
echo "exit_code=\$?" >> /usr/local/bin/$cmdname; \
echo "# VICAR commands often return non-zero on success, treat 0-2 as success" >> /usr/local/bin/$cmdname; \
Expand All @@ -319,6 +322,9 @@ RUN ARCH_DIR="x86-64-linx" && \
echo "export VICSYS=DEVELOPMENT" >> /etc/profile.d/vicar.sh && \
echo "export LD_LIBRARY_PATH=$VICAR_LIB_PATH:\$LD_LIBRARY_PATH" >> /etc/profile.d/vicar.sh && \
echo "export PATH=${V2TOP}/tae53/bin:${V2TOP}/bin:\$PATH" >> /etc/profile.d/vicar.sh && \
echo "# X11 app-defaults (.xres) so Motif GUI (xvd) widget labels resolve instead of showing placeholder resource names" >> /etc/profile.d/vicar.sh && \
echo "export XFILESEARCHPATH=\"${V2TOP}/gui/lib/$ARCH_DIR/%N%S.xres:${V2TOP}/p2/lib/$ARCH_DIR/%N%S.xres:${V2TOP}/mars/lib/$ARCH_DIR/%N%S.xres:/usr/lib/X11/%L/%T/%N%C%S:/usr/lib/X11/%l/%T/%N%C%S:/usr/lib/X11/%T/%N%C%S:/usr/lib/X11/%L/%T/%N%S:/usr/lib/X11/%l/%T/%N%S:/usr/lib/X11/%T/%N%S\${XFILESEARCHPATH:+:\$XFILESEARCHPATH}\"" >> /etc/profile.d/vicar.sh && \
echo "export XBMLANGPATH=\"%D:${V2TOP}/p2/lib/$ARCH_DIR/%B\"" >> /etc/profile.d/vicar.sh && \
chmod +x /etc/profile.d/vicar.sh

# Verify VICAR installation
Expand Down
128 changes: 122 additions & 6 deletions vicar-native-toolkit/.envrc
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@ fi
# Disable SELinux label enforcement on the container (Linux only).
# May be required for images that load certain 32-bit legacy shared
# libraries, which can trigger "cannot change memory protections" under
# SELinux. Opt-in; default keeps standard labeling. Set
# DISABLE_SELINUX_LABEL=true in .envrc.local when needed.
# SELinux, and is required for the container to access the host X11 socket
# (/tmp/.X11-unix) when SELinux is Enforcing. Opt-in; default keeps standard
# labeling. Set DISABLE_SELINUX_LABEL=true in .envrc.local to force it.
# When left unset, the toolkit auto-enables it on SELinux-Enforcing Linux hosts.
if [[ -z "${DISABLE_SELINUX_LABEL+x}" ]]; then
DISABLE_SELINUX_LABEL_EXPLICIT=false
else
DISABLE_SELINUX_LABEL_EXPLICIT=true
fi
: ${DISABLE_SELINUX_LABEL:=false}

# VICAR paths inside container (for terrain-intelligence-generator:opensource)
Expand All @@ -40,6 +47,73 @@ log_error() {
echo "[vicar-toolkit] ERROR: $1" >&2
}

# ===== X11 / Display Readiness =====
# Ensures the host X server will accept connections from the container, so GUI
# tools (xvd) don't fail with "Authorization required, but no authorization
# protocol specified" / "Can't open display". Runs on every activation because
# xhost ACLs and XQuartz state do not persist across restarts/reboots.
ensure_x11_ready() {
if [[ "${HOST_OS}" == "Darwin" ]]; then
# macOS: container reaches XQuartz over TCP via host.docker.internal:0.
# Requires XQuartz running, TCP listening (nolisten_tcp=false), and the
# host to authorize localhost (xhost +localhost).
if ! command -v xhost >/dev/null 2>&1; then
log_info "XQuartz/xhost not found. Install with: brew install --cask xquartz (GUI tools will not work until then)"
return 0
fi

# Ensure XQuartz allows TCP connections (persisted preference).
if command -v defaults >/dev/null 2>&1; then
if [[ "$(defaults read org.xquartz.X11 nolisten_tcp 2>/dev/null)" != "0" ]]; then
defaults write org.xquartz.X11 nolisten_tcp -bool false 2>/dev/null || true
log_info "Set XQuartz to allow TCP connections (restart XQuartz if GUI still fails)"
fi
fi

# Start XQuartz if not running.
if ! pgrep -q -x Xquartz && ! pgrep -q -x X11.bin 2>/dev/null; then
log_info "Starting XQuartz..."
open -a XQuartz 2>/dev/null || log_info "Could not auto-start XQuartz; run: open -a XQuartz"
# Give XQuartz a moment to bind the TCP socket before xhost.
local _tries=0
while ! pgrep -q -x Xquartz && [[ ${_tries} -lt 10 ]]; do
sleep 0.5
((_tries++))
done
fi

# Authorize localhost so the container's TCP connection is accepted.
if DISPLAY=:0 xhost +localhost >/dev/null 2>&1; then
log_info "X11 authorized (xhost +localhost)"
else
log_info "Could not run 'xhost +localhost' yet. If GUI fails, ensure XQuartz is running then run: xhost +localhost && toolkit-restart"
fi
else
# Linux: container shares the X11 unix socket via --network host.
# Authorize local (non-network) connections to the display. Use the
# broad "+local:" form: SELinux label=disable makes the container
# connect as the "LOCAL:" family, which "+local:docker" does NOT cover.
if command -v xhost >/dev/null 2>&1 && [[ -n "${DISPLAY}" ]]; then
xhost +local: >/dev/null 2>&1 || true
fi

# SELinux Enforcing blocks the container (container_t) from the host
# X11 socket (/tmp/.X11-unix), so xvd fails with a misleading
# "Can't open display". Auto-enable label=disable unless the user has
# explicitly configured DISABLE_SELINUX_LABEL themselves.
if command -v getenforce >/dev/null 2>&1 && [[ "$(getenforce 2>/dev/null)" == "Enforcing" ]]; then
if [[ "${DISABLE_SELINUX_LABEL_EXPLICIT}" != "true" ]]; then
if [[ "${DISABLE_SELINUX_LABEL}" != "true" ]]; then
DISABLE_SELINUX_LABEL=true
log_info "SELinux is Enforcing: enabling container '--security-opt label=disable' so GUI tools can reach the X11 socket (set DISABLE_SELINUX_LABEL=false in .envrc.local to override)"
fi
elif [[ "${DISABLE_SELINUX_LABEL}" != "true" ]]; then
log_info "SELinux is Enforcing and DISABLE_SELINUX_LABEL=false: GUI tools (xvd) may fail with 'Can't open display'. Set DISABLE_SELINUX_LABEL=true in .envrc.local if so."
fi
fi
fi
}

# ===== Container Management =====
ensure_container_running() {
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
Expand Down Expand Up @@ -156,8 +230,23 @@ else
REL_PATH="."
fi

# X11 app-defaults so Motif GUI (xvd) widget labels resolve to real text
# instead of placeholder resource names. Passed at exec time so this works
# even on images built before the in-image fix. %N=class (e.g. XVd),
# %S=suffix -> resolves \${GUILIB}/XVd.xres. Runtime value is a fallback:
# if the container already sets XFILESEARCHPATH, that is preserved (appended).
VICAR_GUI_LIB="${VICAR_INSTALL_PREFIX}/gui/lib/x86-64-linx"
VICAR_P2_LIB="${VICAR_INSTALL_PREFIX}/p2/lib/x86-64-linx"
VICAR_MARS_LIB="${VICAR_INSTALL_PREFIX}/mars/lib/x86-64-linx"
X11_DEFAULTS="/usr/lib/X11/%L/%T/%N%C%S:/usr/lib/X11/%l/%T/%N%C%S:/usr/lib/X11/%T/%N%C%S:/usr/lib/X11/%L/%T/%N%S:/usr/lib/X11/%l/%T/%N%S:/usr/lib/X11/%T/%N%S"
XFSP="\${VICAR_GUI_LIB}/%N%S.xres:\${VICAR_P2_LIB}/%N%S.xres:\${VICAR_MARS_LIB}/%N%S.xres:\${X11_DEFAULTS}"
XBMLP="%D:\${VICAR_P2_LIB}/%B"

# Execute command in container at correct working directory
exec docker exec -w "/workspace/\${REL_PATH}" "\${CONTAINER_NAME}" "\${TOOL_NAME}" "\$@"
exec docker exec \\
-e "XFILESEARCHPATH=\${XFSP}" \\
-e "XBMLANGPATH=\${XBMLP}" \\
-w "/workspace/\${REL_PATH}" "\${CONTAINER_NAME}" "\${TOOL_NAME}" "\$@"
VICAR_EXEC_EOF
chmod +x "${exec_script}"

Expand Down Expand Up @@ -232,10 +321,11 @@ create_utility_commands() {
cat > "${utils_script}" << UTILS_HEADER_EOF
#!/bin/bash
# Toolkit utility commands handler
# Handles: toolkit-shell, toolkit-stop, toolkit-restart, toolkit-status
# Handles: toolkit-shell, toolkit-stop, toolkit-restart, toolkit-update, toolkit-status

UTIL_NAME="\$(basename "\$0")"
CONTAINER_NAME="${CONTAINER_NAME}"
CONTAINER_IMAGE="${CONTAINER_IMAGE}"
UTILS_HEADER_EOF
cat >> "${utils_script}" << 'TOOLKIT_UTILS_EOF'

Expand All @@ -253,6 +343,28 @@ case "$UTIL_NAME" in
echo "Container stopped. Re-enter directory to restart."
;;

toolkit-update)
echo "Pulling latest image: ${CONTAINER_IMAGE}"
old_id="$(docker image inspect "${CONTAINER_IMAGE}" --format '{{.Id}}' 2>/dev/null || true)"
if ! docker pull --platform linux/amd64 "${CONTAINER_IMAGE}"; then
echo "Error: docker pull failed for ${CONTAINER_IMAGE}" >&2
exit 1
fi
new_id="$(docker image inspect "${CONTAINER_IMAGE}" --format '{{.Id}}' 2>/dev/null || true)"
if [[ "${old_id}" == "${new_id}" ]]; then
echo "Image already up to date (${new_id#sha256:})."
else
echo "Image updated: ${old_id#sha256:} -> ${new_id#sha256:}"
fi
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker stop "${CONTAINER_NAME}" >/dev/null 2>&1 || true
docker rm "${CONTAINER_NAME}" >/dev/null 2>&1 || true
echo "Old container removed. Re-enter directory to recreate from the new image."
else
echo "No running container. Re-enter directory to create one from the new image."
fi
;;

toolkit-status)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "VICAR Native Toolkit Status:"
Expand All @@ -271,7 +383,7 @@ case "$UTIL_NAME" in
# Direct invocation handler
if [[ $# -eq 0 ]]; then
echo "Usage: toolkit-utils <command>"
echo "Commands: shell, stop, restart, status"
echo "Commands: shell, stop, restart, update, status"
echo "Or create symlinks: ln -s toolkit-utils toolkit-<command>"
exit 1
fi
Expand All @@ -291,9 +403,10 @@ TOOLKIT_UTILS_EOF
ln -sf "../toolkit-utils" "${WRAPPER_DIR}/toolkit-shell"
ln -sf "../toolkit-utils" "${WRAPPER_DIR}/toolkit-stop"
ln -sf "../toolkit-utils" "${WRAPPER_DIR}/toolkit-restart"
ln -sf "../toolkit-utils" "${WRAPPER_DIR}/toolkit-update"
ln -sf "../toolkit-utils" "${WRAPPER_DIR}/toolkit-status"

log_info "Created 4 utility command symlinks"
log_info "Created 5 utility command symlinks"
}

# ===== Main Activation =====
Expand All @@ -304,6 +417,9 @@ main() {
# Ensure workspace exists
mkdir -p "${WORKSPACE_ROOT}"

# Ensure host X server will accept GUI connections from the container
ensure_x11_ready

# Ensure container is running
if ! ensure_container_running; then
log_error "Failed to start container"
Expand Down
1 change: 1 addition & 0 deletions vicar-native-toolkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ toolkit-shell # Open interactive shell in container
toolkit-status # Show container status and wrapper count
toolkit-stop # Stop and remove container
toolkit-restart # Restart container (useful after config changes)
toolkit-update # Pull the latest CONTAINER_IMAGE and recreate the container
toolkit-verify-calib # Verify MARS calibration mounting (if configured)
```

Expand Down
22 changes: 22 additions & 0 deletions vicar-native-toolkit/docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,28 @@ make bootstrap MARS_CALIB=/path/to/mars_calibration
toolkit-verify-calib
```

### SELinux (Linux)

On Linux hosts with SELinux in **Enforcing** mode, the container (`container_t`)
is denied access to the host X11 socket (`/tmp/.X11-unix`), so GUI tools such as
`xvd` fail with a misleading `Can't open display`. The toolkit handles this
automatically:

```bash
# Auto-enabled on SELinux-Enforcing hosts when left unset.
# Adds "--security-opt label=disable" at container creation.
DISABLE_SELINUX_LABEL=true
```

- **Unset (default):** auto-enabled only when `getenforce` reports `Enforcing`.
- **`DISABLE_SELINUX_LABEL=true`:** always disable labeling.
- **`DISABLE_SELINUX_LABEL=false`:** never disable it (the toolkit logs a hint
if it detects Enforcing mode and GUI tools may fail).

Changing this only affects a newly created container. After changing it, run
`toolkit-update` (or `toolkit-restart` then re-enter the directory) to recreate
the container with the new setting.

### Command Auto-Discovery

By default, commands are auto-discovered from the container:
Expand Down
3 changes: 2 additions & 1 deletion vicar-native-toolkit/docs/QUICKREF.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ toolkit-status # Show container status
toolkit-shell # Interactive shell in container
toolkit-stop # Stop and remove container
toolkit-restart # Restart container
toolkit-update # Pull latest image and recreate container
toolkit-verify-calib # Verify MARS calibration (if configured)

# VICAR commands work natively
Expand Down Expand Up @@ -170,7 +171,7 @@ readlink .direnv/wrappers/gen # Should point to ../vicar-exec

# Image issues
docker images | grep terrain-intelligence-generator
docker pull ghcr.io/nasa-ammos/tig/terrain-intelligence-generator:opensource
toolkit-update # pull latest CONTAINER_IMAGE and recreate container

# Container logs
docker logs vicar-sidecar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ Available commands:
toolkit-shell - Open interactive shell in container
toolkit-stop - Stop and remove container
toolkit-restart - Restart container with new configuration
toolkit-update - Pull latest image and recreate container

VICAR tools: 543 commands available
Working directory: <current-path>/vicar-native-toolkit/workspace
Expand Down
Loading