Add configurable DOS drive volumes and tests
This commit is contained in:
parent
b82b1f94d4
commit
cc6d0f989d
6 changed files with 240 additions and 5 deletions
|
|
@ -36,9 +36,9 @@ RUN useradd -m -s /usr/local/bin/dos-shell dosuser && \
|
||||||
mkdir -p /home/dosuser/.ssh && chown -R dosuser:dosuser /home/dosuser && \
|
mkdir -p /home/dosuser/.ssh && chown -R dosuser:dosuser /home/dosuser && \
|
||||||
echo "dosuser:dosuser" | chpasswd
|
echo "dosuser:dosuser" | chpasswd
|
||||||
|
|
||||||
# Create directories for allowed DOS files and the C: drive mount
|
# Create directories for allowed DOS files, extra drives, and the C: drive mount
|
||||||
RUN mkdir -p /opt/allowed_repo /cdrive /etc/dos_env /opt/svardos && \
|
RUN mkdir -p /opt/allowed_repo /opt/dos_drives /cdrive /etc/dos_env /opt/svardos && \
|
||||||
chown -R dosuser:dosuser /opt/allowed_repo /cdrive
|
chown -R dosuser:dosuser /opt/allowed_repo /opt/dos_drives /cdrive
|
||||||
|
|
||||||
# Download and stage SvarDOS base files
|
# Download and stage SvarDOS base files
|
||||||
ARG SVARDOS_IMG_URL
|
ARG SVARDOS_IMG_URL
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ All persistent user data inside the guest lives under `/home/dosuser/.dosemu`, w
|
||||||
- **`config/dos_allowed`** – list relative paths (from `allowed_repo/`) to permit when `DOS_ALLOW_MODE=list`. With `DOS_ALLOW_MODE=all` every file in the repo is staged.
|
- **`config/dos_allowed`** – list relative paths (from `allowed_repo/`) to permit when `DOS_ALLOW_MODE=list`. With `DOS_ALLOW_MODE=all` every file in the repo is staged.
|
||||||
- **`dos_env/` templates** – place `AUTOEXEC.BAT` and/or `CONFIG.SYS` to control boot scripts.
|
- **`dos_env/` templates** – place `AUTOEXEC.BAT` and/or `CONFIG.SYS` to control boot scripts.
|
||||||
- **`dos_home/` volume** – bind-mounted to `/home/dosuser`; persists the DOS user profile, `.dosemu` state, SSH keys, etc.
|
- **`dos_home/` volume** – bind-mounted to `/home/dosuser`; persists the DOS user profile, `.dosemu` state, SSH keys, etc.
|
||||||
|
- **`dos_drives/` volume** – subdirectories named after drive letters (e.g. `F`, `G`) are exposed to DOS as additional drives. Letters `C`, `D`, and `E` are reserved by default (see `DOS_RESERVED_DRIVES` below); set that variable if you want to repurpose them.
|
||||||
- **Pre-boot hook** – create an executable `dos_env/pre-boot.sh` (or point `DOS_PRE_BOOT_HOOK` at another path). It runs as `dosuser` immediately before dosemu starts, with helper environment variables (`C_DRIVE`, `DOSEMU_DIR`, `ALLOWED_REPO`, `SVARDOS_ROOT`, `SVARDOS_BASE`) so you can copy, delete, or patch files on the DOS drive.
|
- **Pre-boot hook** – create an executable `dos_env/pre-boot.sh` (or point `DOS_PRE_BOOT_HOOK` at another path). It runs as `dosuser` immediately before dosemu starts, with helper environment variables (`C_DRIVE`, `DOSEMU_DIR`, `ALLOWED_REPO`, `SVARDOS_ROOT`, `SVARDOS_BASE`) so you can copy, delete, or patch files on the DOS drive.
|
||||||
- **Forcing a reinstall** – set `DOS_FORCE_INSTALL=1` in the environment before logging in; the script re-seeds the drive from `/opt/svardos/base`.
|
- **Forcing a reinstall** – set `DOS_FORCE_INSTALL=1` in the environment before logging in; the script re-seeds the drive from `/opt/svardos/base`.
|
||||||
|
|
||||||
|
|
@ -88,6 +89,10 @@ allowed_repo/
|
||||||
dos_home/
|
dos_home/
|
||||||
.ssh/
|
.ssh/
|
||||||
authorized_keys
|
authorized_keys
|
||||||
|
dos_drives/
|
||||||
|
F/
|
||||||
|
demos/
|
||||||
|
intro.exe
|
||||||
dos_env/
|
dos_env/
|
||||||
AUTOEXEC.BAT
|
AUTOEXEC.BAT
|
||||||
CONFIG.SYS
|
CONFIG.SYS
|
||||||
|
|
@ -107,6 +112,10 @@ You can influence runtime behaviour with environment variables. Set them either
|
||||||
| `DOS_TERMINAL_MODE` | `auto` | Determines whether `dosemu` launches with X11 (`-X`), terminal (`-td`) or `-dumb`. |
|
| `DOS_TERMINAL_MODE` | `auto` | Determines whether `dosemu` launches with X11 (`-X`), terminal (`-td`) or `-dumb`. |
|
||||||
| `DOS_ENV_DIR` | defaults to `/etc/dos_env` | Override if you mount templates somewhere else. |
|
| `DOS_ENV_DIR` | defaults to `/etc/dos_env` | Override if you mount templates somewhere else. |
|
||||||
| `DOS_PRE_BOOT_HOOK` | `${DOS_ENV_DIR}/pre-boot.sh` | Custom shell script to run (as `dosuser`) before launching dosemu. Must be executable. |
|
| `DOS_PRE_BOOT_HOOK` | `${DOS_ENV_DIR}/pre-boot.sh` | Custom shell script to run (as `dosuser`) before launching dosemu. Must be executable. |
|
||||||
|
| `DOS_EXTRA_DRIVE_ROOT` | `/opt/dos_drives` | Override where `dos-shell` looks for extra drive directories (one per drive letter). |
|
||||||
|
| `DOS_RESERVED_DRIVES` | `CDE` | Letters to leave untouched when mapping extra drives (remove or change to claim `D:`/`E:` etc.). |
|
||||||
|
| `DOS_ALLOWED_REPO` | `/opt/allowed_repo` | Alternate root for the allowed-repo staging area (useful in tests or custom mounts). |
|
||||||
|
| `DOS_ALLOWED_LIST` | `/etc/dos_allowed` | Path to the allowlist file consumed when `DOS_ALLOW_MODE=list`. |
|
||||||
| `SVARDOS_ROOT`/`SVARDOS_BASE` | default `/opt/svardos` | Changes where the base image lives (mostly useful during debugging). |
|
| `SVARDOS_ROOT`/`SVARDOS_BASE` | default `/opt/svardos` | Changes where the base image lives (mostly useful during debugging). |
|
||||||
| `AO_DRIVER` | auto-set to `null` when sound is muted | You can override to force libao to a specific backend. |
|
| `AO_DRIVER` | auto-set to `null` when sound is muted | You can override to force libao to a specific backend. |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,5 @@ services:
|
||||||
- ./config/dos_allowed:/etc/dos_allowed:ro
|
- ./config/dos_allowed:/etc/dos_allowed:ro
|
||||||
- ./dos_env:/etc/dos_env
|
- ./dos_env:/etc/dos_env
|
||||||
- ./dos_home:/home/dosuser
|
- ./dos_home:/home/dosuser
|
||||||
|
- ./dos_drives:/opt/dos_drives
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
|
||||||
2
dos_drives/.gitignore
vendored
Normal file
2
dos_drives/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
|
@ -4,8 +4,10 @@
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ALLOWED_LIST="/etc/dos_allowed"
|
ALLOWED_LIST="${DOS_ALLOWED_LIST:-/etc/dos_allowed}"
|
||||||
ALLOWED_REPO="/opt/allowed_repo"
|
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"
|
ENV_DIR="/etc/dos_env"
|
||||||
AUTOEXEC_TEMPLATE="${ENV_DIR}/AUTOEXEC.BAT"
|
AUTOEXEC_TEMPLATE="${ENV_DIR}/AUTOEXEC.BAT"
|
||||||
CONFIG_TEMPLATE="${ENV_DIR}/CONFIG.SYS"
|
CONFIG_TEMPLATE="${ENV_DIR}/CONFIG.SYS"
|
||||||
|
|
@ -113,6 +115,8 @@ detect_dosemu_args() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
allowed_entries=()
|
allowed_entries=()
|
||||||
|
extra_drive_logs=()
|
||||||
|
declare -A reserved_letters=()
|
||||||
|
|
||||||
mkdir -p "${DOSEMU_DIR}" "${C_DRIVE}"
|
mkdir -p "${DOSEMU_DIR}" "${C_DRIVE}"
|
||||||
if [ "$(id -u)" -eq 0 ] && [ ! -d "${ENV_DIR}" ]; then
|
if [ "$(id -u)" -eq 0 ] && [ ! -d "${ENV_DIR}" ]; then
|
||||||
|
|
@ -140,6 +144,19 @@ reset_drive_c() {
|
||||||
echo "SvarDOS base staged to ${C_DRIVE}"
|
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() {
|
sync_allowed_content() {
|
||||||
allowed_entries=()
|
allowed_entries=()
|
||||||
local mode="${ALLOW_MODE}"
|
local mode="${ALLOW_MODE}"
|
||||||
|
|
@ -179,6 +196,70 @@ sync_allowed_content() {
|
||||||
done
|
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() {
|
apply_templates() {
|
||||||
if [ -f "${AUTOEXEC_TEMPLATE}" ]; then
|
if [ -f "${AUTOEXEC_TEMPLATE}" ]; then
|
||||||
cp "${AUTOEXEC_TEMPLATE}" "${C_DRIVE}/AUTOEXEC.BAT"
|
cp "${AUTOEXEC_TEMPLATE}" "${C_DRIVE}/AUTOEXEC.BAT"
|
||||||
|
|
@ -364,6 +445,7 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sync_allowed_content
|
sync_allowed_content
|
||||||
|
sync_extra_drives
|
||||||
apply_templates
|
apply_templates
|
||||||
run_pre_boot_hook
|
run_pre_boot_hook
|
||||||
write_autoconfig
|
write_autoconfig
|
||||||
|
|
@ -381,6 +463,13 @@ elif [ "${#allowed_entries[@]}" -gt 0 ]; then
|
||||||
done
|
done
|
||||||
fi
|
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
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
exec runuser -u "${DOS_USER}" -- dosemu "${DOSEMU_ARGS[@]}"
|
exec runuser -u "${DOS_USER}" -- dosemu "${DOSEMU_ARGS[@]}"
|
||||||
else
|
else
|
||||||
|
|
|
||||||
134
tests/run-dos-shell-tests.sh
Normal file
134
tests/run-dos-shell-tests.sh
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
DOS_SHELL="${ROOT_DIR}/scripts/dos-shell"
|
||||||
|
|
||||||
|
if [ ! -x "${DOS_SHELL}" ]; then
|
||||||
|
echo "dos-shell not found or not executable at ${DOS_SHELL}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_root="$(mktemp -d)"
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "${tmp_root}"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
stub_bin="${tmp_root}/bin"
|
||||||
|
mkdir -p "${stub_bin}"
|
||||||
|
|
||||||
|
cat > "${stub_bin}/dosemu" <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
echo "dosemu stub invoked with: $*" >&2
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
chmod +x "${stub_bin}/dosemu"
|
||||||
|
|
||||||
|
run_case() {
|
||||||
|
local name="$1"
|
||||||
|
local allow_mode="$2"
|
||||||
|
local reserved="$3"
|
||||||
|
local expected_files="$4"
|
||||||
|
shift 4
|
||||||
|
local asserts=("$@")
|
||||||
|
|
||||||
|
local case_dir="${tmp_root}/${name}"
|
||||||
|
local home_dir="${case_dir}/home"
|
||||||
|
local base_dir="${case_dir}/svardos_base"
|
||||||
|
local allowed_repo="${case_dir}/allowed_repo"
|
||||||
|
local allow_list="${case_dir}/allowed_list"
|
||||||
|
local extra_root="${case_dir}/extra_drives"
|
||||||
|
|
||||||
|
mkdir -p "${home_dir}" "${base_dir}" "${allowed_repo}" "${extra_root}"
|
||||||
|
|
||||||
|
# Populate minimal base image
|
||||||
|
echo "base-${name}" > "${base_dir}/BASE.TXT"
|
||||||
|
|
||||||
|
# Populate allowed repo fixtures
|
||||||
|
echo "common-${name}" > "${allowed_repo}/common.txt"
|
||||||
|
mkdir -p "${allowed_repo}/subdir"
|
||||||
|
echo "sub-${name}" > "${allowed_repo}/subdir/sub.txt"
|
||||||
|
echo "denied-${name}" > "${allowed_repo}/denied.txt"
|
||||||
|
|
||||||
|
# Prepare allow list (filled later per mode)
|
||||||
|
: > "${allow_list}"
|
||||||
|
|
||||||
|
# Create extra drive scaffolding
|
||||||
|
mkdir -p "${extra_root}/F"
|
||||||
|
echo "driveF-${name}" > "${extra_root}/F/file.txt"
|
||||||
|
mkdir -p "${extra_root}/D"
|
||||||
|
echo "driveD-${name}" > "${extra_root}/D/file.txt"
|
||||||
|
|
||||||
|
# Additional file for list mode
|
||||||
|
if [ "${allow_mode}" = "list" ]; then
|
||||||
|
{
|
||||||
|
echo "common.txt"
|
||||||
|
echo "subdir/sub.txt"
|
||||||
|
} > "${allow_list}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Expected files in C: after sync
|
||||||
|
IFS=',' read -ra expected_array <<< "${expected_files}"
|
||||||
|
|
||||||
|
(
|
||||||
|
export PATH="${stub_bin}:${PATH}"
|
||||||
|
export HOME="${home_dir}"
|
||||||
|
export SVARDOS_ROOT="${case_dir}/svardos_root"
|
||||||
|
export SVARDOS_BASE="${base_dir}"
|
||||||
|
export DOS_FORCE_INSTALL=1
|
||||||
|
export DOS_ALLOW_MODE="${allow_mode}"
|
||||||
|
export DOS_EXTRA_DRIVE_ROOT="${extra_root}"
|
||||||
|
export DOS_RESERVED_DRIVES="${reserved}"
|
||||||
|
export DOS_ALLOWED_REPO="${allowed_repo}"
|
||||||
|
export DOS_ALLOWED_LIST="${allow_list}"
|
||||||
|
"${DOS_SHELL}" >/dev/null
|
||||||
|
)
|
||||||
|
|
||||||
|
local c_drive="${home_dir}/.dosemu/drive_c"
|
||||||
|
|
||||||
|
for rel_path in "${expected_array[@]}"; do
|
||||||
|
if [ ! -f "${c_drive}/${rel_path}" ]; then
|
||||||
|
echo "[${name}] expected file missing in C: ${rel_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for assert in "${asserts[@]}"; do
|
||||||
|
case "${assert}" in
|
||||||
|
expect_symlink:*)
|
||||||
|
local letter="${assert#expect_symlink:}"
|
||||||
|
local link="${home_dir}/.dosemu/drive_${letter,,}"
|
||||||
|
if [ ! -L "${link}" ]; then
|
||||||
|
echo "[${name}] expected symlink for drive ${letter^^}: not found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
expect_absent:*)
|
||||||
|
local letter="${assert#expect_absent:}"
|
||||||
|
local link="${home_dir}/.dosemu/drive_${letter,,}"
|
||||||
|
if [ -e "${link}" ]; then
|
||||||
|
echo "[${name}] expected drive ${letter^^}: to be absent, found $(ls -ld "${link}")" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
expect_no_file:*)
|
||||||
|
local rel="${assert#expect_no_file:}"
|
||||||
|
if [ -e "${c_drive}/${rel}" ]; then
|
||||||
|
echo "[${name}] unexpected file present in C: ${rel}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[${name}] OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_case "allow-all" "all" "CDE" "BASE.TXT,common.txt,subdir/sub.txt,denied.txt" \
|
||||||
|
"expect_symlink:f" "expect_absent:d"
|
||||||
|
|
||||||
|
run_case "allow-list-reserved-override" "list" "C" "BASE.TXT,common.txt,subdir/sub.txt" \
|
||||||
|
"expect_symlink:f" "expect_symlink:d" "expect_no_file:denied.txt"
|
||||||
|
|
||||||
|
echo "All tests passed."
|
||||||
Loading…
Add table
Add a link
Reference in a new issue