seedetcher/scripts/cups/cups-runtime-bootstrap

487 lines
16 KiB
Bash
Executable File

#!/bin/sh
set -eu
SOCK="${CUPS_SERVER_SOCK:-/var/run/cups/cups.sock}"
CUPS_RUNTIME_DEBUG_TO_KMSG="${CUPS_RUNTIME_DEBUG_TO_KMSG:-0}"
log() {
msg="$*"
echo "$msg" >> /log/cups.log
if [ "$CUPS_RUNTIME_DEBUG_TO_KMSG" = "1" ]; then
echo "DEBUG: CUPS bootstrap: $msg" > /dev/kmsg 2>/dev/null || true
fi
}
debug_echo() {
log "$*"
}
is_mounted() {
mnt="$1"
awk -v m="$mnt" '$2==m{found=1} END{exit found?0:1}' /proc/mounts
}
mount_nix_from_sd() {
mkdir -p /nix
if is_mounted /nix; then
return 0
fi
tries=10
while [ "$tries" -gt 0 ] && [ ! -b /dev/mmcblk0p2 ]; do
sleep 1
tries=$((tries - 1))
done
if [ ! -b /dev/mmcblk0p2 ]; then
echo "cups-runtime-bootstrap: /dev/mmcblk0p2 not found" >&2
return 1
fi
mount -t ext4 /dev/mmcblk0p2 /nix >> /log/cups.log 2>&1
}
if [ -f /cups-runtime.env ]; then
# shellcheck source=/dev/null
. /cups-runtime.env
CUPS_RUNTIME_DEBUG_TO_KMSG="${CUPS_RUNTIME_DEBUG_TO_KMSG:-0}"
fi
BRLASER_DROPIN_ROOT=""
mkdir -p /mnt/boot /var/cups-extra
BRLASER_RUNTIME_ROOT=/var/cups-extra/brlaser-runtime
mkdir -p "$BRLASER_RUNTIME_ROOT/filter" "$BRLASER_RUNTIME_ROOT/lib"
SHOULD_LOAD_DROPIN=0
case "${CUPS_RUNTIME_LOAD_DROPIN:-auto}" in
1|yes|true|on)
SHOULD_LOAD_DROPIN=1
;;
0|no|false|off)
SHOULD_LOAD_DROPIN=0
;;
*)
[ -z "${BRLASER_ROOT:-}" ] && SHOULD_LOAD_DROPIN=1
;;
esac
if [ "$SHOULD_LOAD_DROPIN" -eq 1 ] && [ -b /dev/mmcblk0p1 ]; then
mount -t vfat /dev/mmcblk0p1 /mnt/boot >/dev/null 2>&1 || true
for cand in /mnt/boot/brlaser-root.tar.gz /mnt/boot/brlaser-root.tgz /mnt/boot/brlaser-root.tar; do
if [ -f "$cand" ]; then
rm -rf /var/cups-extra/brlaser-root
mkdir -p /var/cups-extra/brlaser-root
if tar -xf "$cand" -C /var/cups-extra/brlaser-root >/dev/null 2>&1; then
BRLASER_DROPIN_ROOT=/var/cups-extra/brlaser-root
if [ -d /var/cups-extra/brlaser-root/brlaser-root ]; then
BRLASER_DROPIN_ROOT=/var/cups-extra/brlaser-root/brlaser-root
fi
debug_echo "loaded brlaser drop-in from $(basename "$cand")"
else
debug_echo "failed to extract brlaser drop-in $(basename "$cand")"
fi
break
fi
done
umount /mnt/boot >/dev/null 2>&1 || true
elif [ "$SHOULD_LOAD_DROPIN" -eq 0 ]; then
debug_echo "drop-in loading disabled"
else
debug_echo "no boot partition for drop-in loading"
fi
# Minimal identity DB expected by cupsd in initramfs.
if [ ! -f /etc/group ]; then
cat > /etc/group <<'EOF_GROUP'
root:x:0:
sys:x:3:
lp:x:7:
lpadmin:x:19:
EOF_GROUP
fi
if [ ! -f /etc/passwd ]; then
cat > /etc/passwd <<'EOF_PASSWD'
root:x:0:0:root:/root:/bin/sh
lp:x:7:7:CUPS:/var/spool/cups:/bin/false
EOF_PASSWD
fi
grep -q '^lpadmin:' /etc/group || echo 'lpadmin:x:19:' >> /etc/group
grep -q '^lp:' /etc/group || echo 'lp:x:7:' >> /etc/group
grep -q '^sys:' /etc/group || echo 'sys:x:3:' >> /etc/group
grep -q '^root:' /etc/group || echo 'root:x:0:' >> /etc/group
grep -q '^lp:' /etc/passwd || echo 'lp:x:7:7:CUPS:/var/spool/cups:/bin/false' >> /etc/passwd
mkdir -p /etc/cups /var/cache/cups /var/spool/cups/tmp /run/cups /var/run/cups /var/log/cups
chmod 1777 /var/spool/cups/tmp
mount_nix_from_sd
CUPS_BIN_ROOT="$(readlink /bin/cupsd 2>/dev/null | sed 's#/bin/cupsd$##')"
[ -z "$CUPS_BIN_ROOT" ] && CUPS_BIN_ROOT="/nix/store"
CUPS_DATA_ROOT="$CUPS_BIN_ROOT"
if [ ! -d "${CUPS_DATA_ROOT}/share/cups" ] && [ -d "${CUPS_BIN_ROOT}-lib/share/cups" ]; then
CUPS_DATA_ROOT="${CUPS_BIN_ROOT}-lib"
fi
repair_elf_runtime() {
elf_bin="$1"
[ -x "$elf_bin" ] || return 0
[ -x /bin/readelf ] || return 0
interp_path="$(/bin/readelf -l "$elf_bin" 2>/dev/null | sed -n 's@.*Requesting program interpreter: \(.*\)]@\1@p' | head -n 1)"
if [ -n "$interp_path" ] && [ ! -e "$interp_path" ] && [ -e /lib/ld-musl-armhf.so.1 ]; then
mkdir -p "$(dirname "$interp_path")"
ln -snf /lib/ld-musl-armhf.so.1 "$interp_path" 2>/dev/null || true
debug_echo "repaired interp for $(basename "$elf_bin") -> $interp_path"
fi
runpaths="$(/bin/readelf -d "$elf_bin" 2>/dev/null | sed -n 's@.*Library runpath: \[\(.*\)\]@\1@p' | head -n 1)"
[ -n "$runpaths" ] || return 0
gcc_lib_path="$(find /nix/store -path '*gcc*lib*/armv6l-unknown-linux-musleabihf/lib' 2>/dev/null | head -n 1)"
cups_lib_path=""
if [ -d "${CUPS_BIN_ROOT}/lib" ]; then
cups_lib_path="${CUPS_BIN_ROOT}/lib"
elif [ -d "${CUPS_BIN_ROOT}-lib/lib" ]; then
cups_lib_path="${CUPS_BIN_ROOT}-lib/lib"
fi
oldifs="$IFS"
IFS=':'
for rp in $runpaths; do
[ -n "$rp" ] || continue
[ -e "$rp" ] && continue
target=""
case "$rp" in
*musl*"/lib") target="/lib" ;;
*cups*"/lib") target="$cups_lib_path" ;;
*gcc*"/armv6l-unknown-linux-musleabihf/lib") target="$gcc_lib_path" ;;
esac
if [ -n "$target" ] && [ -d "$target" ]; then
mkdir -p "$(dirname "$rp")"
rm -rf "$rp" 2>/dev/null || true
ln -snf "$target" "$rp" 2>/dev/null || true
debug_echo "repaired runpath for $(basename "$elf_bin") -> $rp"
fi
done
IFS="$oldifs"
}
install_brlaser_wrapper() {
filter_bin="/var/cups-serverbin/lib/cups/filter/rastertobrlaser"
runtime_filter="$BRLASER_RUNTIME_ROOT/filter/rastertobrlaser.real"
[ -x "$filter_bin" ] || return 0
cp -a "$filter_bin" "$runtime_filter" 2>/dev/null || return 0
cat > "$filter_bin" <<'EOF_WRAP'
#!/bin/sh
RUNTIME_ROOT="/var/cups-extra/brlaser-runtime"
REAL_FILTER="$RUNTIME_ROOT/filter/rastertobrlaser.real"
[ -x "$REAL_FILTER" ] || exit 127
LD_PATHS="$RUNTIME_ROOT/lib:/lib:/usr/lib"
if [ -n "${BRLASER_LD_LIBRARY_PATH:-}" ]; then
LD_PATHS="$BRLASER_LD_LIBRARY_PATH:$LD_PATHS"
fi
if [ -n "${LD_LIBRARY_PATH:-}" ]; then
LD_PATHS="$LD_LIBRARY_PATH:$LD_PATHS"
fi
export LD_LIBRARY_PATH="$LD_PATHS"
exec "$REAL_FILTER" "$@"
EOF_WRAP
chmod 555 "$filter_bin" 2>/dev/null || true
debug_echo "installed brlaser wrapper at $filter_bin"
}
# Copy CUPS serverbin to writable storage.
mkdir -p /var/cups-serverbin/lib
rm -rf /var/cups-serverbin/lib/cups
if [ -d "${CUPS_BIN_ROOT}/lib/cups" ]; then
cp -a "${CUPS_BIN_ROOT}/lib/cups" /var/cups-serverbin/lib/
fi
for extra_root in "$BRLASER_DROPIN_ROOT" "${BRLASER_ROOT:-}" "${CUPS_FILTERS_ROOT:-}"; do
[ -n "$extra_root" ] || continue
if [ -d "$extra_root/lib/cups" ]; then
cp -a "$extra_root/lib/cups/." /var/cups-serverbin/lib/cups/ 2>/dev/null || true
fi
if [ -d "$extra_root/lib" ]; then
find "$extra_root/lib" -maxdepth 2 -type f \( -name '*.so' -o -name '*.so.*' \) -exec cp -a {} "$BRLASER_RUNTIME_ROOT/lib/" \; 2>/dev/null || true
fi
done
if [ -d /var/cups-serverbin/lib/cups ]; then
chown root:root /var/cups-serverbin/lib/cups 2>/dev/null || true
chown root:root /var/cups-serverbin/lib/cups/backend 2>/dev/null || true
chown root:root /var/cups-serverbin/lib/cups/filter 2>/dev/null || true
chown root:root /var/cups-serverbin/lib/cups/driver 2>/dev/null || true
chown root:root /var/cups-serverbin/lib/cups/daemon 2>/dev/null || true
chmod 755 /var/cups-serverbin/lib/cups 2>/dev/null || true
chmod 755 /var/cups-serverbin/lib/cups/backend 2>/dev/null || true
chmod 755 /var/cups-serverbin/lib/cups/filter 2>/dev/null || true
chmod 755 /var/cups-serverbin/lib/cups/driver 2>/dev/null || true
chmod 755 /var/cups-serverbin/lib/cups/daemon 2>/dev/null || true
chown root:root /var/cups-serverbin/lib/cups/backend/* 2>/dev/null || true
chown root:root /var/cups-serverbin/lib/cups/filter/* 2>/dev/null || true
chown root:root /var/cups-serverbin/lib/cups/driver/* 2>/dev/null || true
chown root:root /var/cups-serverbin/lib/cups/daemon/* 2>/dev/null || true
chmod 555 /var/cups-serverbin/lib/cups/backend/* 2>/dev/null || true
chmod 555 /var/cups-serverbin/lib/cups/filter/* 2>/dev/null || true
chmod 555 /var/cups-serverbin/lib/cups/driver/* 2>/dev/null || true
chmod 555 /var/cups-serverbin/lib/cups/daemon/* 2>/dev/null || true
fi
if [ -d /var/cups-serverbin/lib/cups/backend ]; then
chmod 700 /var/cups-serverbin/lib/cups/backend/* 2>/dev/null || true
fi
# Build writable CUPS data dir and merge optional models/PPDs.
CUPS_RUNTIME_DATA=/var/cups-data
rm -rf "$CUPS_RUNTIME_DATA"
mkdir -p "$CUPS_RUNTIME_DATA"
if [ -d "${CUPS_DATA_ROOT}/share/cups" ]; then
if [ "${CUPS_RUNTIME_DATA_COPY:-minimal}" = "full" ]; then
cp -a "${CUPS_DATA_ROOT}/share/cups/." "$CUPS_RUNTIME_DATA/" 2>/dev/null || true
else
for d in mime usb drv ppdc; do
if [ -d "${CUPS_DATA_ROOT}/share/cups/$d" ]; then
mkdir -p "$CUPS_RUNTIME_DATA/$d"
cp -a "${CUPS_DATA_ROOT}/share/cups/$d/." "$CUPS_RUNTIME_DATA/$d/" 2>/dev/null || true
fi
done
fi
fi
for extra_root in "$BRLASER_DROPIN_ROOT" "${BRLASER_ROOT:-}" "${CUPS_FILTERS_ROOT:-}"; do
[ -n "$extra_root" ] || continue
if [ -d "$extra_root/share/cups/drv" ]; then
mkdir -p "$CUPS_RUNTIME_DATA/drv"
cp -a "$extra_root/share/cups/drv/." "$CUPS_RUNTIME_DATA/drv/" 2>/dev/null || true
fi
if [ -d "$extra_root/share/cups/model" ]; then
mkdir -p "$CUPS_RUNTIME_DATA/model"
cp -a "$extra_root/share/cups/model/." "$CUPS_RUNTIME_DATA/model/" 2>/dev/null || true
fi
if [ -d "$extra_root/share/cups/ppdc" ]; then
mkdir -p "$CUPS_RUNTIME_DATA/ppdc"
cp -a "$extra_root/share/cups/ppdc/." "$CUPS_RUNTIME_DATA/ppdc/" 2>/dev/null || true
fi
if [ -d "$extra_root/share/ppd" ]; then
mkdir -p "$CUPS_RUNTIME_DATA/model"
cp -a "$extra_root/share/ppd/." "$CUPS_RUNTIME_DATA/model/" 2>/dev/null || true
fi
done
mkdir -p "$CUPS_RUNTIME_DATA/model" "$CUPS_RUNTIME_DATA/drv" "$CUPS_RUNTIME_DATA/usb" "$CUPS_RUNTIME_DATA/mime" "$CUPS_RUNTIME_DATA/ppdc"
chown root:root "$CUPS_RUNTIME_DATA" "$CUPS_RUNTIME_DATA/model" "$CUPS_RUNTIME_DATA/drv" "$CUPS_RUNTIME_DATA/usb" "$CUPS_RUNTIME_DATA/mime" "$CUPS_RUNTIME_DATA/ppdc" 2>/dev/null || true
chmod 755 "$CUPS_RUNTIME_DATA" "$CUPS_RUNTIME_DATA/model" "$CUPS_RUNTIME_DATA/drv" "$CUPS_RUNTIME_DATA/usb" "$CUPS_RUNTIME_DATA/mime" "$CUPS_RUNTIME_DATA/ppdc" 2>/dev/null || true
if [ "${CUPS_RUNTIME_REPAIR_ALL_FILTERS:-0}" = "1" ] && [ -d /var/cups-serverbin/lib/cups/filter ]; then
for f in /var/cups-serverbin/lib/cups/filter/*; do
[ -f "$f" ] || continue
repair_elf_runtime "$f"
done
if [ -d /var/cups-serverbin/lib/cups/driver ]; then
for f in /var/cups-serverbin/lib/cups/driver/*; do
[ -f "$f" ] || continue
repair_elf_runtime "$f"
done
fi
else
repair_elf_runtime /var/cups-serverbin/lib/cups/filter/rastertobrlaser
repair_elf_runtime /var/cups-serverbin/lib/cups/driver/drv
repair_elf_runtime /var/cups-serverbin/lib/cups/driver/cups-driverd
fi
install_brlaser_wrapper
run_ppdc_brlaser() {
out_dir="$1"
drv_file="$2"
timeout_secs="${3:-10}"
[ -n "$out_dir" ] && [ -n "$drv_file" ] || return 1
set -- /bin/ppdc -d "$out_dir"
for inc in "$CUPS_RUNTIME_DATA/ppdc" "$CUPS_RUNTIME_DATA/drv" "$CUPS_DATA_ROOT/share/cups/ppdc"; do
[ -d "$inc" ] || continue
set -- "$@" -I "$inc"
done
set -- "$@" "$drv_file"
/bin/timeout "$timeout_secs" "$@"
}
BRLASER_DRV="$CUPS_RUNTIME_DATA/drv/brlaser.drv"
if [ "${CUPS_RUNTIME_ENABLE_PPDC:-0}" = "1" ] && [ -f "$BRLASER_DRV" ] && [ -x /bin/ppdc ]; then
mkdir -p "$CUPS_RUNTIME_DATA/model"
run_ppdc_brlaser "$CUPS_RUNTIME_DATA/model" "$BRLASER_DRV" 10 >/dev/null 2>&1 || debug_echo "ppdc model generation timed out/failed"
fi
cat > /etc/cups/cups-files.conf <<EOF_CFG
SystemGroup root lpadmin
FileDevice Yes
RequestRoot /var/spool/cups
ServerRoot /etc/cups
CacheDir /var/cache/cups
DataDir ${CUPS_RUNTIME_DATA}
ServerBin /var/cups-serverbin/lib/cups
EOF_CFG
if [ ! -f /etc/cups/cupsd.conf ]; then
cat > /etc/cups/cupsd.conf <<'EOF_CUPSD'
LogLevel warn
Listen 0.0.0.0:631
Browsing Off
DefaultAuthType None
WebInterface No
<Location />
Order allow,deny
Allow all
</Location>
EOF_CUPSD
fi
BRLASER_BLOCK_REASON=""
brlaser_filter_usable() {
filter_bin="/var/cups-serverbin/lib/cups/filter/rastertobrlaser"
BRLASER_BLOCK_REASON=""
[ -x "$filter_bin" ] || { BRLASER_BLOCK_REASON="filter missing/not executable"; return 1; }
[ -x /bin/readelf ] || { BRLASER_BLOCK_REASON="readelf unavailable for probe"; return 1; }
interp_path="$(/bin/readelf -l "$filter_bin" 2>/dev/null | sed -n 's@.*Requesting program interpreter: \(.*\)]@\1@p' | head -n 1)"
if [ -n "$interp_path" ] && [ ! -e "$interp_path" ]; then
BRLASER_BLOCK_REASON="missing interpreter: $interp_path"
return 1
fi
runpaths="$(/bin/readelf -d "$filter_bin" 2>/dev/null | sed -n 's@.*Library runpath: \[\(.*\)\]@\1@p' | head -n 1)"
needed_libs="$(/bin/readelf -d "$filter_bin" 2>/dev/null | sed -n 's@.*Shared library: \[\(.*\)\]@\1@p')"
search_paths="$runpaths:/lib:/usr/lib"
for lib in $needed_libs; do
found=""
for p in $(echo "$search_paths" | tr ':' '\n'); do
[ -n "$p" ] || continue
if [ -e "$p/$lib" ]; then
found=1
break
fi
done
if [ -z "$found" ]; then
BRLASER_BLOCK_REASON="missing shared lib: $lib"
return 1
fi
done
probe_err="/tmp/brlaser-probe.err"
: > "$probe_err"
/bin/timeout 2 "$filter_bin" >/dev/null 2>"$probe_err"
rc="$?"
if grep -q -E "Error loading shared library|Error relocating|not found" "$probe_err"; then
BRLASER_BLOCK_REASON="ABI mismatch (relocation/shared-lib errors)"
return 1
fi
if [ "$rc" -eq 126 ] || [ "$rc" -eq 127 ]; then
BRLASER_BLOCK_REASON="filter failed to execute (rc=$rc)"
return 1
fi
return 0
}
find_runtime_brlaser_ppd() {
model="$1"
model_dir="$CUPS_RUNTIME_DATA/model"
[ -d "$model_dir" ] || return 0
if [ -n "$model" ]; then
ppd="$(find "$model_dir" -type f \( -iname "*${model}*.ppd" -o -iname "*${model}*.ppd.gz" \) | head -n 1)"
[ -n "$ppd" ] && { echo "$ppd"; return 0; }
ppd="$(grep -RilsF "ModelName: \"Brother $model\"" "$model_dir" 2>/dev/null | head -n 1)"
[ -n "$ppd" ] && { echo "$ppd"; return 0; }
ppd="$(grep -RilsF "MDL:$model;" "$model_dir" 2>/dev/null | head -n 1)"
[ -n "$ppd" ] && { echo "$ppd"; return 0; }
fi
find "$model_dir" -type f \
\( -iname 'brl*.ppd' -o -iname 'brl*.ppd.gz' -o -iname '*Brother*HL-*.ppd' -o -iname '*Brother*HL-*.ppd.gz' \) \
| head -n 1
}
provision_hbp_queue() {
queue_uri="$1"
[ -n "$queue_uri" ] || return 1
if ! brlaser_filter_usable; then
debug_echo "HBP blocked: skipping test-hbp queue (${BRLASER_BLOCK_REASON})"
return 0
fi
ppd=""
model_name="$(printf '%s' "$queue_uri" | sed -n 's@^usb://Brother/\([^?]*\).*$@\1@p' | sed 's/%20/ /g')"
if [ -d "$CUPS_RUNTIME_DATA/model" ]; then
ppd="$(find_runtime_brlaser_ppd "$model_name")"
fi
if [ -z "$ppd" ] && [ -x /bin/ppdc ] && [ -f "$CUPS_RUNTIME_DATA/drv/brlaser.drv" ]; then
mkdir -p "$CUPS_RUNTIME_DATA/model"
run_ppdc_brlaser "$CUPS_RUNTIME_DATA/model" "$CUPS_RUNTIME_DATA/drv/brlaser.drv" 10 >/dev/null 2>&1 || true
ppd="$(find_runtime_brlaser_ppd "$model_name")"
fi
if [ -n "$ppd" ]; then
/bin/lpadmin -h "$SOCK" -x test-hbp >/dev/null 2>&1 || true
/bin/lpadmin -h "$SOCK" -p test-hbp -E -v "$queue_uri" -P "$ppd" >> /log/cups.log 2>&1 || true
debug_echo "queue test-hbp configured ppd=$ppd"
else
debug_echo "no brlaser PPD found for non-raw queue"
fi
}
maybe_provision_hbp_async() {
queue_uri="$1"
[ -n "$queue_uri" ] || return 0
if [ "${CUPS_RUNTIME_HBP_ASYNC:-1}" = "1" ]; then
(
provision_hbp_queue "$queue_uri"
) &
debug_echo "scheduling test-hbp provisioning in background"
else
provision_hbp_queue "$queue_uri"
fi
}
provision_runtime_queue() {
[ -x /bin/lpadmin ] || return 1
[ -S "$SOCK" ] || return 1
queue_uri=""
usb_backend="/var/cups-serverbin/lib/cups/backend/usb"
if [ -x "$usb_backend" ]; then
queue_uri="$($usb_backend 2>/dev/null | awk '$1=="direct" && $2 ~ /^usb:\/\// {print $2; exit}')"
fi
if [ -z "$queue_uri" ] && [ -c /dev/usb/lp0 ]; then
queue_uri="file:///dev/usb/lp0"
fi
[ -n "$queue_uri" ] || return 1
/bin/lpadmin -h "$SOCK" -x test >/dev/null 2>&1 || true
/bin/lpadmin -h "$SOCK" -p test -E -v "$queue_uri" -m raw >> /log/cups.log 2>&1 || return 1
debug_echo "queue test configured uri=$queue_uri (raw)"
maybe_provision_hbp_async "$queue_uri"
return 0
}
if /bin/cupsd >> /log/cups.log 2>&1; then
if ! provision_runtime_queue; then
if [ "${CUPS_RUNTIME_QUEUE_RETRY:-0}" = "1" ]; then
debug_echo "no printer URI discovered; starting retry loop"
(
retries=90
while [ "$retries" -gt 0 ]; do
sleep 2
if provision_runtime_queue; then
exit 0
fi
retries=$((retries - 1))
done
debug_echo "queue provisioning timed out (no URI discovered)"
) &
else
debug_echo "no printer URI discovered; retry loop disabled"
fi
fi
debug_echo "scheduler started"
else
debug_echo "failed to start cupsd"
fi
log "bootstrap complete"