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:
@@ -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.
|
||||
Executable
+279
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user