#!/bin/bash # /usr/local/bin/dos-shell # Prepare the SvarDOS environment and launch dosemu. set -euo pipefail ALLOWED_LIST="${DOS_ALLOWED_LIST:-/etc/dos_allowed}" ALLOWED_REPO="${DOS_ALLOWED_REPO:-/opt/allowed_repo}" EXTRA_DRIVE_ROOT="${DOS_EXTRA_DRIVE_ROOT:-/opt/dos_drives}" RESERVED_DRIVES_RAW="${DOS_RESERVED_DRIVES:-CDE}" ENV_DIR="/etc/dos_env" AUTOEXEC_TEMPLATE="${ENV_DIR}/AUTOEXEC.BAT" CONFIG_TEMPLATE="${ENV_DIR}/CONFIG.SYS" SVARDOS_ROOT="${SVARDOS_ROOT:-/opt/svardos}" SVARDOS_BASE="${SVARDOS_BASE:-${SVARDOS_ROOT}/base}" ALLOW_MODE="${DOS_ALLOW_MODE:-all}" DEFAULT_DOS_USER="dosuser" INSTALL_SENTINEL_NAME=".svardos_installed" PRE_BOOT_HOOK="${DOS_PRE_BOOT_HOOK:-${ENV_DIR}/pre-boot.sh}" LINUX_SHELL="${DOS_LINUX_SHELL:-/bin/bash}" if [ "$(id -u)" -eq 0 ]; then DOS_USER="${DEFAULT_DOS_USER}" DOS_HOME="$(getent passwd "${DOS_USER}" | cut -d: -f6)" else DOS_USER="$(id -un)" DOS_HOME="${HOME}" fi if [ -z "${DOS_HOME}" ]; then echo "Unable to determine home directory for ${DOS_USER}" >&2 exit 1 fi shell_passthrough=0 shell_payload="" if [ "${1:-}" = "--help" ]; then cat <<'EOF' Usage: dos-shell [--linux-shell [command]] Default invocation boots the DOS environment via dosemu. Use --linux-shell to open the underlying Linux shell instead. EOF exit 0 fi if [ "${1:-}" = "--linux-shell" ]; then shift shell_passthrough=1 if [ $# -gt 0 ]; then shell_payload="$*" fi fi if [ "${shell_passthrough}" -eq 0 ] && [ -n "${SSH_ORIGINAL_COMMAND:-}" ]; then case "${SSH_ORIGINAL_COMMAND}" in linux-shell) shell_passthrough=1 shell_payload="" ;; linux-shell[[:space:]]*) shell_passthrough=1 shell_payload="${SSH_ORIGINAL_COMMAND#linux-shell}" shell_payload="${shell_payload#"${shell_payload%%[![:space:]]*}"}" ;; esac fi if [ "${shell_passthrough}" -eq 1 ]; then if [ "$(id -u)" -eq 0 ]; then if [ -n "${shell_payload}" ]; then exec runuser -u "${DOS_USER}" -- "${LINUX_SHELL}" -lc "${shell_payload}" else exec runuser -u "${DOS_USER}" -- "${LINUX_SHELL}" -l fi else if [ -n "${shell_payload}" ]; then exec "${LINUX_SHELL}" -lc "${shell_payload}" else exec "${LINUX_SHELL}" -l fi fi fi DOSEMU_DIR="${DOS_HOME}/.dosemu" C_DRIVE="${DOSEMU_DIR}/drive_c" INSTALL_SENTINEL_PATH="${DOSEMU_DIR}/${INSTALL_SENTINEL_NAME}" DOSEMU_ARGS=() detect_dosemu_args() { local mode="${DOS_TERMINAL_MODE:-auto}" if [ "${mode}" = "auto" ]; then if [ -n "${DISPLAY:-}" ]; then mode="x" else mode="terminal" fi fi case "${mode,,}" in x|sdl|window|gui) DOSEMU_ARGS=(-X -K "${C_DRIVE}") ;; terminal|tty|text) DOSEMU_ARGS=(-t -K "${C_DRIVE}") ;; dumb) DOSEMU_ARGS=(-dumb -K "${C_DRIVE}") ;; *) echo "Unknown DOS_TERMINAL_MODE '${mode}', defaulting to terminal mode." >&2 DOSEMU_ARGS=(-t -K "${C_DRIVE}") ;; esac } allowed_entries=() extra_drive_logs=() declare -A reserved_letters=() mkdir -p "${DOSEMU_DIR}" "${C_DRIVE}" if [ "$(id -u)" -eq 0 ] && [ ! -d "${ENV_DIR}" ]; then mkdir -p "${ENV_DIR}" fi reset_drive_c() { if [ ! -d "${SVARDOS_BASE}" ] || [ -z "$(ls -A "${SVARDOS_BASE}")" ]; then echo "SvarDOS base directory ${SVARDOS_BASE} is empty. Aborting." >&2 exit 1 fi rm -rf "${C_DRIVE:?}/"* cp -a "${SVARDOS_BASE}/." "${C_DRIVE}/" if [ "$(id -u)" -eq 0 ]; then chown -R "${DOS_USER}:${DOS_USER}" "${DOSEMU_DIR}" fi if [ -f "${DOS_HOME}/.dosemurc" ]; then rm -f "${DOS_HOME}/.dosemurc" fi echo "SvarDOS base staged to ${C_DRIVE}" } normalise_reserved_drives() { local raw="$1" local cleaned="${raw//[^[:alpha:]]/}" local i letter for ((i=0; i<${#cleaned}; i++)); do letter="${cleaned:i:1}" letter="${letter,,}" reserved_letters["${letter}"]=1 done } normalise_reserved_drives "${RESERVED_DRIVES_RAW}" sync_allowed_content() { allowed_entries=() local mode="${ALLOW_MODE}" if [ "${mode}" = "all" ]; then if [ -d "${ALLOWED_REPO}" ] && [ "$(ls -A "${ALLOWED_REPO}" 2>/dev/null)" ]; then cp -a "${ALLOWED_REPO}/." "${C_DRIVE}/" allowed_entries+=("ALL") fi return fi if [ "${mode}" != "list" ] && [ "${mode}" != "allowlist" ]; then echo "Unknown DOS_ALLOW_MODE '${mode}', defaulting to allowlist mode." >&2 mode="list" ALLOW_MODE="${mode}" fi if [ -f "${ALLOWED_LIST}" ]; then while IFS= read -r line || [ -n "$line" ]; do line="$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" case "$line" in ''|\#*) continue ;; esac allowed_entries+=("$line") done < "${ALLOWED_LIST}" fi for entry in "${allowed_entries[@]}"; do src="${ALLOWED_REPO}/${entry}" if [ -e "$src" ]; then dst_dir="$(dirname "${entry}")" mkdir -p "${C_DRIVE}/${dst_dir}" cp -a "$src" "${C_DRIVE}/${dst_dir}/" else echo "Warning: allowed file not found: $src" >&2 fi done } sync_extra_drives() { extra_drive_logs=() local root="${EXTRA_DRIVE_ROOT}" local -A seen_letters=() if [ -d "${root}" ]; then shopt -s nullglob local entry base letter letter_lower letter_upper dest for entry in "${root}"/*; do if [ ! -d "$entry" ]; then continue fi base="$(basename "$entry")" case "$base" in [A-Za-z]|[A-Za-z]:) letter="${base:0:1}" ;; *) continue ;; esac letter_lower="${letter,,}" letter_upper="${letter^^}" if [ -n "${reserved_letters[${letter_lower}]+x}" ]; then echo "dos-shell: skipping extra drive '${base}' (${letter_upper}: is reserved)" >&2 continue fi dest="${DOSEMU_DIR}/drive_${letter_lower}" if [ -e "${dest}" ] && [ ! -L "${dest}" ]; then echo "dos-shell: cannot map ${letter_upper}: - ${dest} already exists and is not a symlink" >&2 continue fi if ! ln -sfn "$entry" "$dest"; then echo "dos-shell: failed to link ${letter_upper}: to ${entry}" >&2 continue fi seen_letters["${letter_lower}"]=1 extra_drive_logs+=("${letter_upper}: ${entry}") done shopt -u nullglob fi shopt -s nullglob local link base letter target for link in "${DOSEMU_DIR}"/drive_[[:lower:]]; do [ -L "$link" ] || continue base="$(basename "$link")" letter="${base#drive_}" if [ -z "$letter" ] || [ "$letter" = "c" ]; then continue fi if [ -n "${seen_letters[$letter]+x}" ]; then continue fi target="$(readlink "$link")" case "$target" in "${EXTRA_DRIVE_ROOT}"/*) rm -f "$link" ;; esac done shopt -u nullglob } apply_templates() { if [ -f "${AUTOEXEC_TEMPLATE}" ]; then cp "${AUTOEXEC_TEMPLATE}" "${C_DRIVE}/AUTOEXEC.BAT" fi if [ -f "${CONFIG_TEMPLATE}" ]; then cp "${CONFIG_TEMPLATE}" "${C_DRIVE}/CONFIG.SYS" fi } detect_dosemu_args run_pre_boot_hook() { if [ ! -e "${PRE_BOOT_HOOK}" ]; then return fi if [ ! -x "${PRE_BOOT_HOOK}" ]; then echo "dos-shell: pre-boot hook found but not executable: ${PRE_BOOT_HOOK}" >&2 return fi echo "dos-shell: running pre-boot hook: ${PRE_BOOT_HOOK}" >&2 if [ "$(id -u)" -eq 0 ]; then runuser -u "${DOS_USER}" -- env \ C_DRIVE="${C_DRIVE}" \ DOSEMU_DIR="${DOSEMU_DIR}" \ DOS_HOME="${DOS_HOME}" \ ALLOWED_REPO="${ALLOWED_REPO}" \ SVARDOS_ROOT="${SVARDOS_ROOT}" \ SVARDOS_BASE="${SVARDOS_BASE}" \ "${PRE_BOOT_HOOK}" else C_DRIVE="${C_DRIVE}" \ DOSEMU_DIR="${DOSEMU_DIR}" \ DOS_HOME="${DOS_HOME}" \ ALLOWED_REPO="${ALLOWED_REPO}" \ SVARDOS_ROOT="${SVARDOS_ROOT}" \ SVARDOS_BASE="${SVARDOS_BASE}" \ "${PRE_BOOT_HOOK}" fi } FORCE_CPU_EMULATION=0 detect_kvm_support() { if [ ! -c /dev/kvm ]; then return 1 fi if [ "$(id -u)" -eq 0 ]; then if command -v runuser >/dev/null 2>&1; then runuser -u "${DOS_USER}" -- test -r /dev/kvm 2>/dev/null || return 1 return 0 fi return 0 fi [ -r /dev/kvm ] } if ! detect_kvm_support; then echo "dos-shell: disabling KVM acceleration (no usable /dev/kvm)" >&2 FORCE_CPU_EMULATION=1 fi should_disable_audio=0 audio_mode="${DOS_AUDIO_MODE:-auto}" case "${audio_mode,,}" in mute|off|disabled) should_disable_audio=1 ;; force|on|enabled) should_disable_audio=0 ;; auto) if [ -n "${SSH_CONNECTION:-}" ]; then if [ -z "${PULSE_SERVER:-}" ] && [ -z "${PIPEWIRE_REMOTE:-}" ]; then should_disable_audio=1 fi fi ;; *) echo "Unknown DOS_AUDIO_MODE '${audio_mode}', defaulting to auto." >&2 if [ -n "${SSH_CONNECTION:-}" ] && [ -z "${PULSE_SERVER:-}" ] && [ -z "${PIPEWIRE_REMOTE:-}" ]; then should_disable_audio=1 fi ;; esac if [ "${should_disable_audio}" -eq 1 ]; then echo "dos-shell: muting DOS audio for this session" >&2 export AO_DRIVER="${AO_DRIVER:-null}" fi detect_landlock_support() { local header for header in /usr/include/linux/landlock.h \ /usr/include/uapi/linux/landlock.h \ /usr/lib*/gcc/*/*/include/uapi/linux/landlock.h; do if [ -f "$header" ] && grep -q 'LANDLOCK_ACCESS_FS_REFER' "$header" 2>/dev/null; then return 0 fi done return 1 } landlock_mode="${DOS_LANDLOCK_MODE:-auto}" case "${landlock_mode,,}" in auto) if detect_landlock_support; then : else echo "dos-shell: disabling Landlock sandbox (ABI mismatch detected)" >&2 DOSEMU_ARGS+=(-p) fi ;; off|disable|disabled) echo "dos-shell: Landlock sandbox disabled by DOS_LANDLOCK_MODE" >&2 DOSEMU_ARGS+=(-p) ;; force|on|enabled) ;; *) echo "Unknown DOS_LANDLOCK_MODE '${landlock_mode}', defaulting to auto." >&2 if detect_landlock_support; then : else echo "dos-shell: disabling Landlock sandbox (ABI mismatch detected)" >&2 DOSEMU_ARGS+=(-p) fi ;; esac write_autoconfig() { local need_config=0 local conf_path="${DOSEMU_DIR}/dosemurc" local legacy_conf="${DOS_HOME}/.dosemurc" local managed_marker="# Managed by dos-shell for compatibility" if [ "${FORCE_CPU_EMULATION}" -eq 1 ] || [ "${should_disable_audio}" -eq 1 ]; then need_config=1 fi if [ "${need_config}" -eq 1 ]; then if [ -f "${legacy_conf}" ] && grep -qF "${managed_marker}" "${legacy_conf}" 2>/dev/null; then rm -f "${legacy_conf}" fi local tmp_conf="${conf_path}.tmp" { echo "${managed_marker}" echo "define parser_version_3" if [ "${FORCE_CPU_EMULATION}" -eq 1 ]; then echo '$_cpu_vm = "emulated"' echo '$_cpu_vm_dpmi = "emulated"' fi if [ "${should_disable_audio}" -eq 1 ]; then echo '$_sound = (off)' echo '$_pcm_hpf = (off)' fi } > "${tmp_conf}" mv "${tmp_conf}" "${conf_path}" if [ "$(id -u)" -eq 0 ]; then chown "${DOS_USER}:${DOS_USER}" "${conf_path}" fi echo "dos-shell: wrote ${conf_path} overrides" >&2 else for candidate in "${conf_path}" "${legacy_conf}"; do if [ -f "${candidate}" ] && grep -qF "${managed_marker}" "${candidate}" 2>/dev/null; then rm -f "${candidate}" echo "dos-shell: removed autogenerated ${candidate}" >&2 fi done fi } if [ "${DOS_FORCE_INSTALL:-0}" = "1" ] || [ ! -f "${INSTALL_SENTINEL_PATH}" ]; then reset_drive_c touch "${INSTALL_SENTINEL_PATH}" if [ "$(id -u)" -eq 0 ]; then chown "${DOS_USER}:${DOS_USER}" "${INSTALL_SENTINEL_PATH}" fi else echo "Reusing existing SvarDOS installation on ${C_DRIVE}" >&2 fi sync_allowed_content sync_extra_drives apply_templates run_pre_boot_hook write_autoconfig if [ "$(id -u)" -eq 0 ]; then chown -R "${DOS_USER}:${DOS_USER}" "${DOSEMU_DIR}" fi if [ "${ALLOW_MODE}" = "all" ] && [ "${#allowed_entries[@]}" -gt 0 ]; then echo "All files in ${ALLOWED_REPO} are available on drive C:." elif [ "${#allowed_entries[@]}" -gt 0 ]; then echo "Allowlisted files copied to C: from ${ALLOWED_REPO}:" for entry in "${allowed_entries[@]}"; do echo " ${entry}" done fi if [ "${#extra_drive_logs[@]}" -gt 0 ]; then echo "Additional DOS drives mapped:" for mapping in "${extra_drive_logs[@]}"; do echo " ${mapping}" done fi if [ "$(id -u)" -eq 0 ]; then exec runuser -u "${DOS_USER}" -- dosemu "${DOSEMU_ARGS[@]}" else exec dosemu "${DOSEMU_ARGS[@]}" fi