Files
Das-OS/srcpkgs/das-helpers/files/dprofile
T
dasandClaude Opus 5.5 fe043711a0 Stage 8: developer profile with dprofile (0.6.0)
dprofile (das-helpers 0.2.0) installs profiles listed in
/usr/share/das/profiles: packages from the Void repository between a
snapper pre/post pair, their services and groups, subordinate IDs and a
shared root mount for rootless Podman, and optional IDEs from Flathub.
The installed system can't see the Das OS repository yet, so a das-dev
metapackage could not be installed there; Void packages always can.

The dev profile: Go, Python, Node.js, Git, Docker, Podman and command-line
tools everywhere, VS Code (code-oss), Meld and Flatpak on a desktop, with
Android Studio and JetBrains IDEs on demand. Questions come before sudo.

das-welcome (das-tools 0.4.0) gets a developer profile row that runs
dprofile in a terminal; das-server-install mentions it at the end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 08:49:53 +03:00

417 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# dprofile - install Das OS profiles: sets of packages from the Void
# repository with their services and groups, plus optional applications from
# Flathub. The profiles are in /usr/share/das/profiles/NAME.conf.
set -euo pipefail
# shellcheck source=helpers.sh
. "${DAS_HELPERS:-/usr/lib/das/helpers.sh}"
profiles_dir=${DAS_PROFILES:-/usr/share/das/profiles}
flathub=https://dl.flathub.org/repo/flathub.flatpakrepo
usage() {
if [[ -n $uk ]]; then
cat <<'EOF'
Використання: dprofile КОМАНДА [ПРОФІЛЬ] [...]
list профілі та їхній стан (типова команда)
show ПРОФІЛЬ пакети, служби й додаткові застосунки профілю
install ПРОФІЛЬ встановити профіль (зі знімком системи для відкату):
-y, --yes без запитань
--apps ID,...|all|none
які застосунки з Flathub додати (інакше запитає)
apps ПРОФІЛЬ додаткові застосунки профілю з Flathub
app add ID... встановити застосунки з Flathub (ID або його кінець:
AndroidStudio, GoLand)
app rm ID... видалити їх
Профіль dev: dprofile install dev
EOF
else
cat <<'EOF'
Usage: dprofile COMMAND [PROFILE] [...]
list profiles and their state (the default)
show PROFILE the profile's packages, services and optional apps
install PROFILE install a profile (with a system snapshot to roll back to):
-y, --yes ask nothing
--apps ID,...|all|none
which Flathub applications to add (asks otherwise)
apps PROFILE the profile's optional applications from Flathub
app add ID... install applications from Flathub (an ID or its end:
AndroidStudio, GoLand)
app rm ID... remove them
The dev profile: dprofile install dev
EOF
fi
}
desktop() {
command -v Xorg >/dev/null
}
live() {
grep -q 'root=live:' /proc/cmdline 2>/dev/null
}
profile_names() {
local f
for f in "$profiles_dir"/*.conf; do
[[ -e $f ]] || continue
f=${f##*/}
echo "${f%.conf}"
done
}
# load NAME: reads the profile into title, desc, packages, services, groups,
# subids, shared_root and apps (one "ID NAME" per line)
load() {
local f=$profiles_dir/$1.conf
[[ $1 =~ ^[a-z0-9-]+$ && -r $f ]] ||
die "немає профілю '$1' (dprofile list)" "no profile '$1' (dprofile list)"
title_uk="" title_en="" desc_uk="" desc_en="" server_desc_uk="" server_desc_en=""
packages="" desktop_packages="" services="" groups="" subids="" shared_root="" apps=""
# shellcheck source=profiles/dev.conf
. "$f"
title=$(say "$title_uk" "$title_en")
desc=$(say "$desc_uk" "$desc_en")
if desktop; then
packages+=" $desktop_packages"
else
apps=""
[[ -z $server_desc_en ]] || desc=$(say "$server_desc_uk" "$server_desc_en")
fi
# shellcheck disable=SC2086
packages=$(printf '%s\n' $packages)
apps=$(grep . <<<"$apps" || true)
}
installed() {
xbps-query "$1" >/dev/null 2>&1
}
# Packages of the loaded profile that are not installed yet
missing_packages() {
local p
while read -r p; do
installed "$p" || echo "$p"
done <<<"$packages"
}
app_installed() {
command -v flatpak >/dev/null && flatpak info --system "$1" >/dev/null 2>&1
}
cmd_list() {
local name total missing state
for name in $(profile_names); do
load "$name"
total=$(grep -c . <<<"$packages")
missing=$(missing_packages | grep -c . || true)
if ((missing == 0)); then
state=$(say "встановлено" "installed")
elif ((missing < total)); then
state=$(say "частково: бракує $missing з $total пакетів" "partly: $missing of $total packages missing")
else
state=$(say "не встановлено" "not installed")
fi
pad "$name" 8
echo "$title — $state"
echo " $desc"
done
}
cmd_show() {
local p id name
load "$1"
echo "$title: $desc"
say "Пакети (* — уже встановлені):" "Packages (* marks the installed ones):"
while read -r p; do
if installed "$p"; then echo " * $p"; else echo " $p"; fi
done <<<"$packages"
[[ -z $services ]] || say "Служби: $services" "Services: $services"
[[ -z $groups ]] || say "Групи користувача: $groups" "User groups: $groups"
if [[ -n $apps ]]; then
say "Додатково з Flathub (dprofile app add ID):" "Optional, from Flathub (dprofile app add ID):"
while read -r id name; do
if app_installed "$id"; then echo " * $(pad "$name" 30)$id"; else echo " $(pad "$name" 30)$id"; fi
done <<<"$apps"
fi
}
cmd_apps() {
local id name
load "$1"
[[ -n $apps ]] || {
say "У профілю $1 немає додаткових застосунків (або тут немає графічного сеансу)." \
"The $1 profile has no optional applications (or there is no graphical session here)."
return
}
while read -r id name; do
if app_installed "$id"; then echo "* $(pad "$name" 30)$id"; else echo " $(pad "$name" 30)$id"; fi
done <<<"$apps"
}
# resolve_app SPEC: the Flathub ID of SPEC, a full ID or its last part in any case
resolve_app() {
local spec=$1 name id found=""
if [[ $spec == *.*.* ]]; then
echo "$spec"
return
fi
shopt -s nocasematch
for name in $(profile_names); do
load "$name"
while read -r id _; do
[[ -n $id && ${id##*.} == "$spec" ]] && found=$id
done <<<"$apps"
done
shopt -u nocasematch
[[ -n $found ]] || die "невідомий застосунок '$spec': повний ID з Flathub або назва з dprofile apps" \
"unknown application '$spec': a full Flathub ID or a name from dprofile apps"
echo "$found"
}
setup_flathub() {
flatpak remote-add --system --if-not-exists flathub "$flathub"
}
# app_add ID...: installs Flathub applications system-wide
app_add() {
(($# > 0)) || return 0
command -v flatpak >/dev/null ||
die "немає flatpak: спершу dprofile install dev" "flatpak is missing: dprofile install dev first"
setup_flathub
say "Встановлюю з Flathub: $*" "Installing from Flathub: $*"
# The exports are in the users' XDG_DATA_DIRS (/etc/profile.d/flatpak.sh),
# not in root's: without them flatpak warns that the menu won't find the apps
XDG_DATA_DIRS=/var/lib/flatpak/exports/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share} \
flatpak install --system --noninteractive -y flathub "$@"
say "Застосунки з'являться в меню після наступного входу." "The applications appear in the menu after the next login."
}
cmd_app() {
local action=${1:-} ids=() spec
shift || true
desktop || die "застосунки з Flathub — лише для системи з графічним сеансом" \
"Flathub applications are only for a system with a graphical session"
(($# > 0)) || die "вкажіть застосунок: dprofile app $action GoLand" "give an application: dprofile app $action GoLand"
for spec in "$@"; do
ids+=("$(resolve_app "$spec")")
done
case $action in
add | install)
need_root app add "${ids[@]}"
app_add "${ids[@]}"
;;
rm | remove)
need_root app rm "${ids[@]}"
flatpak uninstall --system --noninteractive -y "${ids[@]}"
# The runtimes no other application uses take most of the space
flatpak uninstall --system --noninteractive -y --unused
;;
*) die "dprofile app add|rm ID..." "dprofile app add|rm ID..." ;;
esac
}
# choose_apps: asks which optional applications to install into "chosen"
choose_apps() {
local i=0 id name ids=() reply n ok
say "Додаткові середовища з Flathub (великі: 1–4 ГБ разом із рантаймами Flatpak):" \
"Optional IDEs from Flathub (large: 1-4 GB with the Flatpak runtimes):"
while read -r id name; do
ids+=("$id")
i=$((i + 1))
if app_installed "$id"; then
echo " $i) $(pad "$name" 30)$(say "уже встановлено" "already installed")"
else
echo " $i) $name"
fi
done <<<"$apps"
while true; do
chosen=()
ok=1
read -r -p "$(say "Номери через пробіл (Enter — жодного): " "Numbers separated by spaces (Enter for none): ")" reply || return 0
for n in ${reply//,/ }; do
if [[ $n =~ ^[0-9]+$ ]] && ((n >= 1 && n <= ${#ids[@]})); then
chosen+=("${ids[n - 1]}")
else
warn "'$n': номер від 1 до ${#ids[@]}" "'$n': a number from 1 to ${#ids[@]}"
ok=""
fi
done
[[ -z $ok ]] || return 0
done
}
# snapper pre/post pair around the installation, when snapshots are set up
snapshot_pre() {
[[ -e /etc/snapper/configs/root ]] || return 0
snapper -c root create --type pre --print-number --cleanup-algorithm number \
--description "dprofile install $1"
}
snapshot_post() {
[[ -n $1 ]] || return 0
snapper -c root create --type post --pre-number "$1" --cleanup-algorithm number \
--description "dprofile install $2" || true
}
# add_subids USER: subordinate IDs for rootless containers, after the
# highest range already given out
add_subids() {
local user=$1 file start
for file in /etc/subuid /etc/subgid; do
touch "$file"
grep -q "^$user:" "$file" && continue
start=$(awk -F: 'BEGIN { m = 100000 } { if ($2 + $3 > m) m = $2 + $3 } END { print m }' "$file")
if [[ $file == /etc/subuid ]]; then
usermod --add-subuids "$start-$((start + 65535))" "$user"
else
usermod --add-subgids "$start-$((start + 65535))" "$user"
fi
done
}
# Mount propagation for rootless containers, at every boot and right now
make_root_shared() {
local f=/etc/runit/core-services/60-das-shared-root.sh
[[ -e $f ]] && return
cat >"$f" <<'EOF'
# Created by dprofile: rootless Podman wants / to be a shared mount
mount --make-rshared /
EOF
mount --make-rshared /
}
cmd_install() {
local name="" yes="" asked="" app_spec="" user=${SUDO_USER:-} missing chosen=() id pre="" sv g joined=""
while (($# > 0)); do
case $1 in
-y | --yes) yes=1 ;;
--asked) asked=1 ;;
--apps) app_spec=${2:-}; shift ;;
--apps=*) app_spec=${1#--apps=} ;;
-*) die "невідомий параметр $1" "unknown option $1" ;;
*) name=$1 ;;
esac
shift
done
[[ -n $name ]] || die "вкажіть профіль: dprofile install dev" "give a profile: dprofile install dev"
load "$name"
live && die "це live-сесія: спершу встановіть Das OS на диск" "this is a live session: install Das OS to a disk first"
[[ $user == root ]] && user=""
missing=$(missing_packages | xargs)
# The questions come before sudo, so the password is asked for a known plan;
# --asked is the run through sudo after them
if [[ -z $asked ]]; then
echo "$title: $desc"
if [[ -n $missing ]]; then
say "Буде встановлено: $missing" "To be installed: $missing"
else
say "Усі пакети вже встановлено." "All packages are installed already."
fi
if [[ -n $apps && -z $app_spec && -z $yes ]] && interactive; then
choose_apps
app_spec=$(IFS=,; echo "${chosen[*]}")
app_spec=${app_spec:-none}
fi
if [[ -z $yes ]] && interactive; then
confirm "Продовжити?" "Continue?" || exit 1
fi
fi
need_root install "$name" --asked ${app_spec:+--apps "$app_spec"}
chosen=()
if [[ -n $apps ]]; then
case $app_spec in
"" | none) ;;
all) mapfile -t chosen < <(awk '{ print $1 }' <<<"$apps") ;;
*)
for id in ${app_spec//,/ }; do
chosen+=("$(resolve_app "$id")")
done
;;
esac
fi
if [[ -n $missing ]]; then
xbps-install -S
# xbps refuses to install anything while an update of itself is pending
if xbps-install -un 2>/dev/null | grep -q '^xbps-[0-9]'; then
xbps-install -uy xbps
fi
pre=$(snapshot_pre "$name") ||
die "не вдалося створити знімок системи, нічого не встановлено" "could not take a system snapshot, nothing was installed"
# shellcheck disable=SC2086
if ! xbps-install -y $missing; then
snapshot_post "$pre" "$name"
die "xbps-install не вдався" "xbps-install failed"
fi
snapshot_post "$pre" "$name"
fi
for sv in $services; do
if [[ -d /etc/sv/$sv && ! -e /etc/runit/runsvdir/default/$sv ]]; then
ln -s "/etc/sv/$sv" /etc/runit/runsvdir/default/
say "Служба $sv увімкнена" "Service $sv enabled"
fi
done
[[ -z $shared_root ]] || make_root_shared
if [[ -n $user ]]; then
for g in $groups; do
if getent group "$g" >/dev/null && ! id -nG "$user" | grep -qw "$g"; then
gpasswd -a "$user" "$g" >/dev/null
joined+=" $g"
fi
done
[[ -n $subids ]] && add_subids "$user"
[[ -z $joined ]] || say "$user тепер у групах:$joined (діє з наступного входу)" \
"$user is now in the groups:$joined (from the next login)"
elif [[ -n $groups ]]; then
say "Додайте користувачів до груп самі: duser groups КОРИСТУВАЧ +${groups// / +}" \
"Add users to the groups yourself: duser groups USER +${groups// / +}"
fi
if command -v flatpak >/dev/null && desktop; then
setup_flathub
app_add "${chosen[@]}"
fi
say "Профіль $name встановлено." "The $name profile is installed."
[[ -z $pre ]] || say "Відкат: у меню завантаження «Das OS snapshots» → знімок №$pre, потім sudo snapper-rollback $pre." \
"To roll back: \"Das OS snapshots\" → snapshot #$pre in the boot menu, then sudo snapper-rollback $pre."
}
cmd=${1:-list}
[[ $# -gt 0 ]] && shift
case $cmd in
list | ls) cmd_list ;;
show | info)
[[ $# -gt 0 ]] || die "вкажіть профіль: dprofile show dev" "give a profile: dprofile show dev"
cmd_show "$1"
;;
# Quiet, for das-welcome: success when every package is installed
check)
load "${1:-}"
[[ -z $(missing_packages) ]]
;;
install) cmd_install "$@" ;;
apps)
[[ $# -gt 0 ]] || die "вкажіть профіль: dprofile apps dev" "give a profile: dprofile apps dev"
cmd_apps "$1"
;;
app) cmd_app "$@" ;;
-h | --help | help) usage ;;
*)
usage >&2
exit 1
;;
esac