477 lines
12 KiB
Bash
Executable file
477 lines
12 KiB
Bash
Executable file
#!/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=(-td -K "${C_DRIVE}")
|
|
;;
|
|
dumb)
|
|
DOSEMU_ARGS=(-dumb -K "${C_DRIVE}")
|
|
;;
|
|
*)
|
|
echo "Unknown DOS_TERMINAL_MODE '${mode}', defaulting to terminal mode." >&2
|
|
DOSEMU_ARGS=(-td -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
|