Stage 6: server image with a console installer (0.4.0)

- server/ profile and build-iso.sh [desktop|server]; make iso-server,
  make qemu-server, run-qemu.sh --serial; login on tty and ttyS0
- das-server: SSH, chrony, NetworkManager/dhcpcd, Docker with Compose,
  nftables (closed except SSH, own table), fail2ban reading socklog,
  sshd without root passwords, Docker log rotation, tools
- das-server-install: dialog installer in Ukrainian and English, or
  unattended with --config; GPT + Btrfs subvolumes, GRUB for UEFI and BIOS,
  serial console, SSH key with optional keys-only logins
- das-install-common: mount-source and postinstall shared with Calamares
- dsv: runit service manager (list, enable, disable, start, stop,
  restart, status, logs) with bash completion, also on the desktop
- das-update split into a subpackage without GTK; the window and tray
  icon move to /usr/libexec/das/das-update-gui
- das-snapper-setup enables dbus, which snapper needs
- TODO: ISO size review with measured numbers and options

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
das
2026-10-19 21:28:26 +03:00
co-authored by Claude Opus 5.5
parent 5349a028c8
commit eba4516998
42 changed files with 1454 additions and 114 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Das
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+279
View File
@@ -0,0 +1,279 @@
#!/bin/bash
# dsv - manage runit services on Das OS, the way systemctl does elsewhere.
#
# A service is a directory /etc/sv/<name>; it is enabled (started at boot) by
# linking it into the default runlevel, which runsvdir watches through
# /var/service. Logs come from the service's own svlogd directory or, for
# services that log to syslog, from socklog.
set -euo pipefail
sv_dir=/etc/sv
runlevel=/etc/runit/runsvdir/default
# Outside a running system (a chroot, an image) /var/service may not exist
service_dir=/var/service
[[ -d $service_dir ]] || service_dir=$runlevel
socklog=/var/log/socklog
if [[ ${LANG:-} == uk* || ${LC_ALL:-} == uk* || ${LC_MESSAGES:-} == uk* ]]; then
uk=1
else
uk=""
fi
# say UKRAINIAN ENGLISH: prints the message in the user's language
say() {
if [[ -n $uk ]]; then echo "$1"; else echo "$2"; fi
}
die() {
say "dsv: $1" "dsv: $2" >&2
exit 1
}
usage() {
if [[ -n $uk ]]; then
cat <<'EOF'
Використання: dsv КОМАНДА [СЛУЖБА...]
list усі служби: чи ввімкнена, стан, PID, час роботи
status СЛУЖБА... стан служби (sv status)
enable СЛУЖБА... запускати під час завантаження й запустити зараз
disable СЛУЖБА... не запускати під час завантаження й зупинити
start|stop|restart СЛУЖБА...
запустити, зупинити, перезапустити
logs СЛУЖБА [-f] журнал служби (-f: стежити за новими рядками)
Служби — каталоги в /etc/sv. Без команди виконується list.
EOF
else
cat <<'EOF'
Usage: dsv COMMAND [SERVICE...]
list all services: enabled or not, state, PID, uptime
status SERVICE... service state (sv status)
enable SERVICE... start at boot and start now
disable SERVICE... do not start at boot and stop now
start|stop|restart SERVICE...
start, stop, restart
logs SERVICE [-f] the service log (-f: follow new lines)
Services are the directories in /etc/sv. Without a command, runs list.
EOF
fi
}
# Service states live in root-only supervise directories
need_root() {
if [[ $EUID -ne 0 ]]; then
exec sudo "$0" "$@"
fi
}
check_service() {
[[ -d $sv_dir/$1 ]] || die "немає служби '$1' (див. ls $sv_dir)" "no service '$1' (see ls $sv_dir)"
}
is_enabled() {
[[ -e $runlevel/$1 || -L $runlevel/$1 ]]
}
# Waits up to 10 s for runsvdir to pick up a new link (it rescans every 5 s)
wait_supervised() {
local i
for ((i = 0; i < 20; i++)); do
[[ -e $service_dir/$1/supervise/ok ]] && return 0
sleep 0.5
done
return 1
}
# row NAME ENABLED STATE PID UPTIME: one line of the list. printf pads by
# bytes, ${#s} counts characters, so Cyrillic columns stay aligned.
row() {
local s widths=(24 9 6 8) i=0
for s in "$1" "$2" "$3" "$4"; do
printf '%s%*s ' "$s" $((widths[i] - ${#s})) ''
i=$((i + 1))
done
printf '%s\n' "$5"
}
cmd_list() {
local dir name enabled state pid time line
row "$(say СЛУЖБА SERVICE)" "$(say УВІМКН. ENABLED)" "$(say СТАН STATE)" PID "$(say ПРАЦЮЄ UPTIME)"
for dir in "$sv_dir"/*/; do
name=$(basename "$dir")
enabled=$(say ні no)
state=-
pid=-
time=-
if is_enabled "$name"; then
enabled=$(say так yes)
# "run: /var/service/sshd: (pid 812) 3600s; run: log: ..."
line=$(sv status "$service_dir/$name" 2>/dev/null || true)
state=${line%%:*}
[[ $line =~ \(pid\ ([0-9]+)\) ]] && pid=${BASH_REMATCH[1]}
[[ $line =~ \ ([0-9]+)s ]] && time=$(human_time "${BASH_REMATCH[1]}")
[[ -n $state ]] || state='?'
fi
row "$name" "$enabled" "$state" "$pid" "$time"
done
}
# human_time SECONDS: 3d4h, 2h5m, 7m, 42s
human_time() {
local s=$1
if ((s >= 86400)); then
echo "$((s / 86400))d$((s % 86400 / 3600))h"
elif ((s >= 3600)); then
echo "$((s / 3600))h$((s % 3600 / 60))m"
elif ((s >= 60)); then
echo "$((s / 60))m"
else
echo "${s}s"
fi
}
cmd_status() {
local name rc=0
for name in "$@"; do
check_service "$name"
if is_enabled "$name"; then
sv status "$service_dir/$name" || rc=1
else
say "$name: вимкнена (dsv enable $name)" "$name: disabled (dsv enable $name)"
fi
done
return $rc
}
cmd_enable() {
local name
for name in "$@"; do
check_service "$name"
if is_enabled "$name"; then
say "$name: уже ввімкнена" "$name: already enabled"
continue
fi
ln -s "$sv_dir/$name" "$runlevel/$name"
say "$name: увімкнена" "$name: enabled"
# runsvdir starts it within 5 s. Right after "dsv disable" its runsv is
# still there with the service stopped, so ask for "up" as well.
if [[ $service_dir == /var/service ]] && wait_supervised "$name"; then
sv -v up "$service_dir/$name" || true
fi
done
}
cmd_disable() {
local name
for name in "$@"; do
check_service "$name"
if ! is_enabled "$name"; then
say "$name: уже вимкнена" "$name: already disabled"
continue
fi
# Stop it first: runsv only notices the removed link at runsvdir's next scan
[[ $service_dir == /var/service ]] && sv down "$service_dir/$name" 2>/dev/null || true
rm "$runlevel/$name"
say "$name: вимкнена" "$name: disabled"
done
}
cmd_control() {
local action=$1 name rc=0
shift
for name in "$@"; do
check_service "$name"
if ! is_enabled "$name"; then
say "$name: вимкнена, спершу: dsv enable $name" "$name: disabled, first: dsv enable $name" >&2
rc=1
continue
fi
# -v waits for the new state (up to 7 s) and prints it
sv -v "$action" "$service_dir/$name" || rc=1
done
return $rc
}
# The log directory of a service whose log/run starts svlogd, if any
svlogd_dir() {
local run=$sv_dir/$1/log/run words w
[[ -r $run ]] || return 1
grep -q svlogd "$run" || return 1
read -ra words < <(grep -m1 svlogd "$run")
for w in "${words[@]}"; do
[[ $w == /* && -d $w ]] && echo "$w" && return 0
done
return 1
}
cmd_logs() {
local name=${1:-} follow="" dir
[[ -n $name ]] || die "вкажіть службу: dsv logs СЛУЖБА [-f]" "name a service: dsv logs SERVICE [-f]"
[[ ${2:-} == -f ]] && follow=1
check_service "$name"
if dir=$(svlogd_dir "$name"); then
if [[ -n $follow ]]; then
exec tail -n 50 -F "$dir/current"
fi
cat "$dir"/@*.[su] "$dir/current" 2>/dev/null | tail -n 200
return
fi
# Services without their own log write to syslog (vlogger -t <name>, or the
# daemon itself as "<name>[pid]:" or "<name>-<helper>[pid]:", like
# sshd-session), which socklog sorts into directories by facility.
if ! compgen -G "$socklog/*/current" >/dev/null; then
die "у $name немає власного журналу, а syslog не записується; увімкніть socklog: dsv enable socklog-unix nanoklogd" \
"$name has no log of its own and syslog is not recorded; enable socklog: dsv enable socklog-unix nanoklogd"
fi
local pattern="[[:space:]]${name}(-[a-z]+)?(\[[0-9]+\])?:"
if [[ -n $follow ]]; then
tail -q -n 0 -F "$socklog"/*/current | grep --line-buffered -E "$pattern"
else
# A message can be in several directories (e.g. "everything" and "secure")
cat "$socklog"/*/@*.[su] "$socklog"/*/current 2>/dev/null | grep -E "$pattern" | sort -u | tail -n 200 ||
say "$name: у syslog немає записів (служба може писати лише в консоль runit)" \
"$name: no syslog entries (the service may only write to the runit console)"
fi
}
cmd=${1:-list}
[[ $# -gt 0 ]] && shift
case $cmd in
list | ls)
need_root list "$@"
cmd_list
;;
status)
[[ $# -gt 0 ]] || die "вкажіть службу" "name a service"
need_root status "$@"
cmd_status "$@"
;;
enable | disable | start | stop | restart)
[[ $# -gt 0 ]] || die "вкажіть службу" "name a service"
need_root "$cmd" "$@"
case $cmd in
enable) cmd_enable "$@" ;;
disable) cmd_disable "$@" ;;
start) cmd_control up "$@" ;;
stop) cmd_control down "$@" ;;
restart) cmd_control restart "$@" ;;
esac
;;
logs | log)
# socklog's logs are readable by root and the socklog group only
[[ -r $socklog/messages/current ]] || need_root logs "$@"
cmd_logs "$@"
;;
-h | --help | help)
usage
;;
*)
usage >&2
exit 1
;;
esac
+32
View File
@@ -0,0 +1,32 @@
# bash completion for dsv
# shellcheck shell=bash
_dsv() {
local cur=${COMP_WORDS[COMP_CWORD]} cmd=${COMP_WORDS[1]:-}
if ((COMP_CWORD == 1)); then
mapfile -t COMPREPLY < <(compgen -W "list status enable disable start stop restart logs help" -- "$cur")
return
fi
case $cmd in
enable)
# services that are not enabled yet
local all=() s
for s in /etc/sv/*/; do
s=${s%/}
s=${s##*/}
[[ -e /etc/runit/runsvdir/default/$s ]] || all+=("$s")
done
mapfile -t COMPREPLY < <(compgen -W "${all[*]}" -- "$cur")
;;
disable | start | stop | restart)
mapfile -t COMPREPLY < <(compgen -W "$(ls /etc/runit/runsvdir/default 2>/dev/null)" -- "$cur")
;;
status | logs | log)
if [[ $cmd == logs* ]] && ((COMP_CWORD == 3)); then
mapfile -t COMPREPLY < <(compgen -W "-f" -- "$cur")
else
mapfile -t COMPREPLY < <(compgen -W "$(ls /etc/sv 2>/dev/null)" -- "$cur")
fi
;;
esac
}
complete -F _dsv dsv
+15
View File
@@ -0,0 +1,15 @@
# Template file for 'dsv'
pkgname=dsv
version=0.1.0
revision=1
depends="bash runit sudo"
short_desc="Das OS service manager: enable, start and read logs of runit services"
maintainer="Das <anry.das@gmail.com>"
license="MIT"
homepage="https://git.das.org.ua/das/Das-OS"
do_install() {
vbin ${FILESDIR}/dsv
vcompletion ${FILESDIR}/dsv.bash-completion bash
vlicense ${FILESDIR}/LICENSE
}