Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8db767efb9 | ||
|
|
1a62ddaeb8 | ||
|
|
7e7bcbe017 | ||
|
|
60af93df74 | ||
|
|
396986aaf4 | ||
|
|
62b8b98086 | ||
|
|
ea62ec7a26 | ||
|
|
22dc7832bc | ||
|
|
e01e1786d6 | ||
|
|
6564d1eb17 | ||
|
|
94b1bf5f8b | ||
|
|
a2bffc7fe9 | ||
|
|
66ea614e0d | ||
|
|
f3793d468c | ||
|
|
dbfb104d89 | ||
|
|
125f9027c4 | ||
|
|
fe043711a0 | ||
|
|
acb7532a4f | ||
|
|
84768962fc | ||
|
|
eba4516998 | ||
|
|
5349a028c8 | ||
|
|
d1d76c2bef | ||
|
|
3b4527990e | ||
|
|
6d53240b75 | ||
|
|
5d478822ac | ||
|
|
ecced4e3f4 |
@@ -0,0 +1,39 @@
|
||||
# Builds the Das OS packages and both ISO images on the CI host (see ci/runner).
|
||||
# Build output stays on the host in DASOS_OUT, set in the runner's config.
|
||||
# A v* tag also publishes the images as a release (ci/release.sh).
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
tags: ["v*"]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: build-${{ gitea.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: dasci
|
||||
permissions:
|
||||
contents: write # ci/release.sh creates releases
|
||||
steps:
|
||||
- uses: builtin:checkout
|
||||
|
||||
# One make run builds the packages once for both images.
|
||||
# Go tests run inside the das-tools and das-theme package builds.
|
||||
- name: Packages and ISO images
|
||||
run: make iso iso-server
|
||||
|
||||
- name: Result
|
||||
run: ls -lh "$DASOS_OUT/iso/"*-"$(cat VERSION)"-x86_64.iso
|
||||
|
||||
# A v* tag publishes both images and their checksums as a release
|
||||
- name: Release
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
env:
|
||||
GITEA_TOKEN: ${{ gitea.token }}
|
||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||
run: ci/release.sh "${{ gitea.ref_name }}" "$DASOS_OUT/iso"
|
||||
@@ -6,3 +6,7 @@ srcpkgs/das-theme/files/xfwm4gen/xfwm4gen
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
|
||||
# Python
|
||||
.venv/
|
||||
__pycache__/
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# Das OS build entry points. Everything except QEMU runs in the Void builder container.
|
||||
|
||||
.PHONY: help packages iso qemu qemu-bios qemu-disk test clean
|
||||
.PHONY: help packages iso iso-server qemu qemu-bios qemu-disk qemu-server test clean
|
||||
|
||||
help:
|
||||
@echo "make packages build srcpkgs/* into the local repository (out/hostdir/binpkgs)"
|
||||
@echo "make iso build packages, then the live ISO (out/iso)"
|
||||
@echo "make iso-server build packages, then the server ISO (out/iso)"
|
||||
@echo "make qemu boot the newest ISO in QEMU with UEFI"
|
||||
@echo "make qemu-bios boot the newest ISO in QEMU with legacy BIOS"
|
||||
@echo "make qemu-disk boot with a 20 GB virtual disk attached (for installer tests)"
|
||||
@echo "make test run Go tests"
|
||||
@echo "make qemu-server boot the newest server ISO on a serial console, with the disk"
|
||||
@echo "make test run Go and Python tests"
|
||||
@echo "make clean remove build output (keeps download caches)"
|
||||
|
||||
packages:
|
||||
@@ -17,6 +19,9 @@ packages:
|
||||
iso: packages
|
||||
scripts/builder.sh /work/scripts/container/build-iso.sh
|
||||
|
||||
iso-server: packages
|
||||
scripts/builder.sh /work/scripts/container/build-iso.sh server
|
||||
|
||||
qemu:
|
||||
scripts/run-qemu.sh
|
||||
|
||||
@@ -26,10 +31,15 @@ qemu-bios:
|
||||
qemu-disk:
|
||||
scripts/run-qemu.sh --disk
|
||||
|
||||
qemu-server:
|
||||
scripts/run-qemu.sh --serial --disk
|
||||
|
||||
test:
|
||||
cd srcpkgs/das-theme/files/xfwm4gen && go test ./...
|
||||
cd srcpkgs/das-tools/files/src && go test ./...
|
||||
cd srcpkgs/das-shell/files && PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -t .
|
||||
|
||||
# Build output may be owned by root when a build was interrupted, so remove it from the container
|
||||
clean:
|
||||
scripts/builder.sh rm -rf /work/out/hostdir/binpkgs /work/out/iso
|
||||
scripts/builder.sh rm -rf /work/out/hostdir/binpkgs /work/out/hostdir/stamps /work/out/iso
|
||||
rm -rf out/qemu
|
||||
|
||||
@@ -1,25 +1,37 @@
|
||||
## Das OS
|
||||
### _A light, friendly Void Linux–based desktop with Xfce and a Ukrainian touch_
|
||||
### _A light, friendly Void Linux–based desktop with Xfce and a Ukrainian touch, plus a console server edition_
|
||||
|
||||
[](https://voidlinux.org) [](https://xfce.org) [](https://docs.voidlinux.org/config/services/) [](https://go.dev) [](#)
|
||||
[](https://voidlinux.org) [](https://xfce.org) [](https://docs.voidlinux.org/config/services/) [](https://go.dev) [](https://www.docker.com) [](https://www.python.org) [](https://textual.textualize.io) [](#)
|
||||
|
||||
🇺🇦 [Українська версія](README_ua.md)
|
||||
|
||||
### 💡 Purpose
|
||||
Void Linux is fast, simple and independent: runit instead of systemd, and its own xbps package manager. Out of the box, though, it is a bare system, and getting it ready for daily use takes hours of setup. Das OS builds a live ISO that boots straight into a configured Xfce desktop. It has its own look, a Ukrainian interface and keyboard layout, and the usual desktop apps. Everything Das OS adds comes from its own xbps packages, so the setup can be reproduced and updated like any other package.
|
||||
Void Linux is fast, simple and independent: runit instead of systemd, and its own xbps package manager. Out of the box, though, it is a bare system, and getting it ready for daily use takes hours of setup. Das OS builds a live ISO that boots straight into a configured Xfce desktop and installs it with a graphical installer. It has its own look, a Ukrainian interface and keyboard layout, the usual desktop apps and Btrfs snapshots that let a broken update be undone. Everything Das OS adds comes from its own xbps packages, so the setup can be reproduced and updated like any other package. A second, console-only ISO installs a server with SSH, a firewall, fail2ban and Docker set up, on the same Btrfs layout with snapshots.
|
||||
|
||||
### 📃 Features
|
||||
- [Live ISO](#LiveISO) that boots on UEFI and BIOS straight into the desktop
|
||||
- [Graphical installer](#Installer) (Calamares) set up for Void and runit
|
||||
- [Snapshots and rollback](#Snapshots): hourly Btrfs snapshots, bootable from the GRUB menu
|
||||
- [System update](#Update) with a snapshot taken before it: a window, a tray icon and a terminal mode
|
||||
- [Welcome window](#Welcome) at the first login: language, home folders, updates, the developer profile and help
|
||||
- [Server image](#Server): a console live ISO with a text installer, SSH, firewall, fail2ban and Docker
|
||||
- [Command-line helpers](#Helpers) for everyday admin tasks: services (`dsv`), users (`duser`), firewall (`dfw`) and system settings (`dsys`)
|
||||
- [Terminal prompt and SSH login banner](#Terminal): the Das prompt by default, the `dprompt` prompt builder and the `dbanner` banner editor with the Das OS logo and the tryzub
|
||||
- [Aliases and functions](#Aliases) for every terminal: `extract`, `mkcd`, `netinfo`, `pkgowner`, Docker shortcuts and more, with `lsfunc` to list them
|
||||
- [Developer profile](#DevProfile) in one command: Go, Python, Node.js, Docker, rootless Podman, VS Code and IDEs from Flathub
|
||||
- [Das OS look](#Look): light and dark themes, generated window frames, wallpapers and logo
|
||||
- [Desktop defaults](#Defaults) for Xfce that don't overwrite stock files
|
||||
- [Own package repository](#Repository) built by xbps-src from in-tree templates
|
||||
- [Containerised build](#Build) that runs on any Linux host with Docker, and QEMU scripts for testing
|
||||
- [CI build](#CI) of the packages and both ISOs on every push, on a self-hosted Gitea Actions runner
|
||||
|
||||
### ⭐ Advantages
|
||||
- **Void underneath** – a fast runit boot, the xbps package manager and fresh software, with none of the manual setup.
|
||||
- **Safe updates** – `das-update` takes a snapshot before every update, the system can return to any snapshot, and personal files in `/home` are never part of a rollback.
|
||||
- **Light** – no systemd, plus Xfce, LightDM and PipeWire, so it runs well on older laptops too.
|
||||
- **Nothing hidden** – all customisations ship as ordinary xbps packages, so `xbps-query -f das-settings` shows exactly what changed.
|
||||
- **Users keep control** – defaults live in `/etc/xdg/das`, so a user's own settings always take precedence and upgrades never reset them.
|
||||
- **One base for desktop and server** – both images share the packages, the disk layout, snapshots and `das-update`, so a server is updated and rolled back the same way as a laptop.
|
||||
- **Builds anywhere** – the build runs in a Void container, so the host can be Mint, Ubuntu or Fedora.
|
||||
|
||||
### 📱 Screenshots
|
||||
@@ -44,6 +56,26 @@ Void Linux is fast, simple and independent: runit instead of systemd, and its ow
|
||||
<td align="center"><i>Dark wallpaper</i></td>
|
||||
<td align="center"><i>Window frames: light and dark, active and inactive</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><img src="images/installer-welcome.png" width="300" alt="Installer" /></td>
|
||||
<td align="center"><img src="images/installer-partitions.png" width="300" alt="Disk setup with Btrfs" /></td>
|
||||
<td align="center"><img src="images/grub-snapshots.png" width="300" alt="Snapshots in the boot menu" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><i>Installer</i></td>
|
||||
<td align="center"><i>Disk setup with Btrfs</i></td>
|
||||
<td align="center"><i>Snapshots in the boot menu</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><img src="images/welcome.png" width="300" alt="Welcome window" /></td>
|
||||
<td align="center"><img src="images/update.png" width="300" alt="System update" /></td>
|
||||
<td align="center"><img src="images/server-installer.png" width="300" alt="Server installer" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><i>Welcome window</i></td>
|
||||
<td align="center"><i>System update</i></td>
|
||||
<td align="center"><i>Server installer</i></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 📌 Using
|
||||
@@ -56,13 +88,108 @@ Void Linux is fast, simple and independent: runit instead of systemd, and its ow
|
||||
- Ukrainian interface (`uk_UA.UTF-8`) and the Kyiv time zone by default. Keyboard layouts are `us,ua`, switched with Alt+Shift.
|
||||
- Includes NetworkManager, Bluetooth, PipeWire sound and Firefox with the Ukrainian language pack. Services run under runit: `dbus`, `elogind`, `polkitd`, `NetworkManager`, `bluetoothd`, `lightdm`.
|
||||
- Includes guest tools for QEMU/SPICE and VirtualBox.
|
||||
- The *Install Das OS* icon on the desktop and in the menu starts the installer.
|
||||
|
||||
#### Graphical installer<a id='Installer' />
|
||||
**_Calamares installs Das OS in a few steps: language and time zone, keyboard, disk, user._**
|
||||
- The interface is in Ukrainian by default, with the Kyiv time zone and Das OS branding and slides.
|
||||
- The disk can be erased automatically or partitioned by hand. Btrfs is the default, ext4 and XFS are also available. On UEFI machines it creates a 512 MiB EFI partition.
|
||||
- It copies the clean live image (`/dev/mapper/live-base`), without the live user and its boot-time settings.
|
||||
- After copying, it builds locales for the chosen language, keeps only the guest tools of the hypervisor it runs in (none on real hardware), sets up snapshots, builds the initramfs and installs GRUB.
|
||||
- When it finishes, it removes itself and the packages that only it needed.
|
||||
- Disk encryption is not offered yet.
|
||||
|
||||
#### Snapshots and rollback<a id='Snapshots' />
|
||||
**_Btrfs snapshots keep the system recoverable after a bad update._**
|
||||
- The installer creates the subvolumes `@` (root), `@home`, `@snapshots`, `@var_log` and `@var_cache_xbps`. Root snapshots don't include personal files, logs or the package cache.
|
||||
- snapper makes a *Fresh install* snapshot right after installation and an hourly snapshot (cron). It keeps the last 5 hourly, 7 daily and 2 weekly ones.
|
||||
- grub-btrfs adds every snapshot to the *Das OS snapshots* submenu in GRUB and updates it as snapshots appear.
|
||||
- A snapshot is read-only. When it boots, the `das-snapshot-overlay` dracut module puts a writable overlay in RAM on top, so the full desktop works. Changes made there are lost at shutdown.
|
||||
- `sudo snapper-rollback <number>` makes the snapshot the new root (the old one is kept as `@<date>`). The change takes effect after a reboot. It works both from the running system and from a booted snapshot.
|
||||
- Members of `wheel` can list snapshots without sudo: `snapper list`.
|
||||
|
||||
#### System update<a id='Update' />
|
||||
**_`das-update` installs updates between a pair of snapshots, so a bad update can be undone from the boot menu._**
|
||||
- Before updating, it creates a snapper *pre* snapshot, then runs `xbps-install -Su` (updating xbps itself first when needed), then creates the paired *post* snapshot, also when the update failed.
|
||||
- If the *pre* snapshot cannot be created, nothing is installed. Without Btrfs (ext4, XFS) it updates without a snapshot and says so.
|
||||
- When it finishes, it shows the snapshot number and how to roll back to it, and asks for a reboot when the kernel, glibc, runit or other core packages were updated.
|
||||
- The *System update* window lists the packages with the installed and new versions and shows the xbps output. The privileged part runs through pkexec (polkit action `ua.org.das.update`).
|
||||
- In the background it checks for updates 2 minutes after login and then every 6 hours, without root. The tray icon and a notification appear only when updates are available. A click opens the window, and a right click opens a menu with *Check now*. The check is off in the live session.
|
||||
- `das-update --cli` does the same in a terminal (servers, ssh). It asks before installing (`-y` skips the question) and runs itself again through sudo. Without a display it works in the terminal even without `--cli`.
|
||||
- The command itself is the `das-update` package without GTK, the window and the tray icon are `/usr/libexec/das/das-update-gui` from `das-tools`.
|
||||
|
||||
#### Welcome window<a id='Welcome' />
|
||||
**_`das-welcome` opens at the first login and gathers the first steps in one place._**
|
||||
- In the live session it offers to install Das OS.
|
||||
- The interface language can be chosen when the system has more than one locale built. It is stored in AccountsService and `~/.dmrc` and takes effect at the next login.
|
||||
- If the home folders still have English names (`Documents`, `Downloads`…), it offers to rename them to the system language, together with `user-dirs.dirs` and the file manager bookmarks. Folders the user moved, and those whose new name is already taken, are left alone.
|
||||
- It installs the [developer profile](#DevProfile) in a terminal window, where the IDEs and the password are asked for, and once the profile is installed, adds IDEs from Flathub.
|
||||
- It links to the system update, Xfce settings and help.
|
||||
- It opens by itself only once. The *Show at login* check box keeps it opening at every login.
|
||||
|
||||
#### Server image<a id='Server' />
|
||||
**_A console-only live ISO that installs a Das OS server with SSH, a firewall and Docker ready to use._**
|
||||
- `dasos-server-<version>-x86_64.iso` boots on UEFI and BIOS with a login on the screen and on the serial console (`ttyS0`, 115200), so it also works through IPMI, VPS consoles and `qemu -nographic`. Log in as `live` with the password `live`, then run `sudo das-server-install`.
|
||||
- The installer asks in Ukrainian or English: the disk, server name, administrator and password, an SSH key (and whether password logins stay allowed), time zone, console layout, language and network (dhcpcd or NetworkManager). `das-server-install --config FILE` installs without questions, see `/usr/share/examples/das-server-installer/install.conf`.
|
||||
- It uses the same GPT and Btrfs layout as the desktop installer, so snapshots, the *Das OS snapshots* boot menu and `snapper-rollback` work the same way. GRUB is installed for UEFI (also at the fallback path) or BIOS, on the serial console too when the installer was started there.
|
||||
- The administrator gets sudo through `wheel` and is in the `docker` and `socklog` groups. root has no password.
|
||||
- The installed system runs `sshd`, `chronyd`, `nftables`, `fail2ban`, `docker`, `socklog` and the chosen network service.
|
||||
- nftables lets in only SSH, ICMP and DHCPv6 (`/etc/nftables.conf`), more ports are opened with `dfw`. The rules live in their own table `inet das`, so reloading them keeps the Docker and fail2ban rules. Ports that containers publish pass through Docker's own rules.
|
||||
- fail2ban bans addresses that keep failing SSH logins, reading the socklog log. sshd doesn't accept root password logins.
|
||||
- Tools: Docker with Compose, lazydocker, lazygit, Midnight Commander, tmux, htop, ncdu, Python 3, curl, `das-update --cli`, the [helpers](#Helpers), [`dprompt` and `dbanner`](#Terminal), [aliases and functions](#Aliases) and xtools (`xi`, `xq`, `xrs`). There are no graphical libraries, and no firmware for NVIDIA graphics or sound cards (`/etc/xbps.d/das-server.conf` keeps updates from bringing it back).
|
||||
|
||||
#### Command-line helpers<a id='Helpers' />
|
||||
**_Short commands for tasks that systemd distributions handle with systemctl, hostnamectl and the like, and that Void leaves to manual editing._**
|
||||
- They answer in Ukrainian or English, run themselves again through sudo when they need root and have bash completion. In a terminal they ask for whatever is missing, and with all arguments given they work in scripts.
|
||||
- `dsv` manages runit services: `list` (enabled or not, state, PID, uptime), `enable` and `disable` (at boot and right now), `start`, `stop`, `restart`, `status` and `logs [-f]` (the service's own svlogd log or its lines in socklog).
|
||||
- `dsv new` creates a service for your own program: the command, user, working directory and environment variables go to `/etc/sv/NAME`, with a log in `/var/log/NAME`. It then starts the service and checks that it stays up. `dsv rm` deletes such a service and refuses to touch services from packages.
|
||||
- `duser` manages accounts: `add` with the same groups as the desktop installer (`--admin` adds `wheel`, `docker` and `socklog`), a password and an SSH key; `passwd`, `lock` and `unlock` (a locked account can't log in with a key either), `key add|list|rm` for `authorized_keys`, `groups NAME +GROUP -GROUP` and `del`. It won't delete or lock out the current user, and it won't delete the only administrator.
|
||||
- `dfw` opens ports in the server firewall: `dfw allow 80/tcp`, `dfw allow https 8000-8010/tcp`, `dfw deny 80/tcp`. The ports are kept in `/etc/nftables.d/dfw.nft` and checked with `nft -c` before loading, and only the `inet das` table is reloaded. `dfw status` also lists the ports Docker containers publish and the addresses fail2ban has banned. SSH always stays open.
|
||||
- `dsys` replaces hostnamectl, timedatectl and localectl: `info` (time sync, language, console, addresses), `hostname`, `timezone` (part of the name will do: `dsys timezone Kyiv`), `locale` (builds the glibc locale if needed) and `keymap` (with a Cyrillic console font for Cyrillic layouts).
|
||||
- They come in the `das-helpers` package and are on the desktop too. Both images also include `xtools-minimal` from Void for package work: `xi` installs, `xrs` searches, `xq` shows package details and `xcheckrestart` finds programs to restart after an update.
|
||||
|
||||
#### Terminal prompt and SSH login banner<a id='Terminal' />
|
||||
**_A two-line bash prompt out of the box, and two terminal editors to change it and to draw the banner shown at SSH login._**
|
||||
- The stock prompt: `┌─[✗]─[user@host]─[2026-10-21]─[08:12]─[~/dir]` and `└─╼ $` on the second line. `[✗]` appears only after a failed command, root's name is red, and without colours or on the Linux console the shape stays the same.
|
||||
- It comes from `/etc/bash/bashrc.d/das-prompt.sh`: the user's `~/.config/das/prompt.sh`, else `/etc/das/prompt.sh`, else the stock one. Void's `/etc/skel/.bashrc` sets its own PS1 afterwards, so that one is replaced once, but a user's own PS1 is never touched.
|
||||
- `dprompt` is a prompt builder: ready-made prompts (`das`, `das-git` with the git branch, venv and background jobs, `server`, `minimal`, `classic`), segments to add, move and colour, and a live preview as a normal user, after an error and as root. Ctrl+S applies it for yourself, Ctrl+G for every user through sudo. Without the editor: `dprompt list`, `dprompt show NAME`, `dprompt apply NAME [--system]`, `dprompt reset`.
|
||||
- Conditional parts (exit status, git, venv, jobs, SSH, a command's output) are computed by a `PROMPT_COMMAND` hook and only read by PS1, so a branch name is never run as code.
|
||||
- `dbanner` edits the banner shown at SSH login. It starts with a list of ready-made banners with a live preview: `das-classic` (a `#` frame with the name, IP and link), `double` (the host name in big figlet letters), `blocks` (the Das OS logo with system facts), `ukraine` (the lesser coat of arms of Ukraine), `tryzub`, `dashboard` and `minimal`.
|
||||
- The editor draws with the mouse or the keyboard: pen, eraser, line, boxes (single, double, rounded, heavy, ASCII, `#`, blocks), fill, text, big letters, select with copy and paste, undo. A palette offers box and block symbols, 16 colours for text and background and bold. Ready pictures go in with one click: the Das OS logo and the tryzub, plain or on a shield, in two sizes.
|
||||
- Fields show live values in a fixed width, so a frame never shifts: `{hostname}`, `{ip}`, `{uptime}`, `{mem}`, `{os}`, `{kernel}` and others, plus your own variables like `{url}`; `[[Ukrainian|English]]` labels follow the user's language.
|
||||
- Widgets add blocks under or beside the picture: disks with usage bars, memory, load, network, Docker containers, available updates, weather from wttr.in, SMART disk health, runit services that are down, fail2ban bans, snapper snapshots, the previous login, or any command or file with a script's output.
|
||||
- Quick widgets run at login with a short timeout. Slow or root-only ones (updates, weather, SMART, fail2ban) are refreshed by the `das-banner` runit service into `/var/cache/das/banner`, and `dbanner apply` enables it when a banner needs it. No cron is needed.
|
||||
- Drafts are kept in `~/.config/das/banner`; Ctrl+A or `dbanner apply NAME` writes `/etc/das/banner.json` through sudo. `dbanner off` and `on` turn the banner off for everyone, `~/.hushlogin` for one user; `"local": true` shows it on console logins too.
|
||||
|
||||
#### Aliases and functions<a id='Aliases' />
|
||||
**_Ready shortcuts for everyday terminal work in every interactive bash, rewritten for Void: xbps, nftables, runit and Docker._**
|
||||
- They come from `/etc/bash/bashrc.d/das-aliases.sh` (package `das-shell`): `/usr/share/das/shell/aliases.sh` and `functions.sh`, then your own `~/.bash_aliases` and `~/.bash_functions`, which win. `touch ~/.config/das/no-aliases` turns the Das OS ones off for one user. `lsfunc` lists the functions with a description in the user's language, `alias` lists the aliases.
|
||||
- `ls`: `ll`, `la`, `l`, `sl`, `lsl` (in `less`), `lsz` (by size), `lst` (by time), `lsd` (directories only); `grep`, `diff` and `ip` in colour.
|
||||
- Directories: `..`, `...`, `....`, `cd..`, `up [N]` (N levels up), `mkcd DIR` (create and enter), `cl DIR` (enter and list), `path` (one `PATH` entry per line); `mkdir` makes parents.
|
||||
- Files: `f PATTERN [DIR]` (find by name, any case), `fhere NAME`, `cnt` (number of files), `bigfiles [DIR] [N]`, `dirsize [DIR]` (sizes of everything in a directory), `whatsize [DIR]` (subdirectory sizes, `/` by default), `bu FILE...` (copy with the date in the name), `ednt FILE` (edit keeping the modification time).
|
||||
- Archives: `extract ARCHIVE...` unpacks tar of any compression, `.xbps`, zip, rar, 7z, gz, bz2, xz, zst, lz4 and Z; `compress NAME.tar.gz|tar.xz|tar.zst|zip|7z FILE...` packs by the name's extension. A missing tool is named together with the package to install.
|
||||
- Disks and memory: `df`, `dft` (with types and a total), `dftl`, `mnt` (mounted devices), `mounts`, `devid` (lsblk, blkid and lsusb), `snaps` (root snapshots), `free`, `meminfo`, `cpuinfo`, `gpumeminfo`.
|
||||
- Processes and history: `psa TEXT`, `pst TEXT` (as a tree), `psg`, `psmem`/`psmem10`, `pscpu`/`pscpu10`, `j`, `hgrep TEXT`, `histat [N]` and `histop10` (most used commands), `EC` (the previous exit code), `please` (the previous command again with sudo).
|
||||
- Packages: `upd` and `updy` (`das-update --cli` with snapshots, `updy` without questions), `pkgshow [TEXT]` (installed), `pkgfind TEXT` (in the repositories), `pkgowner FILE|COMMAND`, `pkgclean` (package cache and orphans).
|
||||
- Network: `myip`, `netinfo` (addresses, gateway, DNS, public IP), `oports` and `gport TEXT` (listening ports), `ping5`, `fastping`, `header URL` and `headerc URL` (HTTP headers, with compression), `listen PORT`, `serve [PORT]` (the current directory over HTTP), `rmhost HOST|LINE` (clean `known_hosts`), `nftlist`, `firewall` (`dfw status`), `weather [CITY]`, `cheat COMMAND` (cheat.sh).
|
||||
- Docker: `dps`, `dlog CONTAINER`, `dcu`, `dcd`, `dcl`, `dcp` (compose up, down, logs, pull), `lzd`, `lzg`.
|
||||
- Small tools: `calc EXPR`, `epoch [SECONDS]`, `genpass [LENGTH]`, `now`, `nowdate`, `note [-c] [TEXT]` (`~/.notes`), `todo [-l|-r N|-c] [TEXT]` (`~/.todo`), `csrc FILE.c` (compile and run C), `man` with coloured headings.
|
||||
|
||||
#### Developer profile<a id='DevProfile' />
|
||||
**_`dprofile install dev` turns a desktop or a server into a development machine, with a snapshot taken before it._**
|
||||
- Packages from the Void repository: `base-devel`, gdb, strace, Git with LFS, GitHub CLI, lazygit, Go with gopls, delve and golangci-lint, Python with pip, virtualenv and uv, Node.js, Docker with Compose and Buildx, lazydocker, Podman with podman-compose, jq, ripgrep, fd, fzf, direnv, shellcheck and SQLite.
|
||||
- On a desktop also VS Code (Void's open-source build `code-oss`, extensions come from Open VSX), Meld, Flatpak with the Flathub remote and the desktop portals.
|
||||
- It enables Docker and adds the user who runs it to the `docker` group, gives them subordinate IDs for rootless Podman and makes `/` a shared mount at every boot (`/etc/runit/core-services/60-das-shared-root.sh`), which rootless containers need.
|
||||
- Before installing, it asks which IDEs to add from Flathub: Android Studio, IntelliJ IDEA Open and Ultimate, PyCharm, GoLand, WebStorm or Microsoft's Visual Studio Code. Later they are added with `dprofile app add GoLand` and removed with `dprofile app rm`, which also removes the Flatpak runtimes nothing else uses.
|
||||
- The packages are installed between a snapper pre/post snapshot pair, like with `das-update`, and the command prints how to roll back. `dprofile list` and `dprofile show dev` show what is installed already; `--yes` and `--apps ID,...|all|none` make it work without questions.
|
||||
- The profile is a plain list in `/usr/share/das/profiles/dev.conf`. The desktop installer doesn't offer it because the installation copies the live image without a network, and the profile downloads about 550 MB of packages (an IDE from Flathub takes 1–4 GB more with its runtimes).
|
||||
|
||||
#### Das OS look<a id='Look' />
|
||||
**_A modern minimal style with a blue and yellow eight-petal star from Ukrainian embroidery._**
|
||||
- The GTK themes `DasOS` and `DasOS-Dark` build on adw-gtk3 and add the Das OS accent colours. They also style the Xfce panel, which adw-gtk3 leaves black.
|
||||
- The xfwm4 window frames (rounded corners, round buttons, a red close button on hover) are generated as PNG files by `xfwm4gen` (Go) from a single palette.
|
||||
- Light and dark wallpapers show the star over a rushnyk-style embroidered ribbon. The same star is the Das OS logo and the menu button.
|
||||
- Fonts are Inter for the interface and JetBrains Mono in the terminal. Icons are Papirus and the cursor theme is Adwaita.
|
||||
- Fonts are Inter for the interface and JetBrains Mono in the terminal. Icons are Papirus (the Papirus-Light variant, so the panel icons are dark) and the cursor theme is Adwaita.
|
||||
|
||||
#### Desktop defaults<a id='Defaults' />
|
||||
**_The das-settings package configures Xfce without touching files owned by other packages._**
|
||||
@@ -70,30 +197,47 @@ Void Linux is fast, simple and independent: runit instead of systemd, and its ow
|
||||
- One bottom panel holds the Whisker menu, window buttons, tray, keyboard layout, volume, notifications, clock and a show-desktop button.
|
||||
- At the first login, `das-session-init` sets the wallpaper on every connected monitor, because Xfce stores wallpapers per monitor name.
|
||||
- LightDM uses the GTK greeter with the Das OS theme and wallpaper.
|
||||
- Home folders get names in the session language. xdg-user-dirs 0.20 looks for its translations only in the first `XDG_DATA_DIRS` entry that has a `locale` directory, the empty `/usr/local/share/locale`, so it created English folders. `/etc/X11/xinit/xinitrc.d/50-das-user-dirs` creates them before the Xfce session with a lookup path that finds the translations.
|
||||
|
||||
#### Own package repository<a id='Repository' />
|
||||
**_Every Das OS file is installed and updated by xbps._**
|
||||
- `srcpkgs/das-theme` contains the themes, xfwm4 frames, wallpapers and logo.
|
||||
- `srcpkgs/das-settings` contains the Xfce, LightDM and session defaults.
|
||||
- `srcpkgs/adw-gtk3` and `srcpkgs/font-jetbrains-mono` package upstream releases that Void does not have.
|
||||
- `srcpkgs/das-tools` contains `das-welcome` and the update window and tray icon (Go, GTK 3 through gotk3) with their menu and autostart entries. Its subpackage `das-update` is the update command and polkit action, without GTK.
|
||||
- `srcpkgs/das-snapshots` contains the snapper configuration, `das-snapper-setup` and the dracut module for booting snapshots.
|
||||
- `srcpkgs/das-installer` contains the Calamares settings and branding, `srcpkgs/das-server-installer` the console installer. The steps both share (`mount-source`, `postinstall`) are in `srcpkgs/das-install-common`. They exist only on the live ISOs.
|
||||
- `srcpkgs/das-server` is the server metapackage with the nftables, fail2ban, sshd and Docker defaults. `srcpkgs/das-helpers` contains `dsv`, `duser`, `dfw`, `dsys`, `dprofile` and the profiles.
|
||||
- `srcpkgs/publisher` (`publish`) and `srcpkgs/deployer` (`deploy`) are the developer's TUI/CLI tools that publish builds and deploy projects to a server over ssh + rsync. Each one keeps its config in `~/.config/<name>/` (an example is copied there on the first run) and its history in `~/.local/state/<name>/`.
|
||||
- `srcpkgs/calamares`, `srcpkgs/adw-gtk3` and `srcpkgs/font-jetbrains-mono` package upstream releases that Void does not have.
|
||||
- `make packages` lays the templates over a shallow clone of void-packages and builds them with `xbps-src` into `out/hostdir/binpkgs`. The ISO build installs them from there.
|
||||
- A package is rebuilt only when its directory in `srcpkgs/` changed, its binary package is missing, or Void no longer provides a shared library it links to (checked with a dry-run install). `DASOS_REBUILD=1 make packages` rebuilds everything.
|
||||
|
||||
#### Containerised build<a id='Build' />
|
||||
**_The whole build runs in a Docker container, so only Docker and QEMU are needed on the host._**
|
||||
1. `make packages` builds the local xbps repository.
|
||||
2. `make iso` builds the packages and then the ISO with `void-mklive`, using the profile in `live/` (package list, services, root file system overlay).
|
||||
3. `make qemu` boots the newest ISO with UEFI. `make qemu-bios` uses legacy BIOS, and `make qemu-disk` also attaches a 20 GB virtual disk.
|
||||
2. `make iso` builds the packages and then the ISO with `void-mklive`, using the profile in `live/` (package list, services, root file system overlay). `make iso-server` does the same for the server profile in `server/`.
|
||||
3. `make qemu` boots the newest ISO with UEFI. `make qemu-bios` uses legacy BIOS, and `make qemu-disk` also attaches a 20 GB virtual disk to install on. `make qemu-server` boots the server ISO with the disk on a serial console in the terminal (Ctrl-A X quits). The guest's SSH is forwarded to `localhost:2222` (`DASOS_SSH_PORT` changes it).
|
||||
|
||||
#### CI build<a id='CI' />
|
||||
**_Every push to `main` or `dev` and every `v*` tag builds the packages and both ISOs on git.das.org.ua._**
|
||||
- The workflow `.gitea/workflows/build.yml` runs `make iso iso-server` on a self-hosted runner with the `dasci` label.
|
||||
- The runner is a WSL2 distro on a Windows machine: Ubuntu 24.04 with Docker Engine and `gitea-runner` as a systemd service. A scheduled task starts it when Windows boots, no login needed.
|
||||
- `ci/runner/setup.sh` installs Docker, the runner and its `config.yaml`; `setup.sh register <token>` registers it, `autostart.ps1` creates the scheduled task.
|
||||
- `DASOS_OUT` moves `out/` out of the checkout, so void-packages, the bootstrapped masterdir and the download cache survive between builds.
|
||||
- A `v*` tag that matches `VERSION` also publishes both ISOs and `SHA256SUMS` as a release (`ci/release.sh`, with the job's own token): `git tag v0.8.0 && git push origin v0.8.0`. Other builds keep the ISOs on the CI host in `/var/lib/dasos-ci/out/iso`.
|
||||
|
||||
### 🚀 Building the application
|
||||
Requirements: Linux with Docker (the user must be in the `docker` group), QEMU and OVMF for testing, and Go 1.22+ for `make test`. About 10 GB of free disk space.
|
||||
Requirements: Linux with Docker (the user must be in the `docker` group), QEMU and OVMF for testing, and Go 1.24+ with the GTK 3 development files plus Python 3 for `make test`. About 10 GB of free disk space.
|
||||
```shell
|
||||
make iso # build packages and the live ISO (the first run downloads ~2 GB)
|
||||
make qemu # boot it in QEMU with UEFI
|
||||
make test # run Go tests
|
||||
make iso-server # build packages and the server ISO
|
||||
make qemu-server # boot it on a serial console with a disk to install on
|
||||
make test # run Go and Python tests
|
||||
make clean # remove build output, keep the download cache
|
||||
```
|
||||
The ISO appears in `out/iso/dasos-<version>-x86_64.iso`. Write it to a USB stick with `dd` or Ventoy.
|
||||
The ISOs appear in `out/iso/dasos-<version>-x86_64.iso` and `out/iso/dasos-server-<version>-x86_64.iso`. Write them to a USB stick with `dd` or Ventoy. For a Ventoy theme there is a 48×48 menu icon `live/branding/ventoy/dasos.png`: copy it to the theme's `icons/` folder and add `{ "key": "dasos", "class": "dasos" }` to `menu_class` in `ventoy.json`.
|
||||
|
||||
**Note:** Das OS is an early version (0.1.0) and the live ISO cannot install itself yet. The Calamares installer, Btrfs snapshots with rollback, the Go utilities `das-welcome` and `das-update`, the developer profile and signed repository hosting are planned for upcoming stages. Das OS is based on Void Linux but is not affiliated with it.
|
||||
**Note:** Das OS is an early version (0.9.3). Packages and ISOs are built in CI; signed repository hosting is planned for an upcoming stage. Until the repository is hosted, an installed system gets updates from Void only. Das OS is based on Void Linux but is not affiliated with it.
|
||||
|
||||
###### _Made by -=:dAs:=-_
|
||||
|
||||
@@ -1,25 +1,37 @@
|
||||
## Das OS
|
||||
### _Легкий і дружній десктоп на базі Void Linux з Xfce та українським колоритом_
|
||||
### _Легкий і дружній десктоп на базі Void Linux з Xfce та українським колоритом, а також консольна серверна версія_
|
||||
|
||||
[](https://voidlinux.org) [](https://xfce.org) [](https://docs.voidlinux.org/config/services/) [](https://go.dev) [](#)
|
||||
[](https://voidlinux.org) [](https://xfce.org) [](https://docs.voidlinux.org/config/services/) [](https://go.dev) [](https://www.docker.com) [](https://www.python.org) [](https://textual.textualize.io) [](#)
|
||||
|
||||
🇬🇧 [English version](README.md)
|
||||
|
||||
### 💡 Призначення
|
||||
Void Linux швидкий, простий і незалежний: runit замість systemd і власний менеджер пакетів xbps. Проте з коробки це гола система, і щоб довести її до щоденного використання, треба кілька годин налаштовувати. Das OS збирає live-образ ISO, який одразу завантажується в налаштований десктоп Xfce. У ньому власне оформлення, український інтерфейс і розкладка та звичні програми. Усе, що додає Das OS, постачається власними xbps-пакетами, тож налаштування можна відтворити й оновлювати, як будь-який інший пакет.
|
||||
Void Linux швидкий, простий і незалежний: runit замість systemd і власний менеджер пакетів xbps. Проте з коробки це гола система, і щоб довести її до щоденного використання, треба кілька годин налаштовувати. Das OS збирає live-образ ISO, який одразу завантажується в налаштований десктоп Xfce і встановлюється графічним інсталятором. У ньому власне оформлення, український інтерфейс і розкладка, звичні програми та знімки Btrfs, які дозволяють скасувати невдале оновлення. Усе, що додає Das OS, постачається власними xbps-пакетами, тож налаштування можна відтворити й оновлювати, як будь-який інший пакет. Другий, консольний ISO встановлює сервер із налаштованими SSH, файрволом, fail2ban і Docker на тій самій розмітці Btrfs зі знімками.
|
||||
|
||||
### 📃 Можливості
|
||||
- [Live ISO](#LiveISO), що завантажується з UEFI і BIOS одразу в робочий стіл
|
||||
- [Графічний інсталятор](#Installer) Calamares, налаштований під Void і runit
|
||||
- [Знімки й відкат](#Snapshots): щогодинні знімки Btrfs, які можна завантажити з меню GRUB
|
||||
- [Оновлення системи](#Update) зі знімком перед ним: вікно, значок у треї та консольний режим
|
||||
- [Вікно привітання](#Welcome) під час першого входу: мова, теки, оновлення, профіль розробника та довідка
|
||||
- [Серверний образ](#Server): консольний live ISO з текстовим інсталятором, SSH, файрволом, fail2ban і Docker
|
||||
- [Консольні помічники](#Helpers) для щоденного адміністрування: служби (`dsv`), користувачі (`duser`), брандмауер (`dfw`) і системні налаштування (`dsys`)
|
||||
- [Запрошення терміналу й банер при вході по SSH](#Terminal): запрошення Das за замовчуванням, конструктор запрошень `dprompt` і редактор банера `dbanner` з логотипом Das OS і тризубом
|
||||
- [Аліаси й функції](#Aliases) у кожному терміналі: `extract`, `mkcd`, `netinfo`, `pkgowner`, скорочення для Docker та інші, а `lsfunc` показує їх список
|
||||
- [Профіль розробника](#DevProfile) однією командою: Go, Python, Node.js, Docker, rootless Podman, VS Code і середовища з Flathub
|
||||
- [Оформлення Das OS](#Look): світла й темна теми, згенеровані рамки вікон, шпалери та логотип
|
||||
- [Налаштування за замовчуванням](#Defaults) для Xfce, які не перезаписують стандартні файли
|
||||
- [Власний репозиторій пакетів](#Repository), який xbps-src збирає з шаблонів у дереві проєкту
|
||||
- [Збирання в контейнері](#Build), яке працює на будь-якому Linux із Docker, і скрипти QEMU для тестування
|
||||
- [Збирання в CI](#CI) пакетів і обох ISO після кожного push на власному runner Gitea Actions
|
||||
|
||||
### ⭐ Переваги
|
||||
- **Void усередині** – швидке завантаження з runit, менеджер пакетів xbps і свіжі програми без ручного налаштування.
|
||||
- **Безпечні оновлення** – `das-update` робить знімок перед кожним оновленням, систему можна повернути до будь-якого знімка, а особисті файли в `/home` відкат не зачіпає.
|
||||
- **Легкість** – без systemd, а з Xfce, LightDM і PipeWire система добре працює навіть на старих ноутбуках.
|
||||
- **Нічого прихованого** – усі зміни постачаються звичайними xbps-пакетами, і `xbps-query -f das-settings` показує, що саме змінено.
|
||||
- **Контроль у користувача** – налаштування за замовчуванням лежать у `/etc/xdg/das`, тож власні налаштування користувача завжди мають пріоритет і не скидаються після оновлень.
|
||||
- **Одна основа для десктопа й сервера** – обидва образи мають спільні пакети, розмітку диска, знімки й `das-update`, тож сервер оновлюється й відкочується так само, як ноутбук.
|
||||
- **Збирається будь-де** – збирання відбувається в контейнері Void, тому хостом може бути Mint, Ubuntu чи Fedora.
|
||||
|
||||
### 📱 Скріншоти
|
||||
@@ -44,6 +56,26 @@ Void Linux швидкий, простий і незалежний: runit зам
|
||||
<td align="center"><i>Темні шпалери</i></td>
|
||||
<td align="center"><i>Рамки вікон: світлі й темні, активні й неактивні</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><img src="images/installer-welcome.png" width="300" alt="Інсталятор" /></td>
|
||||
<td align="center"><img src="images/installer-partitions.png" width="300" alt="Розмітка диска з Btrfs" /></td>
|
||||
<td align="center"><img src="images/grub-snapshots.png" width="300" alt="Знімки в меню завантаження" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><i>Інсталятор</i></td>
|
||||
<td align="center"><i>Розмітка диска з Btrfs</i></td>
|
||||
<td align="center"><i>Знімки в меню завантаження</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><img src="images/welcome.png" width="300" alt="Вікно привітання" /></td>
|
||||
<td align="center"><img src="images/update.png" width="300" alt="Оновлення системи" /></td>
|
||||
<td align="center"><img src="images/server-installer.png" width="300" alt="Серверний інсталятор" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><i>Вікно привітання</i></td>
|
||||
<td align="center"><i>Оновлення системи</i></td>
|
||||
<td align="center"><i>Серверний інсталятор</i></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 📌 Використання
|
||||
@@ -56,13 +88,108 @@ Void Linux швидкий, простий і незалежний: runit зам
|
||||
- Український інтерфейс (`uk_UA.UTF-8`) і часовий пояс Києва за замовчуванням. Розкладки `us,ua`, перемикання Alt+Shift.
|
||||
- Є NetworkManager, Bluetooth, звук PipeWire і Firefox з українською мовою. Сервіси працюють під runit: `dbus`, `elogind`, `polkitd`, `NetworkManager`, `bluetoothd`, `lightdm`.
|
||||
- Є гостьові інструменти для QEMU/SPICE та VirtualBox.
|
||||
- Значок *Встановити Das OS* на робочому столі та в меню запускає інсталятор.
|
||||
|
||||
#### Графічний інсталятор<a id='Installer' />
|
||||
**_Calamares встановлює Das OS за кілька кроків: мова й часовий пояс, клавіатура, диск, користувач._**
|
||||
- Інтерфейс за замовчуванням український, часовий пояс Києва, з брендингом і слайдами Das OS.
|
||||
- Диск можна очистити автоматично або розмітити вручну. За замовчуванням Btrfs, також доступні ext4 і XFS. На комп'ютерах з UEFI створюється EFI-розділ на 512 МіБ.
|
||||
- Копіюється чистий live-образ (`/dev/mapper/live-base`), без live-користувача і його налаштувань завантаження.
|
||||
- Після копіювання інсталятор збирає локалі для вибраної мови, лишає гостьові інструменти лише того гіпервізора, у якому працює (на реальному залізі — жодних), налаштовує знімки, збирає initramfs і встановлює GRUB.
|
||||
- Наприкінці інсталятор видаляє себе й пакети, потрібні лише йому.
|
||||
- Шифрування диска поки не пропонується.
|
||||
|
||||
#### Знімки й відкат<a id='Snapshots' />
|
||||
**_Знімки Btrfs дозволяють відновити систему після невдалого оновлення._**
|
||||
- Інсталятор створює підтоми `@` (корінь), `@home`, `@snapshots`, `@var_log` і `@var_cache_xbps`. Знімки кореня не містять особистих файлів, журналів і кешу пакетів.
|
||||
- snapper робить знімок *Fresh install* одразу після встановлення і щогодинні знімки (cron). Зберігаються останні 5 щогодинних, 7 щоденних і 2 щотижневих.
|
||||
- grub-btrfs додає кожен знімок у підменю *Das OS snapshots* у GRUB і оновлює його, щойно з'являється новий.
|
||||
- Знімок доступний лише для читання. Коли він завантажується, dracut-модуль `das-snapshot-overlay` накладає зверху записуваний шар у RAM, тож повноцінно працює весь робочий стіл. Зміни, зроблені в такій сесії, зникають після вимкнення.
|
||||
- `sudo snapper-rollback <номер>` робить знімок новим коренем (старий зберігається як `@<дата>`). Зміна набуває чинності після перезавантаження. Команда працює і з робочої системи, і із завантаженого знімка.
|
||||
- Учасники групи `wheel` бачать знімки без sudo: `snapper list`.
|
||||
|
||||
#### Оновлення системи<a id='Update' />
|
||||
**_`das-update` встановлює оновлення між парою знімків, тож невдале оновлення можна скасувати з меню завантаження._**
|
||||
- Перед оновленням створює знімок snapper *pre*, потім запускає `xbps-install -Su` (спершу оновлює сам xbps, якщо треба), а наприкінці створює парний знімок *post*, навіть якщо оновлення не вдалося.
|
||||
- Якщо знімок *pre* не вдалося створити, нічого не встановлюється. Без Btrfs (ext4, XFS) оновлює без знімка й попереджає про це.
|
||||
- Після завершення показує номер знімка й те, як до нього повернутися, і просить перезавантажитися, якщо оновилися ядро, glibc, runit чи інші ключові пакети.
|
||||
- Вікно *Оновлення системи* показує список пакетів зі встановленою і новою версіями та вивід xbps. Частина, якій потрібні права root, запускається через pkexec (дія polkit `ua.org.das.update`).
|
||||
- У фоні перевіряє оновлення через 2 хвилини після входу, а далі кожні 6 годин, без прав root. Значок у треї й сповіщення з'являються лише тоді, коли є оновлення. Клік відкриває вікно, правий клік — меню з пунктом *Перевірити зараз*. У live-сесії перевірка вимкнена.
|
||||
- `das-update --cli` робить те саме в терміналі (сервери, ssh). Перед встановленням питає підтвердження (`-y` його пропускає) і сам перезапускається через sudo. Без графічного дисплея працює в терміналі й без `--cli`.
|
||||
- Сама команда — пакет `das-update` без GTK, а вікно й значок у треї — `/usr/libexec/das/das-update-gui` з `das-tools`.
|
||||
|
||||
#### Вікно привітання<a id='Welcome' />
|
||||
**_`das-welcome` відкривається під час першого входу і збирає перші кроки в одному місці._**
|
||||
- У live-сесії пропонує встановити Das OS.
|
||||
- Мову інтерфейсу можна вибрати, якщо в системі зібрано більше однієї локалі. Вона зберігається в AccountsService і `~/.dmrc` і діє з наступного входу.
|
||||
- Якщо теки домашнього каталогу лишилися з англійськими назвами (`Documents`, `Downloads`…), пропонує перейменувати їх мовою системи разом із `user-dirs.dirs` і закладками файлового менеджера. Теки, які користувач переніс, і ті, чия нова назва вже зайнята, не чіпає.
|
||||
- Встановлює [профіль розробника](#DevProfile) у вікні термінала, де питає про середовища й пароль, а коли профіль встановлено, додає середовища з Flathub.
|
||||
- Містить посилання на оновлення системи, налаштування Xfce і довідку.
|
||||
- Сам відкривається лише один раз. Прапорець *Показувати під час входу* залишає його на кожен вхід.
|
||||
|
||||
#### Серверний образ<a id='Server' />
|
||||
**_Консольний live ISO, що встановлює сервер Das OS з готовими до роботи SSH, файрволом і Docker._**
|
||||
- `dasos-server-<версія>-x86_64.iso` завантажується з UEFI і BIOS із входом і на екрані, і на послідовній консолі (`ttyS0`, 115200), тож працює й через IPMI, консолі VPS та `qemu -nographic`. Увійдіть як `live` з паролем `live` і запустіть `sudo das-server-install`.
|
||||
- Інсталятор питає українською або англійською: диск, ім'я сервера, адміністратора й пароль, SSH-ключ (і чи лишати вхід за паролем), часовий пояс, розкладку консолі, мову та мережу (dhcpcd або NetworkManager). `das-server-install --config ФАЙЛ` встановлює без запитань, приклад — `/usr/share/examples/das-server-installer/install.conf`.
|
||||
- Розмітка GPT і Btrfs та сама, що в десктопному інсталяторі, тож знімки, меню *Das OS snapshots* і `snapper-rollback` працюють так само. GRUB встановлюється для UEFI (також за запасним шляхом) або BIOS, а якщо інсталятор запущено з послідовної консолі, то й на неї.
|
||||
- Адміністратор отримує sudo через `wheel` і входить у групи `docker` та `socklog`. У root немає пароля.
|
||||
- Встановлена система запускає `sshd`, `chronyd`, `nftables`, `fail2ban`, `docker`, `socklog` і вибрану мережеву службу.
|
||||
- nftables пропускає лише SSH, ICMP і DHCPv6 (`/etc/nftables.conf`), інші порти відкриває `dfw`. Правила лежать у власній таблиці `inet das`, тож їх перезавантаження не чіпає правил Docker і fail2ban. Порти, які публікують контейнери, проходять через власні правила Docker.
|
||||
- fail2ban блокує адреси, з яких раз у раз не вдається увійти по SSH, читаючи журнал socklog. sshd не приймає вхід root за паролем.
|
||||
- Інструменти: Docker з Compose, lazydocker, lazygit, Midnight Commander, tmux, htop, ncdu, Python 3, curl, `das-update --cli`, [помічники](#Helpers), [`dprompt` і `dbanner`](#Terminal), [аліаси й функції](#Aliases) та xtools (`xi`, `xq`, `xrs`). Графічних бібліотек немає, як і прошивок для відеокарт NVIDIA та звукових карт (`/etc/xbps.d/das-server.conf` не дає оновленням повернути їх).
|
||||
|
||||
#### Консольні помічники<a id='Helpers' />
|
||||
**_Короткі команди для задач, які в дистрибутивах із systemd розв'язують systemctl, hostnamectl тощо, а у Void доводиться робити вручну._**
|
||||
- Вони відповідають українською або англійською, коли потрібні права root, самі перезапускаються через sudo, і мають автодоповнення bash. У терміналі вони запитують те, чого не вказано, а з усіма аргументами працюють у скриптах.
|
||||
- `dsv` керує службами runit: `list` (чи ввімкнена, стан, PID, час роботи), `enable` і `disable` (під час завантаження й одразу), `start`, `stop`, `restart`, `status` і `logs [-f]` (власний журнал служби svlogd або її рядки в socklog).
|
||||
- `dsv new` створює службу для власної програми: команда, користувач, робочий каталог і змінні середовища записуються в `/etc/sv/НАЗВА`, журнал ведеться в `/var/log/НАЗВА`. Потім служба запускається, і `dsv` перевіряє, що вона працює стабільно. `dsv rm` видаляє таку службу, а служби з пакетів не чіпає.
|
||||
- `duser` керує обліковими записами: `add` з тими ж групами, що дає десктопний інсталятор (`--admin` додає `wheel`, `docker` і `socklog`), паролем і SSH-ключем; `passwd`, `lock` і `unlock` (заблокований запис не ввійде й за ключем), `key add|list|rm` для `authorized_keys`, `groups ІМ'Я +ГРУПА -ГРУПА` і `del`. Не дасть видалити чи заблокувати поточного користувача та видалити єдиного адміністратора.
|
||||
- `dfw` відкриває порти в серверному брандмауері: `dfw allow 80/tcp`, `dfw allow https 8000-8010/tcp`, `dfw deny 80/tcp`. Порти зберігаються в `/etc/nftables.d/dfw.nft` і перевіряються через `nft -c` перед завантаженням, а перезавантажується лише таблиця `inet das`. `dfw status` показує ще й порти, опубліковані контейнерами Docker, та адреси, заблоковані fail2ban. SSH лишається відкритим завжди.
|
||||
- `dsys` замінює hostnamectl, timedatectl і localectl: `info` (синхронізація часу, мова, консоль, адреси), `hostname`, `timezone` (досить частини назви: `dsys timezone Kyiv`), `locale` (збирає локаль glibc, якщо треба) і `keymap` (з кириличним шрифтом консолі для кириличних розкладок).
|
||||
- Вони входять у пакет `das-helpers` і є й на десктопі. Обидва образи також містять `xtools-minimal` з Void для роботи з пакетами: `xi` встановлює, `xrs` шукає, `xq` показує відомості про пакет, а `xcheckrestart` знаходить програми, які треба перезапустити після оновлення.
|
||||
|
||||
#### Запрошення терміналу й банер при вході по SSH<a id='Terminal' />
|
||||
**_Дворядкове запрошення bash одразу після встановлення і два редактори в терміналі: для запрошення і для банера, який видно при вході по SSH._**
|
||||
- Стандартне запрошення: `┌─[✗]─[user@host]─[2026-10-21]─[08:12]─[~/dir]` і `└─╼ $` у другому рядку. `[✗]` з'являється лише після команди з помилкою, ім'я root червоне, а без кольорів чи в консолі Linux вигляд той самий.
|
||||
- Його ставить `/etc/bash/bashrc.d/das-prompt.sh`: `~/.config/das/prompt.sh` користувача, інакше `/etc/das/prompt.sh`, інакше стандартне. `/etc/skel/.bashrc` з Void потім задає власний PS1, тож його замінено один раз, а власний PS1 користувача не чіпається.
|
||||
- `dprompt` — конструктор запрошень: готові варіанти (`das`, `das-git` з гілкою git, venv і фоновими задачами, `server`, `minimal`, `classic`), сегменти можна додавати, переставляти й фарбувати, а живий перегляд показує запрошення звичайного користувача, після помилки і для root. Ctrl+S застосовує його для себе, Ctrl+G — для всіх через sudo. Без редактора: `dprompt list`, `dprompt show НАЗВА`, `dprompt apply НАЗВА [--system]`, `dprompt reset`.
|
||||
- Умовні частини (код помилки, git, venv, задачі, SSH, вивід команди) обчислює хук у `PROMPT_COMMAND`, а PS1 лише читає їх, тож назва гілки ніколи не виконується як код.
|
||||
- `dbanner` редагує банер при вході по SSH. Він починає зі списку готових банерів із живим переглядом: `das-classic` (рамка з `#`, назва, IP і посилання), `double` (ім'я хоста великими літерами figlet), `blocks` (логотип Das OS і дані системи), `ukraine` (малий герб України), `tryzub`, `dashboard` і `minimal`.
|
||||
- Редактор малює мишею або з клавіатури: олівець, гумка, лінія, рамки (одинарна, подвійна, заокруглена, жирна, ASCII, `#`, блоки), заливка, текст, великі літери, виділення з копіюванням і вставкою, скасування. Палітра — символи рамок і блоків, 16 кольорів тексту й тла, жирний шрифт. Готові малюнки вставляються одним клацанням: логотип Das OS і тризуб, окремо чи на щиті, у двох розмірах.
|
||||
- Поля показують живі значення фіксованої ширини, тож рамка не зсувається: `{hostname}`, `{ip}`, `{uptime}`, `{mem}`, `{os}`, `{kernel}` та інші, а також власні змінні на кшталт `{url}`; підписи `[[українською|англійською]]` йдуть мовою користувача.
|
||||
- Віджети додають блоки під малюнком або праворуч: диски зі смужками заповнення, пам'ять, навантаження, мережа, контейнери Docker, доступні оновлення, погода з wttr.in, стан дисків SMART, служби runit, що не працюють, блокування fail2ban, знімки snapper, попередній вхід або будь-яка команда чи файл із виводом скрипта.
|
||||
- Швидкі віджети виконуються при вході з коротким тайм-аутом. Повільні чи ті, що потребують root (оновлення, погода, SMART, fail2ban), оновлює служба runit `das-banner` у `/var/cache/das/banner`, і `dbanner apply` вмикає її, коли банерові це треба. Cron не потрібен.
|
||||
- Чернетки лежать у `~/.config/das/banner`; Ctrl+A або `dbanner apply НАЗВА` записує `/etc/das/banner.json` через sudo. `dbanner off` і `on` вимикають банер для всіх, `~/.hushlogin` — для одного користувача; `"local": true` показує його й при вході в консоль.
|
||||
|
||||
#### Аліаси й функції<a id='Aliases' />
|
||||
**_Готові скорочення для щоденної роботи в терміналі в кожному інтерактивному bash, переписані під Void: xbps, nftables, runit і Docker._**
|
||||
- Їх завантажує `/etc/bash/bashrc.d/das-aliases.sh` (пакет `das-shell`): `/usr/share/das/shell/aliases.sh` і `functions.sh`, а потім ваші `~/.bash_aliases` і `~/.bash_functions`, які мають перевагу. `touch ~/.config/das/no-aliases` вимикає аліаси Das OS для одного користувача. `lsfunc` показує функції з описом мовою користувача, `alias` — аліаси.
|
||||
- `ls`: `ll`, `la`, `l`, `sl`, `lsl` (у `less`), `lsz` (за розміром), `lst` (за часом), `lsd` (лише каталоги); `grep`, `diff` і `ip` у кольорі.
|
||||
- Каталоги: `..`, `...`, `....`, `cd..`, `up [N]` (на N рівнів вгору), `mkcd КАТАЛОГ` (створити й перейти), `cl КАТАЛОГ` (перейти й показати), `path` (`PATH` по одному запису в рядку); `mkdir` створює й батьківські каталоги.
|
||||
- Файли: `f ШАБЛОН [КАТАЛОГ]` (пошук за назвою без регістру), `fhere НАЗВА`, `cnt` (кількість файлів), `bigfiles [КАТАЛОГ] [N]`, `dirsize [КАТАЛОГ]` (розміри всього в каталозі), `whatsize [КАТАЛОГ]` (розміри підкаталогів, типово `/`), `bu ФАЙЛ...` (копія з датою в назві), `ednt ФАЙЛ` (редагувати, зберігши час зміни).
|
||||
- Архіви: `extract АРХІВ...` розпаковує tar з будь-яким стисненням, `.xbps`, zip, rar, 7z, gz, bz2, xz, zst, lz4 і Z; `compress НАЗВА.tar.gz|tar.xz|tar.zst|zip|7z ФАЙЛ...` пакує за розширенням назви. Якщо бракує програми, функція називає пакет, який треба встановити.
|
||||
- Диски й пам'ять: `df`, `dft` (з типами й підсумком), `dftl`, `mnt` (змонтовані пристрої), `mounts`, `devid` (lsblk, blkid і lsusb), `snaps` (знімки кореня), `free`, `meminfo`, `cpuinfo`, `gpumeminfo`.
|
||||
- Процеси й історія: `psa ТЕКСТ`, `pst ТЕКСТ` (деревом), `psg`, `psmem`/`psmem10`, `pscpu`/`pscpu10`, `j`, `hgrep ТЕКСТ`, `histat [N]` і `histop10` (найчастіші команди), `EC` (код попередньої команди), `please` (попередня команда ще раз через sudo).
|
||||
- Пакети: `upd` і `updy` (`das-update --cli` зі знімками, `updy` без запитань), `pkgshow [ТЕКСТ]` (встановлені), `pkgfind ТЕКСТ` (у репозиторіях), `pkgowner ФАЙЛ|КОМАНДА`, `pkgclean` (кеш пакетів і сироти).
|
||||
- Мережа: `myip`, `netinfo` (адреси, шлюз, DNS, зовнішня IP), `oports` і `gport ТЕКСТ` (відкриті порти), `ping5`, `fastping`, `header URL` і `headerc URL` (заголовки HTTP, зі стисненням), `listen ПОРТ`, `serve [ПОРТ]` (поточний каталог по HTTP), `rmhost ХОСТ|РЯДОК` (почистити `known_hosts`), `nftlist`, `firewall` (`dfw status`), `weather [МІСТО]`, `cheat КОМАНДА` (cheat.sh).
|
||||
- Docker: `dps`, `dlog КОНТЕЙНЕР`, `dcu`, `dcd`, `dcl`, `dcp` (compose up, down, logs, pull), `lzd`, `lzg`.
|
||||
- Дрібні інструменти: `calc ВИРАЗ`, `epoch [СЕКУНДИ]`, `genpass [ДОВЖИНА]`, `now`, `nowdate`, `note [-c] [ТЕКСТ]` (`~/.notes`), `todo [-l|-r N|-c] [ТЕКСТ]` (`~/.todo`), `csrc ФАЙЛ.c` (скомпілювати й запустити C), `man` з кольоровими заголовками.
|
||||
|
||||
#### Профіль розробника<a id='DevProfile' />
|
||||
**_`dprofile install dev` перетворює десктоп чи сервер на машину для розробки, перед цим створивши знімок системи._**
|
||||
- Пакети з репозиторію Void: `base-devel`, gdb, strace, Git з LFS, GitHub CLI, lazygit, Go з gopls, delve і golangci-lint, Python з pip, virtualenv і uv, Node.js, Docker з Compose і Buildx, lazydocker, Podman з podman-compose, jq, ripgrep, fd, fzf, direnv, shellcheck і SQLite.
|
||||
- На десктопі ще VS Code (відкрита збірка Void `code-oss`, розширення з Open VSX), Meld, Flatpak з репозиторієм Flathub і порталами робочого столу.
|
||||
- Вмикає Docker і додає користувача, який його запустив, до групи `docker`, дає йому підлеглі ідентифікатори для rootless Podman і під час кожного завантаження робить `/` спільним монтуванням (`/etc/runit/core-services/60-das-shared-root.sh`), без чого rootless-контейнери працюють з попередженнями.
|
||||
- Перед встановленням питає, які середовища додати з Flathub: Android Studio, IntelliJ IDEA Open та Ultimate, PyCharm, GoLand, WebStorm чи Visual Studio Code від Microsoft. Пізніше їх додає `dprofile app add GoLand`, а видаляє `dprofile app rm`, разом із рантаймами Flatpak, які більше нічому не потрібні.
|
||||
- Пакети встановлюються між парою знімків snapper pre/post, як і в `das-update`, а наприкінці команда підказує, як відкотитися. `dprofile list` і `dprofile show dev` показують, що вже встановлено; з `--yes` і `--apps ID,...|all|none` команда працює без запитань.
|
||||
- Профіль — звичайний список у `/usr/share/das/profiles/dev.conf`. Десктопний інсталятор його не пропонує: встановлення копіює live-образ без мережі, а профіль завантажує близько 550 МБ пакетів (середовище з Flathub разом із рантаймами — ще 1–4 ГБ).
|
||||
|
||||
#### Оформлення Das OS<a id='Look' />
|
||||
**_Сучасний мінімалізм із синьо-жовтою восьмипелюстковою зіркою з української вишивки._**
|
||||
- GTK-теми `DasOS` і `DasOS-Dark` побудовані на adw-gtk3 і додають акцентні кольори Das OS. Вони також оформлюють панель Xfce, яку adw-gtk3 лишає чорною.
|
||||
- Рамки вікон xfwm4 (заокруглені кути, круглі кнопки, червона кнопка закриття при наведенні) генерує у PNG утиліта `xfwm4gen` (Go) з однієї палітри.
|
||||
- Світлі й темні шпалери зображують зірку над смугою орнаменту в стилі рушника. Та сама зірка є логотипом Das OS і кнопкою меню.
|
||||
- Шрифти: Inter для інтерфейсу і JetBrains Mono в терміналі. Іконки Papirus, тема курсора Adwaita.
|
||||
- Шрифти: Inter для інтерфейсу і JetBrains Mono в терміналі. Іконки Papirus (варіант Papirus-Light, тому значки панелі темні), тема курсора Adwaita.
|
||||
|
||||
#### Налаштування за замовчуванням<a id='Defaults' />
|
||||
**_Пакет das-settings налаштовує Xfce, не чіпаючи файлів інших пакетів._**
|
||||
@@ -70,30 +197,47 @@ Void Linux швидкий, простий і незалежний: runit зам
|
||||
- Одна нижня панель містить меню Whisker, кнопки вікон, трей, розкладку клавіатури, гучність, сповіщення, годинник і кнопку «показати стільницю».
|
||||
- При першому вході `das-session-init` встановлює шпалери на кожному підключеному моніторі, бо Xfce зберігає шпалери за назвою монітора.
|
||||
- LightDM використовує GTK-вітальню з темою і шпалерами Das OS.
|
||||
- Теки домашнього каталогу отримують назви мовою сеансу. xdg-user-dirs 0.20 шукає переклади лише в першій теці з `XDG_DATA_DIRS`, де є підкаталог `locale`, — порожній `/usr/local/share/locale`, тому створював теки англійською. `/etc/X11/xinit/xinitrc.d/50-das-user-dirs` створює їх ще до сеансу Xfce зі шляхом пошуку, де переклади знаходяться.
|
||||
|
||||
#### Власний репозиторій пакетів<a id='Repository' />
|
||||
**_Кожен файл Das OS встановлює й оновлює xbps._**
|
||||
- `srcpkgs/das-theme` містить теми, рамки xfwm4, шпалери й логотип.
|
||||
- `srcpkgs/das-settings` містить налаштування Xfce, LightDM і сесії.
|
||||
- `srcpkgs/adw-gtk3` і `srcpkgs/font-jetbrains-mono` пакують релізи з GitHub, яких немає у Void.
|
||||
- `srcpkgs/das-tools` містить `das-welcome`, вікно оновлення і значок у треї (Go, GTK 3 через gotk3) з пунктами меню й автозапуском. Його підпакет `das-update` — сама команда оновлення й дія polkit, без GTK.
|
||||
- `srcpkgs/das-snapshots` містить конфігурацію snapper, `das-snapper-setup` і dracut-модуль для завантаження знімків.
|
||||
- `srcpkgs/das-installer` містить налаштування й брендинг Calamares, `srcpkgs/das-server-installer` — консольний інсталятор. Спільні для обох кроки (`mount-source`, `postinstall`) лежать у `srcpkgs/das-install-common`. Вони є лише на live-ISO.
|
||||
- `srcpkgs/das-server` — серверний метапакет із налаштуваннями nftables, fail2ban, sshd і Docker. `srcpkgs/das-helpers` містить `dsv`, `duser`, `dfw`, `dsys`, `dprofile` і профілі.
|
||||
- `srcpkgs/publisher` (`publish`) і `srcpkgs/deployer` (`deploy`) — інструменти розробника з TUI і командним рядком: публікують збірки й розгортають проєкти на сервері через ssh + rsync. Конфіг кожного лежить у `~/.config/<назва>/` (приклад копіюється туди при першому запуску), історія — у `~/.local/state/<назва>/`.
|
||||
- `srcpkgs/calamares`, `srcpkgs/adw-gtk3` і `srcpkgs/font-jetbrains-mono` пакують релізи, яких немає у Void.
|
||||
- `make packages` накладає шаблони на неглибокий клон void-packages і збирає їх через `xbps-src` в `out/hostdir/binpkgs`. Збирання ISO встановлює їх звідти.
|
||||
- Пакет перезбирається, лише коли змінився його каталог у `srcpkgs/`, зник бінарний пакет або Void більше не надає бібліотеку, з якою він зібраний (перевіряється пробним встановленням). `DASOS_REBUILD=1 make packages` перезбирає все.
|
||||
|
||||
#### Збирання в контейнері<a id='Build' />
|
||||
**_Усе збирання відбувається в Docker-контейнері, тож на хості потрібні лише Docker і QEMU._**
|
||||
1. `make packages` збирає локальний xbps-репозиторій.
|
||||
2. `make iso` збирає пакети, а потім ISO через `void-mklive` за профілем у `live/` (список пакетів, сервіси, оверлей кореневої файлової системи).
|
||||
3. `make qemu` завантажує найновіший ISO з UEFI. `make qemu-bios` використовує старий BIOS, а `make qemu-disk` ще й підключає віртуальний диск на 20 ГБ.
|
||||
2. `make iso` збирає пакети, а потім ISO через `void-mklive` за профілем у `live/` (список пакетів, сервіси, оверлей кореневої файлової системи). `make iso-server` робить те саме для серверного профілю в `server/`.
|
||||
3. `make qemu` завантажує найновіший ISO з UEFI. `make qemu-bios` використовує старий BIOS, а `make qemu-disk` ще й підключає віртуальний диск на 20 ГБ для встановлення. `make qemu-server` завантажує серверний ISO з диском на послідовній консолі прямо в терміналі (вихід — Ctrl-A X). SSH гостьової системи доступний на `localhost:2222` (змінює `DASOS_SSH_PORT`).
|
||||
|
||||
#### Збирання в CI<a id='CI' />
|
||||
**_Кожен push у `main` чи `dev` і кожен тег `v*` збирає пакети й обидва ISO на git.das.org.ua._**
|
||||
- Workflow `.gitea/workflows/build.yml` запускає `make iso iso-server` на власному runner з міткою `dasci`.
|
||||
- Runner — дистрибутив WSL2 на Windows-машині: Ubuntu 24.04 з Docker Engine і `gitea-runner` як службою systemd. Завдання планувальника запускає його під час завантаження Windows, вхід у систему не потрібен.
|
||||
- `ci/runner/setup.sh` встановлює Docker, runner і його `config.yaml`; `setup.sh register <токен>` реєструє його, `autostart.ps1` створює завдання планувальника.
|
||||
- `DASOS_OUT` виносить `out/` за межі робочої копії, тож void-packages, підготовлений masterdir і кеш завантажень зберігаються між збираннями.
|
||||
- Тег `v*`, що збігається з `VERSION`, ще й публікує обидва ISO та `SHA256SUMS` як реліз (`ci/release.sh`, з власним токеном завдання): `git tag v0.8.0 && git push origin v0.8.0`. Інші збирання залишають ISO на CI-хості в `/var/lib/dasos-ci/out/iso`.
|
||||
|
||||
### 🚀 Збирання застосунку
|
||||
Вимоги: Linux із Docker (користувач має бути в групі `docker`), QEMU та OVMF для тестування, Go 1.22+ для `make test`. Приблизно 10 ГБ вільного місця.
|
||||
Вимоги: Linux із Docker (користувач має бути в групі `docker`), QEMU та OVMF для тестування, Go 1.24+ і файли розробки GTK 3 та Python 3 для `make test`. Приблизно 10 ГБ вільного місця.
|
||||
```shell
|
||||
make iso # зібрати пакети й live-ISO (перший запуск завантажує ~2 ГБ)
|
||||
make qemu # запустити його в QEMU з UEFI
|
||||
make test # запустити Go-тести
|
||||
make iso-server # зібрати пакети й серверний ISO
|
||||
make qemu-server # запустити його на послідовній консолі з диском для встановлення
|
||||
make test # запустити тести Go і Python
|
||||
make clean # видалити результати збирання, зберегти кеш завантажень
|
||||
```
|
||||
ISO з'являється в `out/iso/dasos-<версія>-x86_64.iso`. Записати його на флешку можна через `dd` або Ventoy.
|
||||
ISO з'являються в `out/iso/dasos-<версія>-x86_64.iso` і `out/iso/dasos-server-<версія>-x86_64.iso`. Записати їх на флешку можна через `dd` або Ventoy. Для теми Ventoy є значок меню 48×48 `live/branding/ventoy/dasos.png`: скопіюйте його в теку `icons/` теми й додайте `{ "key": "dasos", "class": "dasos" }` до `menu_class` у `ventoy.json`.
|
||||
|
||||
**Примітка:** Das OS — рання версія (0.1.0), і live-ISO поки не вміє встановлюватися на диск. Інсталятор Calamares, знімки Btrfs із відкатом, Go-утиліти `das-welcome` і `das-update`, профіль розробника та підписаний репозиторій на хостингу заплановані на наступні етапи. Das OS базується на Void Linux, але не пов'язана з ним офіційно.
|
||||
**Примітка:** Das OS — рання версія (0.9.3). Пакети й ISO збираються в CI; підписаний репозиторій на хостингу заплановано на наступний етап. Поки репозиторій не розміщено на хостингу, встановлена система отримує оновлення лише з Void. Das OS базується на Void Linux, але не пов'язана з ним офіційно.
|
||||
|
||||
###### _Made by -=:dAs:=-_
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# Das OS — що зробити далі
|
||||
|
||||
Стан на 2026-10-25: версія 0.9.8 (не закомічено), етапи 1–8 готові (live ISO, власний репозиторій, оформлення, інсталятор Calamares зі знімками Btrfs, Go-утиліти `das-update` і `das-welcome`, серверний образ з консольним інсталятором, менші ISO, консольні помічники, профіль розробника), CI (етап 9: пакети й обидва ISO збираються на власному runner, ~45 хв).
|
||||
|
||||
## Етап 5. Go-утиліти (готово, лишилися перевірки)
|
||||
- [x] `das-update`: вікно, значок у треї (лише коли є оновлення), консольний режим `--cli`, знімок snapper pre/post, встановлення через pkexec, підказка про відкат.
|
||||
- [x] `das-welcome`: вікно першого входу (встановлення в live-сесії, мова, теки, оновлення, налаштування, довідка).
|
||||
- [x] Англійські назви тек: xdg-user-dirs 0.20 шукає переклади лише в першій теці `XDG_DATA_DIRS` з підкаталогом `locale` (порожня `/usr/local/share/locale`). Виправлено в `das-settings` (`/etc/X11/xinit/xinitrc.d/50-das-user-dirs`), уже створені теки перейменовує `das-welcome`.
|
||||
- [x] Тести `go test ./...` (`make test` і під час збирання пакета).
|
||||
- [x] Перевірено у встановленій системі на Btrfs (QEMU, UEFI): оновлення з вікна й `--cli` створює пару знімків pre/post, вони з'являються в меню GRUB, завантаження в знімок і `snapper-rollback` повертають попередню версію пакета; вибір мови в `das-welcome` діє після перезавантаження.
|
||||
- [x] Виправлено після перевірки: версія в Calamares береться з `VERSION`; значок у треї ховається, коли оновлення встановлено поза ним (стежить за базою пакетів xbps); інструменти VirtualBox прибираються з не-VirtualBox машин (у KVM було повідомлення при кожному вході); live-сесія не блокує екран під час встановлення.
|
||||
- [x] Live ISO 0.4.0: екран не блокується під час встановлення.
|
||||
- [ ] Вікно автентифікації polkit (`xfce-polkit`) англійською: перекласти або замінити агент.
|
||||
- [ ] Повідомити авторів xdg-user-dirs про помилку з `XDG_DATA_DIRS`.
|
||||
- [x] Пункт «Профіль розробника» в `das-welcome` (етап 8).
|
||||
|
||||
## Етап 6. Серверний образ (готово)
|
||||
Live ISO без графіки з консольним інсталятором: `dasos-server-<версія>-x86_64.iso`.
|
||||
- [x] Профіль `server/` поруч із `live/`; `build-iso.sh [desktop|server]`, цілі `make iso-server` і `make qemu-server`. Kernel cmdline `console=tty0 console=ttyS0,115200`, вхід і на послідовній консолі.
|
||||
- [x] Метапакет `das-server`: openssh, chrony, dhcpcd, NetworkManager, docker + docker-compose, nftables, fail2ban, socklog-void, python3, curl, lazydocker, lazygit, mc, tmux, htop, das-snapshots, das-update, das-helpers, xtools-minimal.
|
||||
- [x] Налаштування: nftables «закрито все, крім SSH» (лише своя таблиця `inet das`, правила Docker і fail2ban не чіпає), jail sshd через nftables з розбором журналу socklog, sshd `PermitRootLogin prohibit-password`, журнали Docker з ротацією (`local`).
|
||||
- [x] `dsv`: `list`, `status`, `enable`, `disable`, `start`, `stop`, `restart`, `logs [-f]`, автодоповнення bash; є й на десктопі.
|
||||
- [x] `das-update` окремим підпакетом без GTK (вікно й трей — `/usr/libexec/das/das-update-gui` у `das-tools`).
|
||||
- [x] `das-server-install`: TUI на `dialog` українською й англійською (диск, ім'я, користувач і пароль, SSH-ключ і заборона паролів, часовий пояс, розкладка, мова, dhcpcd/NetworkManager) і режим без запитань `--config`. GPT + Btrfs з тими ж підтомами, GRUB для UEFI (і запасний `BOOTX64.EFI`) та BIOS.
|
||||
- [x] Спільний пакет `das-install-common` (`mount-source`, `postinstall`) для обох інсталяторів; `das-snapper-setup` вмикає `dbus`, без нього snapper не працює.
|
||||
- [x] `scripts/run-qemu.sh --serial`.
|
||||
- [x] Перевірено в QEMU: встановлення через TUI на UEFI (з SSH-ключем) і BIOS (з паролем, NetworkManager, `ua-utf`), вхід по SSH лише за ключем, `docker run hello-world`, опублікований порт nginx, fail2ban бачить невдалі входи, `das-update --cli` зі знімками pre/post, `dsv`.
|
||||
- [x] `das-server-install --config` перевірено (0.5.0, UEFI).
|
||||
- [x] Розмір ISO: див. нижче.
|
||||
- [x] Запрошення до входу (`/etc/issue`) показувало версію ядра (`\r` agetty, 6.18): тепер версія Das OS з `VERSION` (`@VERSION@` у `branding/issue`), серверний інсталятор переносить перший рядок у встановлену систему (0.9.2).
|
||||
- [x] Українська в `das-server-install` на консолі показувалася латиницею («Ukra■ns'ka»): стандартний шрифт підміняє кирилицю схожими латинськими літерами, а `setfont` не спрацьовував, бо `sudo` запускає інсталятор у власному pty (`tty` → `/dev/pts/N`). Тепер `setfont -C /dev/tty0 cyr-sun16` до вибору мови (перевірено в QEMU: «Гаразд їєіґ»).
|
||||
- [x] Серед дисків для встановлення був дисковод `/dev/fd0`: тепер пропускається.
|
||||
- [x] Перевірено серверний ISO 0.9.2 у QEMU: «Das OS Server 0.9.2 (tty1)» у запрошенні, «Українська» в меню мови, `/dev/fd0` немає серед дисків.
|
||||
|
||||
### Розмір ISO: 0.4.0 → 0.5.0
|
||||
Десктоп 1,96 → 1,72 ГБ, сервер 1,57 → 1,14 ГБ.
|
||||
- [x] `initrd` live-образу без драйверів `amdgpu`/`radeon`/`nouveau`/`i915`/`xe` і їхніх прошивок (`--omit-drivers` у виклику dracut у mklive): 245 → 89 МБ в обох ISO. Драйвери завантажуються з кореневої системи; десктоп у QEMU доходить до робочого столу.
|
||||
- [x] Сервер без `linux-firmware-nvidia`, `sof-firmware`, `alsa-firmware`: `mklive -g` і `/etc/xbps.d/das-server.conf` (`ignorepkg`), тож оновлення їх не повертають. Службовий `mklive-ignore.conf` інсталятори видаляють.
|
||||
- [x] squashfs з блоком 1 МБ і фільтром BCJ x86: десктоп −5 %; сервер разом із прошивками 1274 → 999 МБ.
|
||||
- [ ] Перевірити на реальному залізі з AMD/Intel/NVIDIA, що графіка вмикається після `switch_root` (у QEMU — virtio-gpu).
|
||||
- [ ] Переклади `/usr/share/locale` (265 МБ на десктопі, 98 МБ на сервері) лише для uk і en через `noextract` у xbps: ≈ −60…80 МБ на десктопі. Мінус: інші мови інтерфейсу доведеться доставляти перевстановленням пакетів.
|
||||
- [ ] Десктоп: зайві варіанти Papirus (`ePapirus*`, `Papirus-Light`) з 294 МБ іконок; з'ясувати, хто тягне gtk4, flite, ghostscript.
|
||||
- Не чіпати: Firefox (299 МБ), ядро й модулі (180 МБ), LLVM для Mesa (141 МБ), мережеві прошивки (442 МБ, Wi-Fi та мережеві карти).
|
||||
|
||||
## Етап 7. Консольні помічники (готово)
|
||||
Пакет `das-helpers` (на десктопі й сервері), спільні функції в `/usr/lib/das/helpers.sh`, українська й англійська, автодоповнення bash, запитання в терміналі й аргументи для скриптів.
|
||||
- [x] `dsv` переїхав у `das-helpers`; `dsv new` (команда, користувач, каталог, змінні, журнал svlogd, перевірка, що служба не падає) і `dsv rm` (лише не з пакетів).
|
||||
- [x] `duser`: `list`, `add` (групи як у Calamares, `--admin`, ключ, пароль), `del`, `passwd`, `lock`/`unlock` (і вхід за ключем), `key add|list|rm`, `groups +/-`; захист від видалення чи блокування себе та єдиного адміністратора.
|
||||
- [x] `dfw`: `status`, `allow`, `deny`, `reload`; набори `tcp_ports`/`udp_ports` у таблиці `inet das`, порти в `/etc/nftables.d/dfw.nft`, перевірка `nft -c`; показує порти Docker і заблоковані fail2ban адреси.
|
||||
- [x] `dsys`: `info`, `hostname`, `timezone`, `locale`, `keymap`.
|
||||
- [x] `xtools-minimal` (`xi`, `xq`, `xrs`, `xcheckrestart`) в обох образах.
|
||||
- [x] Виправлено: коментар у `nftables.conf` радив `dsv restart nftables`, а `finish` служби Void робить `nft flush ruleset` і стирає правила fail2ban та Docker. Тепер `dfw reload`.
|
||||
- [x] Перевірено на встановленому сервері: `dsv new` з Python-сервером на 8080, доступ з хоста після `dfw allow` і закриття після `dfw deny`; `duser add` з ключем, вхід, `lock` блокує й вхід за ключем; `dsys` (часовий пояс, ім'я, мова, розкладка); `dfw reload` поруч із контейнером nginx.
|
||||
- [x] Повідомлення наприкінці `das-server-install` згадує `dsv`, `duser`, `dfw`, `dsys`.
|
||||
|
||||
## Етап 8. Профіль розробника (готово)
|
||||
Не метапакет, а `dprofile` у `das-helpers` зі списком пакетів Void у `/usr/share/das/profiles/dev.conf`: встановлена система поки не бачить репозиторію Das OS, тож метапакет `das-dev` звідти не встановився б, а пакети Void доступні завжди.
|
||||
- [x] `dprofile install dev`: base-devel, gdb, strace, git, git-lfs, github-cli, lazygit, go, gopls, delve, golangci-lint, python3-pip, virtualenv, uv, nodejs, docker, docker-compose, docker-buildx, lazydocker, podman, podman-compose, jq, ripgrep, fd, fzf, direnv, shellcheck, sqlite; на десктопі ще vscode (`code-oss`), meld, flatpak і портали. Пара знімків snapper pre/post, підказка про відкат; спершу оновлює xbps, якщо треба.
|
||||
- [x] Налаштування: служба docker, група `docker` для того, хто запустив, `/etc/subuid`/`subgid` для rootless Podman, `/` як спільне монтування (`/etc/runit/core-services/60-das-shared-root.sh`; runit лишає його приватним, і Podman попереджає).
|
||||
- [x] Flathub на вимогу (лише десктоп): Android Studio, IntelliJ IDEA Open/Ultimate, PyCharm, GoLand, WebStorm, VS Code від Microsoft; вибір номерами перед встановленням, `dprofile app add|rm` (з `--unused`, бо рантайми займають 1–4 ГБ). PyCharm Community на Flathub застарів (2024.3), тому `PyCharm-Professional` (безкоштовний режим).
|
||||
- [x] Запитання й план — до `sudo`, пароль питається після них; `--yes`, `--apps ID,...|all|none` для скриптів.
|
||||
- [x] `das-welcome`: рядок «Профіль розробника» відкриває `xfce4-terminal` з `dprofile install dev`; після встановлення — «Додати середовища».
|
||||
- [x] Calamares packagechooser не додаємо: інсталятор копіює live-образ без мережі, а профіль завантажує ≈ 550 МБ.
|
||||
- [x] Перевірено: сервер (QEMU) — `go run`, Node.js, uv, `docker run`, rootless `podman run` без попередження після перезавантаження; десктоп — встановлення з `das-welcome`, Code - OSS і VS Code з Flathub у меню після входу, `dprofile app rm` звільняє рантайми.
|
||||
- [x] Повідомлення наприкінці `das-server-install` згадує `dprofile install dev`.
|
||||
|
||||
## Етап 9. CI (готово)
|
||||
- [x] Workflow `.gitea/workflows/build.yml`: `make iso iso-server` на власному runner `dasci:host` (push у `main`/`dev`, теги `v*`, ручний запуск).
|
||||
- [x] CI-хост: Windows-машина (192.168.88.193), WSL2 Ubuntu 24.04 `dasci` у `C:\DasOS\wsl` (SSD), Docker Engine, `gitea-runner` 5.1.1 як служба systemd, автозапуск завданням планувальника (`ci/runner`).
|
||||
- [x] `DASOS_OUT` у `scripts/builder.sh`: кеш `out/` на CI-хості в `/var/lib/dasos-ci/out`.
|
||||
- [x] Runner зареєстровано, перший запуск у Gitea (run 1) успішний за 46 хв.
|
||||
- [x] Після перезавантаження Windows runner піднімається сам, без входу в систему (завдання S4U).
|
||||
- [x] Тег `v*` публікує обидва ISO і `SHA256SUMS` як реліз Gitea (`ci/release.sh`). Ліміт вкладення релізу — 2 ГБ на файл (`[repository.release] FILE_MAX_SIZE`).
|
||||
- [x] Перша публікація: реліз v0.8.0 з обома ISO і `SHA256SUMS` (завантаження ~9 хв). Перша спроба впала на 502: Traefik на git.das.org.ua обривав запити довші за 60 с, `readTimeout` піднято до 30m.
|
||||
|
||||
## Етап 10. Термінал (0.9.1)
|
||||
Пакет `das-shell` (десктоп і сервер), Python + Textual.
|
||||
- [x] Запрошення bash за замовчуванням, як на робочому комп'ютері автора: `┌─[✗]─[user@host]─[дата]─[час]─[каталог]` / `└─╼ $` (`/etc/bash/bashrc.d/das-prompt.sh`, заміна PS1 зі `/etc/skel/.bashrc` Void).
|
||||
- [x] `dprompt`: конструктор запрошень (5 готових, сегменти, кольори, живий перегляд), застосування для себе або для всіх.
|
||||
- [x] `dbanner`: редактор банера при вході по SSH (7 готових, полотно з інструментами, поля зі змінними, віджети, служба `das-banner` для кешу), малюнки: логотип Das OS і тризуб (`tools/art`).
|
||||
- [x] `scripts/run-qemu.sh`: SSH гостя на `localhost:2222`.
|
||||
- [x] Запрошення за замовчуванням: `└─╼` другого рядка міг бути білим, бо колір успадковувався з першого рядка, а readline перемальовує лише останній. Тепер кожен рядок задає колір заново (das-shell 0.2.0).
|
||||
- [x] Перевірено автором на сервері (das-shell 0.3.0): `└─╼` червоний, і після кольорового виводу, і після помилки.
|
||||
- [x] `dbanner`: кнопки внизу стартового екрана перекривав рядок підказок клавіш (обидва були прикріплені до низу): тепер кнопки — звичайний рядок під списком.
|
||||
- [x] У консолі Linux (`TERM=linux`) рамки кнопок, полів і списків `dprompt`/`dbanner` були квадратиками: Textual малює їх символами `▊▎▔▁`, яких немає в жодному консольному шрифті (до 512 символів; перевірено `cyr-sun16`, `default8x16`, `eurlatgr`, Terminus). Тепер там рамки `┌─┐`, а решта відсутніх символів замінюється на наявні. Символи палітри малювання (★ ☀ ♥…) лишаються як є: це вміст банера для SSH-клієнтів.
|
||||
- [x] Перевірено автором у консолі сервера: `dprompt` і `dbanner` з рамками `┌─┐` без квадратиків, кнопки `dbanner` видно повністю.
|
||||
- [x] Аліаси й функції для bash (das-shell 0.3.0, 0.9.3): на основі `~/.bash_aliases` і `~/.bash_functions` автора, переписані під Void (xbps, nftables, `das-update`), з виправленими помилками (`rmhost`, `netinfo`, `todo -r`, `dirsize`, `extract`), двомовними описами для `lsfunc` і новими функціями (`up`, `bigfiles`, `pkgowner`, `please`, `serve`, `weather`, `cheat`, `genpass`, скорочення Docker). Перейменовано те, що затіняло команди: `gh` → `hgrep` (GitHub CLI), `install` прибрано (є `xi`), `mount` → `mounts`, `ed` прибрано.
|
||||
- [x] Аліаси й функції перевірено автором на сервері (das-shell 0.3.0 з локального репозиторію).
|
||||
- [x] Аліаси й функції перевірено автором на десктопі (ISO 0.9.3).
|
||||
- [x] Перевірено автором на реальному сервері (0.9.1): банер `das-classic` з IP при вході по SSH, свій банер і `dbanner apply ukraine`, `dbanner apply dashboard` вмикає службу `das-banner` (погода й оновлення з'являються), запрошення Das і в SSH, і в консолі (`>` замість `╼`).
|
||||
- [x] `ssh host cmd` виконується без банера (перевірено автором).
|
||||
- [x] Десктоп: `dprompt` і `dbanner` перевірено автором (ISO 0.9.3).
|
||||
- [x] Вихід з `dprompt` і `dbanner` однаково Ctrl+Q (у `dbanner` на стартовому екрані лишилась і `q`); 0.9.4.
|
||||
- [ ] Десктоп, xfce4-terminal: `└─╼` сірий, а не червоний (у консолі сервера червоний). З'ясувати.
|
||||
- [ ] Узгодити решту клавіш: у `dprompt` Ctrl+S «застосувати», у `dbanner` Ctrl+S «зберегти», Ctrl+A «застосувати».
|
||||
|
||||
## Репозиторій пакетів
|
||||
- [ ] Підпис через `xbps-rindex --sign`, ключ поза репозиторієм.
|
||||
- [ ] Хостинг репозиторію (власний сервер або GitHub Releases/Pages).
|
||||
- [ ] `/etc/xbps.d/das.conf` у встановленій системі, щоб пакети `das-*` оновлювалися. Зараз встановлена система бачить лише репозиторій Void.
|
||||
|
||||
## Інсталятор
|
||||
- [ ] Перевірити встановлення десктопа на старий BIOS (MBR, `grub-install --target=i386-pc`); серверний інсталятор на BIOS перевірено.
|
||||
- [ ] Перевірити на реальному залізі: видалення гостьових інструментів VM, Wi-Fi, NVIDIA/AMD.
|
||||
- [ ] Шифрування диска (LUKS): `luksbootkeyfile`, crypttab, dracut, `GRUB_ENABLE_CRYPTODISK`.
|
||||
- [ ] Перевірити ручну розмітку з Btrfs (чи створюються підтоми) і встановлення на ext4/XFS (знімки мають вимкнутися без помилок).
|
||||
- [ ] Перевірити розкладку, відмінну від `us,ua` (автологін перевірено).
|
||||
|
||||
## Знімки й відкат
|
||||
- [ ] Перевірити, що cron справді робить щогодинні знімки й очищує старі.
|
||||
- [ ] Графічний відкат (у `das-update` або окремою утилітою) замість `sudo snapper-rollback N`.
|
||||
- [ ] Прибирати старі корені `@<дата>`, які лишаються після відкату.
|
||||
|
||||
## Оформлення та дрібниці
|
||||
- [x] Світла тема: значки панелі (трей, звук, розкладка) були блідо-сірими на світлій панелі; тепер текст і значки `#1f2430` (`das-theme` 0.2.0, 0.9.4; перевірити на ISO).
|
||||
- [x] Значок кнопки меню на вибір: `das-panel-icon star|trident` (зірка `das-logo` за замовчуванням, тризуб `das-trident` жовтий із синім контуром); 0.9.4, перевірити на ISO.
|
||||
- [x] Значки Wi-Fi і Bluetooth у треї лишались блідими: панельні значки Papirus (`#dfdfdf`) зроблені для темних панелей, а CSS їх не перефарбовує. Світла тема тепер на `Papirus-Light` (`#444`): `xsettings.xml`, `index.theme`, екран входу; `das-theme` 0.3.0, `das-settings` 0.6.0, 0.9.5; перевірити на ISO.
|
||||
- [x] `das-panel-icon help` (`-h`, `--help`) показує підказку, а не ставить значок «?»; невідомі `-ключі` відхиляються.
|
||||
- [x] На десктопний образ додано `publisher` (`publish`) і `deployer` (`deploy`): копії проєктів з `~/Dev/Projects/Python`, конфіг у `~/.config/<назва>/` (створюється з прикладу при першому запуску), історія в `~/.local/state/<назва>/`; перевірити на ISO.
|
||||
- [x] Зміну шляхів конфігу та `history.log` перенесено в оригінальні `publisher` і `deployer` (`config.default_path()`, `pipeline._history_path()`): у checkout усе як було, у пакеті — `~/.config` і `~/.local/state`. Копії пакетів відрізняються від оригіналів лише значеннями за замовчуванням (localhost, user).
|
||||
- [x] У десктопний образ і в `das-server` 0.5.0 додано `nano`: `publish` і `deploy` відкривають конфіг у редакторі за F2 (`$VISUAL`/`$EDITOR`, інакше `nano`), без нього завершувались; 0.9.6.
|
||||
- [x] У десктопний образ додано `curl` (у `das-server` він уже був); 0.9.7.
|
||||
- [x] `publish` і `deploy` падали з `BrokenPipeError`, коли `ssh` завершується одразу (недоступний хост): тепер це `RemoteError` з повідомленням `ssh` (код 255 завжди помилка), тест з фіктивним `ssh`; `publisher`/`deployer` 0.3.1, 0.9.8; виправлено й в оригіналах.
|
||||
- [ ] Прибрати значки томів live-носія («Том 6,6 ГБ») з робочого столу live-сесії.
|
||||
- [ ] Тема GRUB у встановленій системі (зараз стандартний чорний екран).
|
||||
- [ ] Перевірити меню Whisker вручну.
|
||||
- [x] `ncdu` (аналіз зайнятого місця на диску в терміналі): у десктопному образі й у `das-server` 0.4.0.
|
||||
- [x] Прискорити `make packages`: пакет перезбирається лише після змін у `srcpkgs/<назва>`, без бінарного пакета або коли у Void зникла потрібна йому бібліотека (`xbps-install -n`). `DASOS_REBUILD=1` перезбирає все.
|
||||
|
||||
## Відкладено
|
||||
- [ ] ARM-образ для Raspberry Pi.
|
||||
- [ ] Вбудувати текстовий редактор MultiText.
|
||||
- [ ] Перший стабільний реліз: злиття `dev` → `main`.
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
# Publishes the ISO images of the current version as a Gitea release:
|
||||
# release.sh <tag> <iso dir>
|
||||
# Needs GITEA_TOKEN, GITEA_SERVER_URL and GITEA_REPOSITORY (owner/name); in CI
|
||||
# the job provides them. Running it again for the same tag replaces the files.
|
||||
set -eu
|
||||
|
||||
tag=$1
|
||||
dir=$2
|
||||
version=$(cat VERSION)
|
||||
[ "$tag" = "v$version" ] || { echo "release: tag $tag does not match VERSION $version" >&2; exit 1; }
|
||||
|
||||
api=$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY
|
||||
auth="Authorization: token $GITEA_TOKEN"
|
||||
json() { python3 -c "import json,sys; d=json.load(sys.stdin); $1"; }
|
||||
|
||||
isos="dasos-$version-x86_64.iso dasos-server-$version-x86_64.iso"
|
||||
cd "$dir"
|
||||
sha256sum $isos >SHA256SUMS
|
||||
|
||||
if ! id=$(curl -fsS -H "$auth" "$api/releases/tags/$tag" 2>/dev/null | json 'print(d["id"])' 2>/dev/null); then
|
||||
id=$(curl -fsS -H "$auth" -H 'Content-Type: application/json' \
|
||||
-d "{\"tag_name\":\"$tag\",\"name\":\"Das OS $version\",\"body\":\"Desktop and server live ISO images. Check them with sha256sum -c SHA256SUMS.\"}" \
|
||||
"$api/releases" | json 'print(d["id"])')
|
||||
echo "release: created $tag (id $id)"
|
||||
fi
|
||||
|
||||
# Files left from an earlier run for this tag are replaced
|
||||
curl -fsS -H "$auth" "$api/releases/$id/assets" |
|
||||
json '[print(a["id"], a["name"]) for a in d]' |
|
||||
while read -r asset name; do
|
||||
case " $isos SHA256SUMS " in
|
||||
*" $name "*) curl -fsS -X DELETE -H "$auth" "$api/releases/$id/assets/$asset" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
for f in $isos SHA256SUMS; do
|
||||
echo "release: uploading $f"
|
||||
curl -fsS -o /dev/null -H "$auth" -F "attachment=@$f" "$api/releases/$id/assets?name=$f"
|
||||
done
|
||||
echo "release: $GITEA_SERVER_URL/$GITEA_REPOSITORY/releases/tag/$tag"
|
||||
@@ -0,0 +1,12 @@
|
||||
# Starts the dasci WSL distro (Docker and gitea-runner inside) when Windows
|
||||
# boots, without anyone logging in. Run once in an elevated PowerShell.
|
||||
# The task keeps a process attached so WSL never stops the distro as idle.
|
||||
$wsl = 'C:\Program Files\WSL\wsl.exe'
|
||||
$action = New-ScheduledTaskAction -Execute $wsl -Argument '-d dasci --exec /bin/sleep infinity'
|
||||
$trigger = New-ScheduledTaskTrigger -AtStartup
|
||||
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType S4U -RunLevel Highest
|
||||
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1)
|
||||
Register-ScheduledTask -TaskName 'DasOS CI (WSL dasci)' -Action $action -Trigger $trigger `
|
||||
-Principal $principal -Settings $settings -Force | Out-Null
|
||||
Get-ScheduledTask -TaskName 'DasOS CI (WSL dasci)' | Select-Object TaskName, State
|
||||
@@ -0,0 +1,23 @@
|
||||
# gitea-runner configuration for the Das OS CI host (WSL2 distro "dasci").
|
||||
# Jobs run directly on the host: scripts/builder.sh needs the host's Docker
|
||||
# and paths it can bind-mount into the privileged Void builder container.
|
||||
log:
|
||||
level: info
|
||||
|
||||
runner:
|
||||
file: /var/lib/gitea-runner/.runner
|
||||
# One build at a time: packages and ISOs share the cache in DASOS_OUT
|
||||
capacity: 1
|
||||
# Matches Gitea's default job timeout; the first build bootstraps void-packages
|
||||
timeout: 3h
|
||||
envs:
|
||||
DASOS_OUT: /var/lib/dasos-ci/out
|
||||
labels:
|
||||
- "dasci:host"
|
||||
|
||||
cache:
|
||||
# Build caches live in DASOS_OUT, the workflow does not use actions/cache
|
||||
enabled: false
|
||||
|
||||
host:
|
||||
workdir_parent: /var/lib/gitea-runner/work
|
||||
@@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=Gitea Actions runner for Das OS
|
||||
After=docker.service network-online.target
|
||||
Wants=network-online.target
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
User=runner
|
||||
Group=runner
|
||||
WorkingDirectory=/var/lib/gitea-runner
|
||||
ExecStart=/usr/local/bin/gitea-runner -c /etc/gitea-runner/config.yaml daemon
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/bin/sh
|
||||
# Sets up the Das OS CI host: Docker Engine, the runner user and gitea-runner.
|
||||
# Run as root inside a fresh Ubuntu 24.04 WSL2 distro with systemd enabled:
|
||||
# sh setup.sh install everything
|
||||
# sh setup.sh register <token> register the runner with git.das.org.ua
|
||||
# Safe to run again.
|
||||
set -eu
|
||||
|
||||
RUNNER_VERSION=5.1.1
|
||||
INSTANCE=https://git.das.org.ua
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
|
||||
if [ "${1:-}" = register ]; then
|
||||
[ -n "${2:-}" ] || { echo "usage: setup.sh register <token>" >&2; exit 1; }
|
||||
sudo -u runner /usr/local/bin/gitea-runner -c /etc/gitea-runner/config.yaml \
|
||||
register --no-interactive --instance "$INSTANCE" --token "$2" --name dasci
|
||||
systemctl restart gitea-runner
|
||||
exit 0
|
||||
fi
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -q
|
||||
apt-get install -yq ca-certificates curl git make xz-utils
|
||||
|
||||
# Docker Engine from Docker's repository (Ubuntu's docker.io lags behind)
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||
. /etc/os-release
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $VERSION_CODENAME stable" \
|
||||
>/etc/apt/sources.list.d/docker.list
|
||||
apt-get update -q
|
||||
apt-get install -yq docker-ce docker-ce-cli containerd.io docker-buildx-plugin
|
||||
systemctl enable --now docker
|
||||
|
||||
# Jobs run as an unprivileged user: build-packages.sh creates its xbps-src user
|
||||
# with the caller's UID, which must not be root
|
||||
id runner >/dev/null 2>&1 || useradd --create-home --shell /bin/bash --groups docker runner
|
||||
install -d -o runner -g runner /var/lib/gitea-runner /var/lib/dasos-ci /var/lib/dasos-ci/out
|
||||
|
||||
url=https://gitea.com/gitea/runner/releases/download/v$RUNNER_VERSION/gitea-runner-$RUNNER_VERSION-linux-amd64.xz
|
||||
tmp=$(mktemp -d)
|
||||
curl -fsSL -o "$tmp/gitea-runner.xz" "$url"
|
||||
curl -fsSL -o "$tmp/gitea-runner.xz.sha256" "$url.sha256"
|
||||
(cd "$tmp" && echo "$(cut -d' ' -f1 gitea-runner.xz.sha256) gitea-runner.xz" | sha256sum -c --quiet)
|
||||
xz -d "$tmp/gitea-runner.xz"
|
||||
install -m 0755 "$tmp/gitea-runner" /usr/local/bin/gitea-runner
|
||||
rm -rf "$tmp"
|
||||
|
||||
install -D -m 0644 "$here/config.yaml" /etc/gitea-runner/config.yaml
|
||||
install -m 0644 "$here/gitea-runner.service" /etc/systemd/system/gitea-runner.service
|
||||
systemctl daemon-reload
|
||||
systemctl enable gitea-runner
|
||||
# The runner starts once it is registered
|
||||
if [ -f /var/lib/gitea-runner/.runner ]; then
|
||||
systemctl restart gitea-runner
|
||||
else
|
||||
echo "Now register: sh setup.sh register <token>"
|
||||
fi
|
||||
gitea-runner --version
|
||||
@@ -0,0 +1,652 @@
|
||||
# Як створити власний дистрибутив Linux
|
||||
|
||||
Докладна інструкція: від вибору основи до опублікованого релізу. Загальні поради підкріплено прикладами з Das OS (Void Linux + Xfce): шляхи на кшталт `scripts/container/build-iso.sh` ведуть до файлів цього репозиторію, а позначка **«Досвід Das OS»** — до реальних помилок, на які ми натрапили.
|
||||
|
||||
## Зміст
|
||||
1. [Що таке «власний дистрибутив»](#1-що-таке-власний-дистрибутив)
|
||||
2. [Планування перед першим рядком коду](#2-планування-перед-першим-рядком-коду)
|
||||
3. [Вибір основи](#3-вибір-основи)
|
||||
4. [Середовище збирання](#4-середовище-збирання)
|
||||
5. [Власні пакети й репозиторій](#5-власні-пакети-й-репозиторій)
|
||||
6. [Як влаштований live-образ](#6-як-влаштований-live-образ)
|
||||
7. [Збирання ISO](#7-збирання-iso)
|
||||
8. [Налаштування за замовчуванням і оформлення](#8-налаштування-за-замовчуванням-і-оформлення)
|
||||
9. [Інсталятор](#9-інсталятор)
|
||||
10. [Завантажувач, initramfs і розмітка диска](#10-завантажувач-initramfs-і-розмітка-диска)
|
||||
11. [Оновлення, знімки й відкат](#11-оновлення-знімки-й-відкат)
|
||||
12. [Тестування](#12-тестування)
|
||||
13. [CI і релізи](#13-ci-і-релізи)
|
||||
14. [Безпека](#14-безпека)
|
||||
15. [Ліцензії й торгові марки](#15-ліцензії-й-торгові-марки)
|
||||
16. [Локалізація й доступність](#16-локалізація-й-доступність)
|
||||
17. [Документація та спільнота](#17-документація-та-спільнота)
|
||||
18. [Типові пастки](#18-типові-пастки)
|
||||
19. [Покроковий план з нуля](#19-покроковий-план-з-нуля)
|
||||
20. [Чекліст перед релізом](#20-чекліст-перед-релізом)
|
||||
21. [Корисні посилання](#21-корисні-посилання)
|
||||
|
||||
---
|
||||
|
||||
## 1. Що таке «власний дистрибутив»
|
||||
|
||||
Дистрибутив — це не ядро і не набір програм, а **рішення**: які пакети встановлені, як вони налаштовані, як система завантажується, встановлюється й оновлюється. Залежно від того, скільки рішень ви берете на себе, розрізняють кілька рівнів:
|
||||
|
||||
| Рівень | Що ви робите | Приклади | Складність |
|
||||
|---|---|---|---|
|
||||
| **Ремікс (респін)** | Готовий дистрибутив + свої пакети й налаштування, той самий репозиторій | Ремікси Ubuntu через Cubic, Fedora Remix | Низька |
|
||||
| **Похідний дистрибутив** | Основа upstream + власний репозиторій пакетів, оформлення, інсталятор, утиліти | Linux Mint, Pop!_OS, EndeavourOS, **Das OS** | Середня |
|
||||
| **Форк** | Власна копія всього дерева пакетів, власний цикл випусків | Devuan, Rocky Linux | Висока |
|
||||
| **З нуля** | Збираєте кожен пакет самі, власний менеджер пакетів | Linux From Scratch, Void, Alpine | Дуже висока |
|
||||
|
||||
**Рекомендація:** починайте з похідного дистрибутива. Upstream дає безпекові оновлення тисяч пакетів, а ви зосереджуєтеся на тому, що відрізняє ваш дистрибутив. Кожен пакет, який ви «забираєте» собі, — це назавжди ваш обов'язок стежити за його CVE.
|
||||
|
||||
Головний принцип, який варто прийняти з першого дня: **усі зміни постачаються пакетами**. Не правте файли в образі руками й не «доналаштовуйте» систему скриптом після встановлення. Пакет можна оновити, відкотити, подивитися його вміст (`xbps-query -f das-settings`, `dpkg -L`, `pacman -Ql`), а скрипт — лише запустити ще раз і сподіватися.
|
||||
|
||||
---
|
||||
|
||||
## 2. Планування перед першим рядком коду
|
||||
|
||||
Запишіть відповіді на ці запитання в README або окремий документ. Без них проєкт розповзається.
|
||||
|
||||
### 2.1. Для кого і навіщо
|
||||
- Хто користувач: новачок, розробник, адміністратор серверів, старий ноутбук, вбудована система?
|
||||
- Яку конкретну проблему ви вирішуєте, якої не вирішує основа? «Гарніші шпалери» — не причина для дистрибутива, а «Void, готовий до роботи за 10 хвилин замість кількох годин налаштувань» — так.
|
||||
- Які варіанти образів: десктоп, сервер, мінімальний, ARM? Кожен варіант множить тестування.
|
||||
|
||||
### 2.2. Технічні рішення, які важко змінити потім
|
||||
| Рішення | Варіанти | Наслідки |
|
||||
|---|---|---|
|
||||
| Основа | Debian, Ubuntu, Fedora, Arch, Void, Alpine, NixOS… | Менеджер пакетів, цикл випусків, інструменти збирання |
|
||||
| Модель випусків | Rolling / фіксовані версії / LTS | Rolling ламається частіше; фіксовані вимагають гілок і бекпортів |
|
||||
| init | systemd / runit / OpenRC / s6 | Багато програм (GNOME, частина Flatpak-порталів, snap) очікують systemd |
|
||||
| libc | glibc / musl | musl менший, але ламає бінарні програми (Steam, драйвери NVIDIA, частину Python-коліс) |
|
||||
| Архітектури | x86_64, aarch64, i686… | Кожна — окреме збирання й тестування |
|
||||
| Файлова система | ext4 / Btrfs / XFS / ZFS | Btrfs дає знімки й відкат; ZFS — ліцензійно складний |
|
||||
| Середовище | Xfce, KDE, GNOME, без графіки | Розмір образу, вимоги до пам'яті, кількість налаштувань |
|
||||
| Завантаження | UEFI, BIOS, Secure Boot | Secure Boot потребує підписаного ланцюжка (див. [10.5](#105-secure-boot)) |
|
||||
|
||||
**Das OS:** Void (rolling, runit, glibc), x86_64, Btrfs зі знімками, Xfce; окремий консольний серверний образ на тих самих пакетах.
|
||||
|
||||
### 2.3. Назва й ідентифікатори
|
||||
- Перевірте назву: DistroWatch, GitHub, пошук торгових марок, домен. Назва не повинна містити чужих марок («Ubuntu», «Fedora», «Windows»).
|
||||
- Оберіть короткий машинний ідентифікатор (`dasos`): він піде в `ID=` у `/etc/os-release`, імена пакетів (`das-*`), мітку ISO, ім'я файлу образу, теку бренду Calamares.
|
||||
- Префікс для власних пакетів (`das-`) одразу показує, що пакет ваш, і не перетинається з upstream.
|
||||
|
||||
### 2.4. Версіонування
|
||||
- Одна версія дистрибутива в одному місці (`VERSION`), з якої беруться ім'я ISO, меню завантаження, інсталятор і тег релізу.
|
||||
- Версії власних пакетів незалежні від версії дистрибутива; підвищуйте їх при кожній зміні пакета, інакше менеджер пакетів не оновить установлені системи.
|
||||
- Узгодьте правило: SemVer (`0.9.1`), календарні версії (`24.04`) або кодові імена. Головне — не змінювати схему посеред шляху.
|
||||
|
||||
---
|
||||
|
||||
## 3. Вибір основи
|
||||
|
||||
| Основа | Інструмент live-образу | Збирання пакетів | Сильні сторони | На що зважати |
|
||||
|---|---|---|---|---|
|
||||
| **Debian** | `live-build` (`lb config`, `lb build`) | `dpkg-buildpackage`, `sbuild`, `reprepro`/`aptly` для репозиторію | Стабільність, величезний вибір пакетів, дозвільна політика назв | Пакети «старі»; оформлення deb-пакетів багатослівне |
|
||||
| **Ubuntu** | `livecd-rootfs`, для реміксів — Cubic | Launchpad PPA, `sbuild` | Драйвери, підтримка заліза, Secure Boot «з коробки» | Суворі правила торгових марок, snap |
|
||||
| **Fedora** | KIWI, `livemedia-creator` + kickstart, `lorax` | `rpmbuild`, `mock`, COPR | Свіжий стек, SELinux, Secure Boot | Короткий цикл (≈13 міс. підтримки), треба замінити `fedora-logos` на `generic-logos` |
|
||||
| **openSUSE** | KIWI | Open Build Service (OBS) | OBS збирає пакети й образи для багатьох дистрибутивів | Більше інфраструктури |
|
||||
| **Arch** | `archiso` (`mkarchiso`) | `makepkg`, `repo-add` | Простий PKGBUILD, rolling, AUR | Часті ламкі оновлення; Secure Boot вручну |
|
||||
| **Void** | `void-mklive` (`mklive.sh`) | `xbps-src`, `xbps-rindex` | Швидкий, простий, runit, чисті шаблони | Менша спільнота, немає Secure Boot з коробки |
|
||||
| **Alpine** | `aports/scripts/mkimage.sh` | `abuild` | Дуже маленький, musl | musl: не всі бінарні програми працюють |
|
||||
| **NixOS** | `nixos-generators`, `isoImage` у flake | Nix-вирази | Повністю декларативна й відтворювана система | Інша модель мислення, свій шлях до всього |
|
||||
| **Gentoo** | `catalyst` | ebuild | Повний контроль над прапорцями збирання | Збирання з вихідних кодів займає години |
|
||||
|
||||
Критерії вибору:
|
||||
1. **Ви вже знаєте цю систему?** Найважливіший критерій: налагоджувати доведеться саме менеджер пакетів і процес завантаження.
|
||||
2. **Чи є інструмент для live-образу,** який підтримує upstream? Писати свій з нуля — тижні роботи.
|
||||
3. **Чи легко зібрати власний пакет** і підключити свій репозиторій поруч з основним?
|
||||
4. **Політика назв і логотипів** основи (див. [15](#15-ліцензії-й-торгові-марки)).
|
||||
5. **Життєвий цикл:** скільки часу основу підтримують і як часто доведеться переходити на нову версію.
|
||||
|
||||
---
|
||||
|
||||
## 4. Середовище збирання
|
||||
|
||||
### 4.1. Збирайте в контейнері
|
||||
Збирання на робочій машині з «чимось, що колись встановили» не відтворюється на іншому комп'ютері. Контейнер з основою дистрибутива вирішує це:
|
||||
- інструменти (`xbps-src`, `mklive`, `live-build`, `archiso`) працюють у «рідній» системі, хост може бути будь-яким Linux із Docker;
|
||||
- пакети збираються проти тієї самої glibc і бібліотек, з якими поставляються.
|
||||
|
||||
**Das OS:** `docker/builder/Dockerfile` — образ на основі `void-glibc-full`, `scripts/builder.sh` запускає в ньому будь-яку команду, проєкт змонтовано в `/work`.
|
||||
|
||||
> Вимоги до образу збирача беруться з основи дистрибутива, а не з загальних звичок. Наприклад, для Void це образ Void, а не Alpine: `xbps-src` і `mklive` потребують `xbps`.
|
||||
|
||||
### 4.2. Привілеї
|
||||
Збирання live-образу монтує `/proc`, `/sys`, `/dev`, loop-пристрої та chroot, тому контейнер запускається з `--privileged` (або з `CAP_SYS_ADMIN` і доступом до `/dev/loop*`). Наслідки:
|
||||
- не запускайте збирання на спільному сервері з недовіреним кодом;
|
||||
- перерване збирання може лишити змонтовані файлові системи й файли, що належать root (`make clean` у Das OS прибирає їх зсередини контейнера);
|
||||
- інструменти пакетування часто **відмовляються працювати від root** (`xbps-src`, `makepkg`): створіть у контейнері непривілейованого користувача з UID хоста, щоб файли в `out/` належали вам (`scripts/container/build-packages.sh`).
|
||||
|
||||
### 4.3. Закріплюйте версії інструментів
|
||||
- Інструмент live-образу — на конкретному коміті (`ARG MKLIVE_COMMIT=8ca31d1`), оновлюйте свідомо й перевіряйте.
|
||||
- Базовий образ контейнера бажано закріпити за дайджестом (`image@sha256:…`), а не `:latest`, якщо потрібна точна відтворюваність.
|
||||
- Якщо ви **латаєте** upstream-інструмент (`sed` по скрипту mklive), спершу перевіряйте, що шуканий рядок існує, і падайте з помилкою, якщо його немає. Інакше після оновлення інструмента латка тихо перестане застосовуватися (так зроблено в `build-iso.sh`).
|
||||
|
||||
### 4.4. Кеші й місце на диску
|
||||
- Кеш завантажених пакетів (`/var/cache/xbps`, `/var/cache/apt/archives`, `/var/cache/pacman/pkg`) монтуйте з хоста: повторне збирання не качатиме гігабайти.
|
||||
- Дерево збирання пакетів (`void-packages`, `masterdir`/chroot) теж зберігайте між запусками.
|
||||
- Закладіть **30–50 ГБ** вільного місця: chroot збирача, кеш, робоча коренева система образу, squashfs і сам ISO.
|
||||
- SSD суттєво пришвидшує збирання (Das OS: перехід CI на SSD скоротив збирання ISO з ~45 до ~22 хв).
|
||||
|
||||
### 4.5. Відтворюваність
|
||||
- `SOURCE_DATE_EPOCH` фіксує дати у файлах, які це підтримують.
|
||||
- Не використовуйте в збиранні мережеві ресурси без версії («завантаж останній реліз з GitHub»).
|
||||
- Збирайте з чистого дерева git; версію образу беріть із файлу в репозиторії, а не з дати.
|
||||
|
||||
---
|
||||
|
||||
## 5. Власні пакети й репозиторій
|
||||
|
||||
### 5.1. Що оформлювати пакетами
|
||||
| Пакет | Вміст | Приклад у Das OS |
|
||||
|---|---|---|
|
||||
| Налаштування | Конфіги за замовчуванням, служби, drop-in файли | `das-settings` |
|
||||
| Оформлення | Тема GTK/Qt, іконки, шпалери, логотипи, шрифти | `das-theme`, `font-jetbrains-mono`, `adw-gtk3` |
|
||||
| Утиліти | Ваші програми | `das-tools`, `das-helpers`, `das-shell` |
|
||||
| Інсталятор | Конфіги Calamares, скрипти | `das-installer`, `das-server-installer`, `das-install-common` |
|
||||
| Метапакет | Лише залежності: «що входить у варіант» | `das-server` |
|
||||
| Пакети з upstream | Новіша або перезібрана версія чужого пакета | `calamares` |
|
||||
|
||||
### 5.2. Правила гарного пакета
|
||||
1. **Не перезаписуйте файли інших пакетів.** Менеджер пакетів або відмовиться, або наступне оновлення upstream поверне все назад. Замість цього:
|
||||
- використовуйте каталоги drop-in: `/etc/sudoers.d/`, `/etc/sysctl.d/`, `/etc/xbps.d/`, `/etc/ssh/sshd_config.d/`, `/usr/share/lightdm/lightdm.conf.d/`, `/etc/profile.d/`, `/etc/bash/bashrc.d/`;
|
||||
- для XDG-налаштувань — окремий каталог, доданий **на початок** `XDG_CONFIG_DIRS` (Das OS: `/etc/xdg/das` через `/etc/profile.d/das-xdg.sh`): стандартні файли лишаються недоторканими, а налаштування користувача в `~/.config` все одно мають пріоритет;
|
||||
- для GNOME — dconf-профілі й `*.gschema.override`, для KDE — `/etc/xdg/kdeglobals`.
|
||||
2. **Позначайте конфігураційні файли** (`conf_files` у xbps, `backup=()` у pacman, conffiles у deb), щоб оновлення не стерло зміни адміністратора.
|
||||
3. **Не чіпайте `/home` і `/etc/skel` без потреби.** `/etc/skel` копіюється лише при створенні користувача, тож оновлення до вже створених користувачів не дійдуть. Краще системні значення за замовчуванням, які користувач може перевизначити.
|
||||
4. **Залежності — явно.** Метапакет перелічує все, що потрібно варіанту; пакет налаштувань залежить від програм, які він налаштовує.
|
||||
5. **Підвищуйте версію або ревізію** при кожній зміні. Інакше `xbps-install -Su`/`apt upgrade` нічого не побачить.
|
||||
6. **Тести під час збирання** (`go test`, `python -m unittest`) ловлять помилки до того, як вони потраплять в образ.
|
||||
7. **Ліцензія** в кожному пакеті (`vlicense`), навіть у пакеті з конфігами.
|
||||
|
||||
### 5.3. Збирання поверх дерева upstream
|
||||
Інструменти пакетування зазвичай очікують повне дерево пакетів (спільні build-style, хуки, залежності). Робочий підхід:
|
||||
- неглибокий клон дерева upstream (`git clone --depth 1 void-packages`);
|
||||
- ваші шаблони копіюються поверх (`srcpkgs/<назва>`);
|
||||
- збирання й публікація у власний локальний репозиторій (`out/hostdir/binpkgs`).
|
||||
|
||||
Нюанси, які ми врахували в `build-packages.sh`:
|
||||
- **Інкрементальне збирання.** Пакет перезбирається лише тоді, коли змінився його каталог (хеш файлів), зник бінарний пакет або upstream оновив бібліотеку, з якою пакет зв'язаний (зміна soname). Інакше кожен коміт збирав би Calamares 17 хвилин.
|
||||
- **Зміна soname в rolling-основі.** Якщо upstream оновив, скажімо, `libicu`, ваш старий бінарний пакет посилається на бібліотеку, якої вже немає. Перевірка «чи встановиться пакет у порожній корінь» (`xbps-install -n`) ловить це автоматично.
|
||||
- **Залишки перерваного збирання** (`destdir`) потрапляють у наступний пакет. Чистіть їх перед збиранням.
|
||||
- **Пакет з'явився в upstream.** Попереджайте, коли ваш шаблон має таку саму назву, як новий пакет upstream: можливо, свій уже можна прибрати.
|
||||
|
||||
### 5.4. Репозиторій
|
||||
- Індекс репозиторію: `xbps-rindex -a`, `repo-add`, `reprepro`/`aptly`, `createrepo_c`.
|
||||
- **Підпис:** `xbps-rindex --sign`, `gpg` для apt/pacman/rpm. Приватний ключ — поза репозиторієм і поза образом, у CI — лише як секрет. Публічний ключ — у пакеті, що підключає репозиторій.
|
||||
- **Підключення в установленій системі:** окремий пакет або файл (`/etc/xbps.d/das.conf`, `/etc/apt/sources.list.d/`, `/etc/pacman.conf`). Без цього система після встановлення бачить лише upstream, і ваші пакети ніколи не оновляться (саме ця задача відкрита в TODO Das OS).
|
||||
- **Пріоритет репозиторіїв:** якщо пакет є і у вас, і в upstream, визначте, чий важливіший (порядок `repository=` у xbps, pinning в apt). Інакше upstream може «оновити» ваш пакет своєю версією.
|
||||
- **Хостинг:** будь-який статичний HTTPS-сервер (nginx, Gitea/GitHub Releases, S3, Cloudflare R2). Плануйте дзеркала, якщо користувачів стане багато.
|
||||
- **Ігнорування пакетів:** якщо варіант свідомо не містить пакетів (Das OS Server — без `linux-firmware-nvidia`, `sof-firmware`), пропишіть це і для збирання образу (`mklive -g`), і для встановленої системи (`ignorepkg` в `/etc/xbps.d/`), інакше перше ж оновлення поверне їх.
|
||||
|
||||
---
|
||||
|
||||
## 6. Як влаштований live-образ
|
||||
|
||||
Щоб налагоджувати завантаження, треба розуміти ланцюжок:
|
||||
|
||||
```
|
||||
Прошивка (UEFI або BIOS)
|
||||
└─ завантажувач з ISO (GRUB для UEFI, isolinux/syslinux для BIOS)
|
||||
└─ ядро vmlinuz + initramfs, параметри з меню (root=live:CDLABEL=..., quiet, nomodeset...)
|
||||
└─ initramfs шукає носій за міткою тому → монтує ISO
|
||||
└─ монтує squashfs з кореневою системою (лише читання)
|
||||
└─ overlayfs: зверху tmpfs для запису (зміни зникають після вимкнення)
|
||||
└─ switch_root → init (runit/systemd) → служби → вхід/робочий стіл
|
||||
```
|
||||
|
||||
Що з цього випливає:
|
||||
- **Мітка тому ISO** (volume label) — ключ, за яким initramfs шукає носій. Змінили мітку, але не параметри ядра → «не знайдено кореневу систему». Мітка ISO9660 обмежена 32 символами; уникайте пробілів і нелатинських літер.
|
||||
- **Гібридний ISO** (UEFI + BIOS, CD + USB) складається з El Torito для BIOS, образу EFI-розділу (FAT) для UEFI та MBR/GPT для запису на флешку через `dd`. Інструменти (`xorriso`, mklive, live-build, archiso) роблять це самі — не збирайте ISO вручну без потреби.
|
||||
- **initramfs live-образу** має лише знайти й змонтувати носій. Драйвери GPU і їхні прошивки (≈150 МБ) можна прибрати з нього: вони завантажаться з кореневої системи після `switch_root` (Das OS: `--omit-drivers "amdgpu radeon nouveau i915 xe"`, initrd 245 → 89 МБ). Перевіряйте на реальному залізі з різними відеокартами.
|
||||
- **Режим копіювання в RAM** (`rd.live.ram`, `toram`) дозволяє вийняти флешку; потребує пам'яті більше за розмір squashfs.
|
||||
- **Пункт `nomodeset`** у меню рятує на машинах, де екран лишається чорним.
|
||||
- **Live-користувач** створюється під час завантаження (у Void це робить dracut-модуль mklive) і не повинен потрапити у встановлену систему.
|
||||
|
||||
### 6.1. Стиснення squashfs
|
||||
| Алгоритм | Розмір | Швидкість розпакування | Коли |
|
||||
|---|---|---|---|
|
||||
| xz (+ BCJ x86, блок 1 МБ) | Найменший | Повільніше | Образи для завантаження/розповсюдження |
|
||||
| zstd (рівень 15–19) | Трохи більший | Швидко | Коли важлива швидкість завантаження live |
|
||||
| lz4 | Великий | Дуже швидко | Налагодження, швидкі ітерації |
|
||||
|
||||
**Das OS:** `-b 1M -Xbcj x86` дав −5 % на десктопі.
|
||||
|
||||
### 6.2. Що займає місце (для оптимізації)
|
||||
- Прошивки (`linux-firmware*`) — сотні мегабайтів; мережеві не прибирайте, без них не буде Wi-Fi.
|
||||
- Переклади `/usr/share/locale` (у Das OS — 265 МБ). Можна лишити лише потрібні мови (`noextract` у xbps, `path-exclude` у dpkg), але тоді інші мови інтерфейсу доведеться доставляти окремо.
|
||||
- Дублікати тем іконок, документація (`/usr/share/doc`, `man`), заголовки.
|
||||
- Подивитися найбільші пакети: `xbps-query -m | ...`, `expac -H M '%m %n' | sort -h`, `dpkg-query -Wf '${Installed-Size} ${Package}\n' | sort -n`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Збирання ISO
|
||||
|
||||
### 7.1. Профіль образу
|
||||
Тримайте опис кожного варіанта образу окремим каталогом, а не в скрипті:
|
||||
|
||||
```
|
||||
live/ server/
|
||||
├── packages.x86_64 ├── packages.x86_64 # список пакетів, коментарі через '#'
|
||||
├── services ├── services # служби, увімкнені в live
|
||||
├── branding/ └── branding/
|
||||
│ ├── issue └── issue
|
||||
│ ├── splash.svg
|
||||
│ └── ventoy/dasos.png
|
||||
└── include/ # файли, що накладаються на корінь образу
|
||||
└── etc/...
|
||||
```
|
||||
|
||||
Аналоги в інших основах: `config/package-lists/*.list.chroot` та `config/includes.chroot/` у live-build; `packages.x86_64`, `airootfs/`, `profiledef.sh` в archiso; `%packages` у kickstart.
|
||||
|
||||
### 7.2. Що класти в `include/`, а що — у пакет
|
||||
- `include/` — лише те, що потрібне **тільки live-сесії** (автовхід, розкладка для live, заглушки гостьових інструментів). Пам'ятайте: інсталятор, який копіює образ на диск, перенесе і ці файли — їх доведеться прибирати (див. [9.4](#94-копіювання-образу-чи-встановлення-пакетів)).
|
||||
- Усе, що має бути у встановленій системі, — у пакети.
|
||||
|
||||
### 7.3. Основні параметри (на прикладі mklive)
|
||||
```sh
|
||||
./mklive.sh \
|
||||
-a x86_64 \ # архітектура
|
||||
-r /work/out/hostdir/binpkgs \ # ваш репозиторій (додається перед upstream)
|
||||
-c /var/cache/xbps \ # кеш пакетів
|
||||
-p "$(read_list live/packages.x86_64)" \
|
||||
-S "$(read_list live/services)" \ # служби runit
|
||||
-I "$include" \ # накладка на корінь
|
||||
-l uk_UA.UTF-8 -k us \ # локаль і розкладка live
|
||||
-T "Das OS 0.9.1" \ # заголовок меню завантаження
|
||||
-C "live.user=live quiet loglevel=3" \ # параметри ядра
|
||||
-o out/iso/dasos-0.9.1-x86_64.iso
|
||||
```
|
||||
|
||||
### 7.4. Нюанси
|
||||
- **Меню завантаження** за замовчуванням показує версію ядра; користувачу корисніша версія дистрибутива. Латайте шаблони меню GRUB і isolinux, а не результат.
|
||||
- **Серверний образ:** параметри `console=tty0 console=ttyS0,115200` дають вивід і на екран, і на послідовну консоль (IPMI, VPS, `qemu -nographic`). Останній `console=` стає основним для `/dev/console`.
|
||||
- **Часовий пояс і локаль live** задавайте явно, інакше буде UTC і `en_US`.
|
||||
- **Ventoy:** багато користувачів завантажують ISO через Ventoy. Перевірте, що образ працює і в ньому; Ventoy може показувати власний значок за назвою образу (`live/branding/ventoy/`).
|
||||
- **Розмір файлу:** образ понад 4 ГБ не скопіюється на флешку з FAT32 (Ventoy за замовчуванням використовує exFAT, це нормально, але «записати файлом» на FAT32 не вийде).
|
||||
- **Ім'я файлу ISO** — `назва-варіант-версія-архітектура.iso`: користувачі й скрипти покладаються на передбачуване ім'я.
|
||||
|
||||
---
|
||||
|
||||
## 8. Налаштування за замовчуванням і оформлення
|
||||
|
||||
### 8.1. Ідентичність системи
|
||||
- `/etc/os-release` (`NAME`, `PRETTY_NAME`, `ID`, `ID_LIKE`, `VERSION_ID`, `HOME_URL`, `BUG_REPORT_URL`, `LOGO`, `ANSI_COLOR`). **Обережно з `ID`:** скрипти й програми (інсталятори, `neofetch`, Steam, драйвери, Calamares) перевіряють `ID=void`/`ID=ubuntu`. Якщо змінюєте `ID`, обов'язково вкажіть `ID_LIKE=<основа>`, і перевірте програми, які на це покладаються. Файл зазвичай належить базовому пакету upstream — постачайте свій через механізм, що не конфліктує з ним (альтернативи, `/usr/lib/os-release` vs `/etc/os-release`, drop-in, або власний пакет base-files, якщо ви форк).
|
||||
- `/etc/issue` (текст перед входом у консоль) — у live він може підказувати логін/пароль, у встановленій системі — нейтральний.
|
||||
- Ім'я хоста live (`dasos`), а не upstream-овий (`void-live`).
|
||||
- Банер SSH, MOTD, запрошення shell (у Das OS — `das-shell`).
|
||||
|
||||
### 8.2. Візуальне оформлення
|
||||
| Що | Де | Нюанси |
|
||||
|---|---|---|
|
||||
| Заставка меню завантаження | GRUB (UEFI), isolinux (BIOS) | Різні формати й роздільність; isolinux — 640×480, обмежена палітра |
|
||||
| Тема GRUB у встановленій системі | `/boot/grub/themes/`, `GRUB_THEME` | Без неї — чорний екран зі стандартним текстом |
|
||||
| Plymouth (графічне завантаження) | Тема Plymouth | Потребує KMS; ховає повідомлення ядра, тож залиште спосіб їх побачити |
|
||||
| Екран входу | LightDM/SDDM/GDM конфіги | Через drop-in (`lightdm.conf.d`), не правкою основного файлу |
|
||||
| Тема GTK/Qt, іконки, курсор, шрифти | Пакет теми | Перевіряйте світлий і темний варіанти, GTK3 і GTK4/libadwaita |
|
||||
| Рамки вікон | Тема менеджера вікон | Das OS генерує рамки xfwm4 програмою (`xfwm4gen`), а не малює вручну |
|
||||
| Шпалери, логотип | SVG у пакеті теми | Векторні джерела в репозиторії, растр генеруйте під час збирання |
|
||||
| Слайди інсталятора | Бренд Calamares | Версію підставляйте під час збирання з `VERSION` |
|
||||
|
||||
- Зберігайте **джерела** зображень (SVG), а PNG генеруйте (`rsvg-convert`), щоб змінювати колір чи текст в одному місці.
|
||||
- Перевіряйте ліцензії шрифтів, іконок і шпалер, які берете з інтернету (OFL, CC BY-SA вимагає зазначити автора).
|
||||
|
||||
### 8.3. Налаштування робочого середовища
|
||||
- Готуйте налаштування в живій сесії, потім експортуйте у файли (`xfconf-query`, `dconf dump`, `kwriteconfig`), а не пишіть XML вручну.
|
||||
- Розкладка клавіатури та її перемикання — і в X11 (`/etc/X11/xorg.conf.d/00-keyboard.conf`), і в консолі (`KEYMAP`), і в екрані входу.
|
||||
- Мова інтерфейсу за замовчуванням, часовий пояс, формат дати.
|
||||
- Назви тек користувача (`xdg-user-dirs`): реальна помилка, з якою ми стикнулися, описана в [18](#18-типові-пастки).
|
||||
- Звук (PipeWire + WirePlumber), мережа (NetworkManager), Bluetooth, автомонтування (udisks2, gvfs), керування живленням.
|
||||
- Агент polkit і `pkexec` для графічних програм, що потребують прав root.
|
||||
|
||||
---
|
||||
|
||||
## 9. Інсталятор
|
||||
|
||||
### 9.1. Вибір
|
||||
| Інсталятор | Для чого | Плюси | Мінуси |
|
||||
|---|---|---|---|
|
||||
| **Calamares** | Графічний, незалежний від дистрибутива | Модульний, бренд, переклади, розмітка дисків, LUKS | Залежить від Qt/KDE Frameworks; під runit/не-systemd потребує налаштувань |
|
||||
| Anaconda | Fedora/RHEL | Kickstart для автоматизації | Важко відірвати від Fedora |
|
||||
| debian-installer, Subiquity | Debian, Ubuntu | Перевірені | Складно брендувати |
|
||||
| archinstall | Arch | Python, скриптується | Лише Arch |
|
||||
| **Власний TUI** | Сервери, мінімальні образи | Повний контроль, `dialog`/`whiptail`, режим без запитань | Розмітку, завантажувач і крайові випадки пишете самі |
|
||||
|
||||
**Das OS:** Calamares для десктопа, власний `das-server-install` на `dialog` для сервера, спільні кроки в `das-install-common` (`mount-source`, `postinstall`).
|
||||
|
||||
### 9.2. Що має зробити будь-який інсталятор
|
||||
1. Розмітити диск (автоматично і вручну), створити файлові системи (і підтоми Btrfs).
|
||||
2. Перенести систему на диск.
|
||||
3. Згенерувати `/etc/fstab` за **UUID**, не за `/dev/sdX` (імена дисків змінюються).
|
||||
4. Налаштувати локаль (і **зібрати** її: у Void — `/etc/default/libc-locales` + `xbps-reconfigure -f glibc-locales`, у Debian — `locale-gen`), часовий пояс, розкладку (X11 і консоль).
|
||||
5. Створити користувача, групи (`wheel`, `audio`, `video`, `network`, `docker`…), sudo.
|
||||
6. Задати ім'я хоста, мережу.
|
||||
7. **Згенерувати унікальні дані машини** (див. 9.3).
|
||||
8. Перезібрати initramfs під цю машину.
|
||||
9. Встановити завантажувач.
|
||||
10. Прибрати все, що потрібне лише live-сесії й самому інсталятору.
|
||||
|
||||
### 9.3. Унікальні дані, яких не повинно бути в образі
|
||||
Усе, що копіюється з образу, буде однаковим на кожному комп'ютері. Тому в образі не повинно бути:
|
||||
- `/etc/machine-id`, `/var/lib/dbus/machine-id` — генеруються під час встановлення (модуль `machineid` у Calamares) або першого запуску;
|
||||
- **ключів хоста SSH** (`/etc/ssh/ssh_host_*`) — інакше всі сервери матимуть однаковий ключ, і MITM стане тривіальним; вони генеруються під час першого запуску sshd;
|
||||
- зерна генератора випадкових чисел (`/var/lib/random-seed`);
|
||||
- ключів, токенів, паролів збирача, історії shell, журналів збирання.
|
||||
|
||||
### 9.4. Копіювання образу чи встановлення пакетів
|
||||
| Спосіб | Як | Плюси | Мінуси |
|
||||
|---|---|---|---|
|
||||
| Копіювання образу (`unpackfs`, rsync) | Розпакувати squashfs на диск | Швидко, без мережі, встановлюється те, що користувач бачив у live | Треба прибирати live-специфічне |
|
||||
| Встановлення пакетів (`pacstrap`, `debootstrap`, `xbps-install -r`) | Ставити пакети з репозиторію в цільовий корінь | Чиста система, завжди найсвіжіші пакети | Потребує мережі або повного локального репозиторію |
|
||||
|
||||
При копіюванні:
|
||||
- копіюйте **чистий** корінь (у Void — `/dev/mapper/live-base`, нижній шар без змін live-сесії), а не змонтований overlay з live-користувачем і його налаштуваннями;
|
||||
- після копіювання видаліть live-користувача, автовхід, службові файли (`mklive-ignore.conf`), сам інсталятор і пакети, потрібні лише йому;
|
||||
- гостьові інструменти віртуальних машин лишайте лише для того гіпервізора, у якому відбувається встановлення (`/sys/class/dmi/id/sys_vendor`, прапорець `hypervisor` у `/proc/cpuinfo`); на реальному залізі — жодних (у Das OS інструменти VirtualBox показували помилку при кожному вході в KVM);
|
||||
- перевірте, що копіювання **не робить файли розрідженими** там, де це шкодить (див. [18](#18-типові-пастки) про GRUB і Btrfs).
|
||||
|
||||
### 9.5. Calamares під не-systemd систему
|
||||
- `settings.conf` задає послідовність модулів. Свої кроки — через `shellprocess` з різними `id` (`shellprocess@postinstall`).
|
||||
- Модулі, що викликають `systemctl` (`services-systemd`), замініть на свої (для runit — посилання в `/etc/runit/runsvdir/default/`).
|
||||
- Модуль `displaymanager` налаштовує автовхід/екран входу; перевірте, що він знає ваш DM.
|
||||
- `unpackfs` — джерело й тип файлової системи; `mount` — опції монтування й підтоми Btrfs; `partition` — файлові системи за замовчуванням, розмір EFI-розділу, swap.
|
||||
- Бренд: `branding.desc`, логотипи, слайди, кольори; версію підставляйте під час збирання.
|
||||
- Збирайте Calamares тієї версії, яку перевірили: оновлення може змінити формат конфігів.
|
||||
- Запускайте з `-d` для налагодження; журнал — `~/.cache/calamares/session.log`, і його варто копіювати у встановлену систему для звітів про помилки.
|
||||
|
||||
### 9.6. Консольний інсталятор
|
||||
- Режим без запитань (`--config file`) для автоматизації й тестів у CI.
|
||||
- Перевіряйте введене (ім'я користувача, ім'я хоста, пароль, SSH-ключ) **до** того, як почати стирати диск.
|
||||
- Явне підтвердження з назвою диска й розміром перед стиранням.
|
||||
- Опція «вхід по SSH лише за ключем» і перевірка, що ключ справді додано, інакше сервер стане недоступним.
|
||||
- Повідомлення наприкінці: як увійти, які є команди, де документація.
|
||||
|
||||
---
|
||||
|
||||
## 10. Завантажувач, initramfs і розмітка диска
|
||||
|
||||
### 10.1. Розмітка
|
||||
| Режим | Таблиця | Обов'язкові розділи |
|
||||
|---|---|---|
|
||||
| UEFI | GPT | EFI System Partition (FAT32, 300–1024 МіБ; Das OS — 512 МіБ), корінь |
|
||||
| BIOS + MBR | MBR | Корінь (позначений активним для деяких BIOS) |
|
||||
| BIOS + GPT | GPT | **BIOS boot partition** 1 МіБ (тип `ef02`) для GRUB, корінь |
|
||||
|
||||
- Вирівнюйте розділи по 1 МіБ.
|
||||
- Swap: розділ, файл (на Btrfs — з `chattr +C` і без стиснення) або zram. Для сплячого режиму (hibernate) потрібен swap не менший за обсяг RAM і `resume=` у параметрах ядра.
|
||||
- `/tmp` на tmpfs.
|
||||
|
||||
### 10.2. Btrfs для знімків
|
||||
Типова схема підтомів (як у Das OS, openSUSE, Ubuntu з zsys):
|
||||
```
|
||||
@ → /
|
||||
@home → /home (особисті файли не відкочуються разом із системою)
|
||||
@snapshots → /.snapshots
|
||||
@var_log → /var/log (журнали лишаються після відкату — зручно розбиратися)
|
||||
@var_cache_xbps → /var/cache/xbps (кеш пакетів не роздуває знімки)
|
||||
```
|
||||
Опції монтування: `compress=zstd`, `noatime`. Для баз даних і образів VM — `chattr +C` на каталог.
|
||||
|
||||
### 10.3. GRUB
|
||||
- UEFI: `grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=DasOS`.
|
||||
- **Запасний шлях `\EFI\BOOT\BOOTX64.EFI`** (`--removable` або копія): деякі прошивки «забувають» записи NVRAM або взагалі їх не підтримують — без запасного файлу система не завантажиться.
|
||||
- BIOS: `grub-install --target=i386-pc /dev/sdX` (на диск, не на розділ).
|
||||
- **Подвійне завантаження:** з GRUB 2.06 `os-prober` вимкнено за замовчуванням; щоб бачити Windows, потрібен `GRUB_DISABLE_OS_PROBER=false` у `/etc/default/grub`.
|
||||
- Тема й назва пунктів меню (`GRUB_DISTRIBUTOR`).
|
||||
- Годинник: Windows тримає апаратний годинник у місцевому часі, Linux — в UTC; при подвійному завантаженні час «стрибає». Виберіть одне й задокументуйте.
|
||||
|
||||
### 10.4. initramfs
|
||||
- Live-образ має **загальний** initramfs з усіма драйверами; у встановленій системі збирайте **під машину** (host-only), як це робить основа при оновленні ядра (`dracut --force`, `mkinitcpio -P`, `update-initramfs -u`).
|
||||
- Власні модулі dracut/хуки (Das OS: `90das-snapshot-overlay` для завантаження у знімок лише для читання) постачайте пакетом і перевіряйте, що вони потрапляють у initramfs.
|
||||
- Шифрування (LUKS): `crypttab`, ключ-файл у initramfs, щоб не вводити пароль двічі (GRUB + initramfs), `GRUB_ENABLE_CRYPTODISK=y` для зашифрованого `/boot`. GRUB підтримує LUKS2 лише з PBKDF2, не з Argon2 — враховуйте при створенні тому.
|
||||
|
||||
### 10.5. Secure Boot
|
||||
- Прошивка з увімкненим Secure Boot завантажує лише підписане ключем Microsoft (або вашим, якщо користувач його додав).
|
||||
- Реалістичні варіанти для невеликого дистрибутива:
|
||||
1. Основа вже має підписаний ланцюжок `shim → GRUB → ядро` (Debian, Ubuntu, Fedora, openSUSE) і ви **не змінюєте ядро й GRUB** — використовуйте їхні підписані пакети.
|
||||
2. Власний ключ (`sbctl`, MOK через `mokutil`): користувач один раз реєструє ключ. Кожен модуль ядра (DKMS, NVIDIA) теж треба підписувати.
|
||||
3. Документувати, що Secure Boot треба вимкнути (так у Void, Arch за замовчуванням).
|
||||
- Власний shim, підписаний Microsoft, — тривалий процес перевірки, для малого проєкту зазвичай недосяжний.
|
||||
|
||||
---
|
||||
|
||||
## 11. Оновлення, знімки й відкат
|
||||
|
||||
- **Rolling-основа може зламатися будь-якого дня.** Захист: знімок перед кожним оновленням (Das OS: `das-update` робить пару snapper pre/post), меню GRUB зі знімками (`grub-btrfs`), команда відкату (`snapper-rollback`).
|
||||
- Знімки — **не резервна копія**: вони на тому самому диску.
|
||||
- Відкат має не чіпати `/home` (окремий підтом).
|
||||
- Регулярне очищення старих знімків (snapper timeline/number cleanup), інакше диск заповниться.
|
||||
- Після відкату лишаються старі корені — плануйте їх прибирання.
|
||||
- Для дистрибутивів з фіксованими версіями: канали `stable`/`testing`, тестування оновлень перед публікацією, бекпорти безпеки.
|
||||
- Оновлення ваших пакетів має дійти до встановлених систем — перевірте весь шлях: коміт → збирання → підписаний репозиторій → `xbps-install -Su` на чистій установці.
|
||||
- Тримайте окремий консольний режим оновлення (`das-update --cli`): на сервері немає графіки, а графічна програма може зламатися саме під час оновлення.
|
||||
- Розгляньте атомарні/незмінні системи (ostree, NixOS, A/B-розділи), якщо відкат — ключова цінність; це інший рівень складності.
|
||||
|
||||
---
|
||||
|
||||
## 12. Тестування
|
||||
|
||||
### 12.1. Віртуальні машини
|
||||
```sh
|
||||
# UEFI (OVMF), диск на 20 ГБ, KVM
|
||||
qemu-system-x86_64 -enable-kvm -m 4G -smp 2 \
|
||||
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.fd \
|
||||
-drive if=pflash,format=raw,file=ovmf_vars.fd \
|
||||
-cdrom out/iso/dasos-0.9.1-x86_64.iso \
|
||||
-drive file=disk.qcow2,if=virtio \
|
||||
-nic user,hostfwd=tcp::2222-:22
|
||||
# BIOS: те саме без pflash (SeaBIOS за замовчуванням)
|
||||
# Сервер: -nographic -serial mon:stdio
|
||||
```
|
||||
- Тестуйте **обидва** режими: UEFI і BIOS; і live, і встановлену систему.
|
||||
- Копія `OVMF_VARS` для кожної ВМ, інакше записи завантаження змішуються.
|
||||
- Перенаправлення порту SSH (`localhost:2222`) спрощує автоматичні перевірки.
|
||||
- Знімки ВМ (`qemu-img snapshot`) перед встановленням пришвидшують повторні тести.
|
||||
- Різні відеоадаптери: `virtio-vga`, `qxl`, `std` — і VirtualBox/VMware, бо там свої гостьові інструменти.
|
||||
|
||||
**Das OS:** `make qemu`, `make qemu-bios`, `make qemu-disk`, `make qemu-server` (`scripts/run-qemu.sh`).
|
||||
|
||||
### 12.2. Реальне залізо
|
||||
ВМ не замінюють залізо. Обов'язковий мінімум перед стабільним релізом:
|
||||
- Графіка Intel, AMD, NVIDIA (nouveau і пропрієтарний драйвер), гібридні ноутбуки.
|
||||
- Wi-Fi: Intel (iwlwifi), Realtek, Broadcom (часто потребує пропрієтарного `broadcom-wl`), Bluetooth.
|
||||
- NVMe, SATA, режим Intel RST/RAID (Linux не бачить диск, поки в BIOS не перемкнути на AHCI — задокументуйте).
|
||||
- Старий BIOS без UEFI, UEFI з увімкненим/вимкненим Secure Boot.
|
||||
- Подвійне завантаження з Windows (і BitLocker).
|
||||
- Сплячий режим, яскравість, тачпад, звук через HDMI, мікрофон, вебкамера.
|
||||
- Завантаження з флешки, записаної через `dd`, Ventoy, Rufus (режим DD).
|
||||
|
||||
### 12.3. Автоматизація
|
||||
- Модульні тести ваших утиліт під час збирання пакетів (`make test`).
|
||||
- `shellcheck` для shell-скриптів.
|
||||
- Інсталятор без запитань (`--config`) у ВМ у CI, після цього вхід по SSH і перевірки (`openQA` — повноцінний варіант, як у openSUSE і Fedora).
|
||||
- Чекліст ручних перевірок у TODO, з відмітками, на якій версії перевірено.
|
||||
|
||||
---
|
||||
|
||||
## 13. CI і релізи
|
||||
|
||||
### 13.1. Збирання в CI
|
||||
- Self-hosted runner: збирання ISO потребує привілейованого контейнера, гігабайтів кешу й десятків хвилин — хмарні безкоштовні runner-и часто не підходять за часом і місцем.
|
||||
- Кеш (`DASOS_OUT`) між запусками на хості runner-а, інакше кожне збирання з нуля.
|
||||
- `concurrency` з `cancel-in-progress`, щоб новий push скасовував старе збирання.
|
||||
- Збирайте **всі варіанти одним запуском**, щоб пакети збиралися один раз.
|
||||
- Для змін, які не впливають на образ (документація), пропускайте збирання: `[skip ci]` у повідомленні коміту або `paths-ignore` у workflow.
|
||||
- Runner має підніматися сам після перезавантаження хоста.
|
||||
|
||||
### 13.2. Публікація релізу
|
||||
- Реліз — за тегом (`v0.9.1`), версія тегу має збігатися з `VERSION` (перевіряйте в CI).
|
||||
- Публікуйте `SHA256SUMS` і **підпис** до нього (`gpg --detach-sign SHA256SUMS`), з публічним ключем на сайті.
|
||||
- Великі файли: ліміти розміру вкладень (Gitea — `[repository.release] FILE_MAX_SIZE`), таймаути зворотного проксі. **Досвід Das OS:** Traefik обривав завантаження ISO через 60 секунд (502), довелося підняти `readTimeout`.
|
||||
- Торенти й дзеркала для зменшення навантаження.
|
||||
- Нотатки до релізу: що нового, відомі проблеми, як оновитися з попередньої версії, мінімальні вимоги.
|
||||
- Не видаляйте старі релізи одразу: комусь потрібно відкотитися.
|
||||
|
||||
---
|
||||
|
||||
## 14. Безпека
|
||||
|
||||
- Підписані пакети й репозиторій, лише HTTPS.
|
||||
- Приватні ключі — поза git, поза образом, у CI — як секрети з мінімальним доступом.
|
||||
- Ніяких паролів за замовчуванням у встановленій системі. Пароль `live/live` допустимий лише в live-сесії.
|
||||
- SSH: `PermitRootLogin prohibit-password`, вхід за ключем, fail2ban або аналог, файрвол «закрито все, крім потрібного» (Das OS Server: nftables у власній таблиці `inet das`, щоб не ламати правила Docker і fail2ban).
|
||||
- Оновлення безпеки з upstream мають доходити швидко: rolling-основа дає їх сама; власні пакети — ваша відповідальність.
|
||||
- Стежте за CVE пакетів, які ви збираєте самі (Calamares, власні версії бібліотек).
|
||||
- `sudo` без пароля — лише в live.
|
||||
- Мінімум запущених служб за замовчуванням.
|
||||
- Телеметрія: краще без неї; якщо є — лише з явної згоди й з описом, що збирається.
|
||||
- Перевіряйте, що в образ не потрапили секрети збирача: `unsquashfs -l` та пошук по `/root`, `/home`, `/tmp`, історії shell.
|
||||
|
||||
---
|
||||
|
||||
## 15. Ліцензії й торгові марки
|
||||
|
||||
> Це не юридична консультація. Для комерційного проєкту зверніться до юриста.
|
||||
|
||||
- **GPL:** розповсюджуючи бінарні файли під GPL (ядро, GRUB, coreutils…), ви зобов'язані надати вихідні коди **точно тих версій**. Найпростіше — публікувати поруч з ISO архів вихідних кодів або дзеркало; посилання на upstream надійне лише доти, доки там лежить саме та версія. Ваші власні латки й шаблони — у публічному репозиторії.
|
||||
- **Ліцензія вашого коду:** оберіть явно (Das OS — MIT) і покладіть `LICENSE` у кожен пакет.
|
||||
- **Невільні компоненти:** прошивки, драйвери NVIDIA, кодеки (патентні питання в деяких країнах), шрифти Microsoft. Деякі з них не можна перерозповсюджувати в модифікованому вигляді або зовсім — перевірте ліцензію кожного; у Void вони в репозиторії `nonfree`.
|
||||
- **Торгові марки основи:**
|
||||
- Ubuntu (Canonical) вимагає дозволу на використання назви для похідних і модифікованих образів — прибирайте їхні логотипи й назву;
|
||||
- Fedora дозволяє «Remix», але логотипи Fedora треба замінити (`generic-logos`);
|
||||
- Debian, Arch і Void значно ліберальніші, але й там не видавайте свій дистрибутив за офіційний.
|
||||
- **Програми з торговими марками:** Firefox дозволяє розповсюджувати незмінені збірки під своєю назвою; якщо ви змінюєте код чи брендинг, перевірте правила Mozilla (історичний приклад — Iceweasel у Debian).
|
||||
- **Графіка й шрифти:** ліцензії CC, OFL, вимоги зазначати авторство. Державна символіка (наприклад, тризуб) має свої правила використання — перевірте законодавство своєї країни.
|
||||
|
||||
---
|
||||
|
||||
## 16. Локалізація й доступність
|
||||
|
||||
- Мова за замовчуванням і вибір мови в інсталяторі та вікні привітання; зміна мови має діяти після перезавантаження/повторного входу.
|
||||
- Зібрані локалі для вибраної мови і `en_US.UTF-8` як запасна.
|
||||
- Переклади ваших утиліт (gettext або власний модуль `i18n` з тестами, що всі рядки перекладено).
|
||||
- Переклади Calamares і бренду (слайди, назви).
|
||||
- Розкладки: основна + англійська, зручне перемикання, однаково в X11, консолі й на екрані входу.
|
||||
- Вікна, що лишаються англійською (у Das OS — агент polkit), — окремий пункт у TODO.
|
||||
- Доступність: зчитувач екрана (Orca), висока контрастність, масштабування; хоча б перевірте, що їх можна увімкнути.
|
||||
|
||||
---
|
||||
|
||||
## 17. Документація та спільнота
|
||||
|
||||
- README двома мовами: призначення, можливості, скріншоти, як завантажити й встановити, як зібрати.
|
||||
- Мінімальні вимоги до заліза.
|
||||
- Відомі проблеми й обхідні шляхи.
|
||||
- Як повідомити про помилку: які журнали додати (`~/.cache/calamares/session.log`, `dmesg`, `/var/log/socklog`, `xbps-query -l`).
|
||||
- Журнал змін (CHANGELOG або нотатки до релізів).
|
||||
- Канал зв'язку (форум, Telegram, Matrix, issues).
|
||||
- Вкажіть, на чому базується дистрибутив, і подякуйте upstream — частину проблем користувачі мають повідомляти саме туди.
|
||||
- Помилки, знайдені в upstream, повідомляйте авторам (як у TODO Das OS — про `xdg-user-dirs`).
|
||||
- Після стабільного релізу — заявка на DistroWatch.
|
||||
|
||||
---
|
||||
|
||||
## 18. Типові пастки
|
||||
|
||||
Реальні проблеми з історії Das OS і загальновідомі:
|
||||
|
||||
| Симптом | Причина | Рішення |
|
||||
|---|---|---|
|
||||
| GRUB: «premature end of file» при завантаженні ядра з Btrfs | Інсталятор копіював `rsync --sparse`, хвіст ядра з нулів став «діркою» Btrfs, яку GRUB 2.12 не читає | Переписати ядра без розрідженості (`cp --sparse=never`) після копіювання |
|
||||
| Теки користувача англійською попри українську локаль | `xdg-user-dirs` 0.20 шукає переклади лише в першій теці `XDG_DATA_DIRS` | Задати правильний порядок/обхід у сесії, перейменувати вже створені теки |
|
||||
| Після перезапуску файрволу зникли правила Docker і fail2ban | Скрипт `finish` служби nftables у Void робить `nft flush ruleset` | Окрема таблиця й команда `dfw reload`, що перезавантажує лише її |
|
||||
| snapper не працює у встановленій системі | Не запущено `dbus` | Інсталятор вмикає `dbus` разом зі snapper |
|
||||
| Podman попереджає про спільне монтування | runit лишає `/` приватним | Скрипт ранньої ініціалізації `mount --make-rshared /` |
|
||||
| Повідомлення VirtualBox при кожному вході в KVM/на залізі | Гостьові інструменти скопійовані з live | Видаляти інструменти не того гіпервізора під час встановлення |
|
||||
| Версія в інсталяторі не та | Версія жорстко прописана в пакеті | Підставляти з `VERSION` під час збирання образу |
|
||||
| Латка до upstream-інструмента «тихо» перестала діяти | Інструмент оновився, рядок змінився | Перевіряти наявність рядка перед `sed` і падати з помилкою |
|
||||
| Пакет зібрано з чужими файлами | Залишки `destdir` після перерваного збирання | Чистити `destdir` перед кожним збиранням |
|
||||
| Пакет не встановлюється після оновлення upstream | Upstream змінив soname бібліотеки | Автоматично перезбирати пакети з незадоволеними залежностями |
|
||||
| 502 під час завантаження ISO в реліз | Таймаут зворотного проксі 60 с | Підняти `readTimeout` (Traefik) / `proxy_read_timeout` (nginx) |
|
||||
| Live-сесія блокує екран під час довгого встановлення | Заставка/блокування за замовчуванням | Вимкнути блокування в live |
|
||||
| Чорний екран на деяких відеокартах | Проблеми KMS | Пункт меню з `nomodeset`, тестування на залізі |
|
||||
| Не видно Windows у меню GRUB | `os-prober` вимкнено з GRUB 2.06 | `GRUB_DISABLE_OS_PROBER=false` |
|
||||
| Система не завантажується після оновлення прошивки | Прошивка стерла запис NVRAM | Запасний `\EFI\BOOT\BOOTX64.EFI` |
|
||||
| Усі сервери мають однаковий ключ SSH | Ключі хоста потрапили в образ | Генерувати під час першого запуску |
|
||||
| Користувацькі налаштування скидаються після оновлення | Пакет перезаписує файли в `/home` або основні конфіги | Значення за замовчуванням у `/etc/xdg/<дистр>` і drop-in, `conf_files` |
|
||||
| Пакети, свідомо прибрані з образу, повертаються | Ігнор налаштовано лише для збирання образу | Той самий список `ignorepkg` у встановленій системі |
|
||||
| Встановлена система не оновлює ваші пакети | Репозиторій дистрибутива не підключено після встановлення | Пакет з налаштуванням репозиторію й ключем |
|
||||
|
||||
---
|
||||
|
||||
## 19. Покроковий план з нуля
|
||||
|
||||
Порядок, у якому розвивався Das OS; кожен етап завершується робочим результатом, який можна перевірити.
|
||||
|
||||
1. **Репозиторій і контейнер збирання.** `git init`, `.gitignore` (`/out/`, `.idea/`), `Dockerfile` з основою, скрипт запуску команд у контейнері, `Makefile` з цілями `packages`, `iso`, `qemu`, `clean`.
|
||||
2. **Чистий live-образ основи.** Зберіть ISO інструментом upstream без змін, завантажте в QEMU (UEFI і BIOS). Це база для порівняння: якщо щось зламається пізніше, ви знатимете, що це ваша зміна.
|
||||
3. **Профіль образу.** Список пакетів, служби, локаль, часовий пояс, розкладка, меню завантаження з вашою назвою й версією, ім'я хоста.
|
||||
4. **Перший власний пакет.** Шаблон з одним конфігом, збирання у власний репозиторій, підключення репозиторію при збиранні ISO. Переконайтеся, що пакет потрапив в образ.
|
||||
5. **Налаштування робочого середовища пакетом** (`das-settings`): панель, теми, екран входу — через drop-in і `/etc/xdg/<дистр>`.
|
||||
6. **Оформлення** (`das-theme`): шпалери, логотип, тема, заставки завантажувача, бренд інсталятора.
|
||||
7. **Інсталятор.** Спершу автоматична розмітка на чистий диск, UEFI, одна файлова система. Потім BIOS, ручна розмітка, інші файлові системи, прибирання live-специфічного.
|
||||
8. **Знімки й відкат** (якщо Btrfs): підтоми, snapper, меню GRUB, утиліта оновлення зі знімком.
|
||||
9. **Власні утиліти** (вікно привітання, оновлення, консольні помічники) з тестами.
|
||||
10. **Додаткові варіанти образу** (сервер) на тих самих пакетах.
|
||||
11. **CI**: збирання на кожен push, реліз на тег, контрольні суми.
|
||||
12. **Реальне залізо.** Виправлення окремими патч-версіями.
|
||||
13. **Підписаний репозиторій і оновлення встановлених систем.**
|
||||
14. **Перший стабільний реліз**: злиття в `main`, нотатки, документація, оголошення.
|
||||
|
||||
Мінімальна команда, з якої все починається (Void):
|
||||
```sh
|
||||
docker run --rm -it --privileged -v "$PWD:/work" ghcr.io/void-linux/void-glibc-full sh -c '
|
||||
xbps-install -Syu xbps && xbps-install -y git bash &&
|
||||
git clone https://github.com/void-linux/void-mklive /opt/mklive && cd /opt/mklive &&
|
||||
./mklive.sh -a x86_64 -p "xfce4 xorg-minimal lightdm dbus" -S "dbus lightdm" -o /work/test.iso'
|
||||
```
|
||||
Для Arch аналог — `mkarchiso -v -w /tmp/work -o out /usr/share/archiso/configs/releng`, для Debian — `lb config && sudo lb build`.
|
||||
|
||||
---
|
||||
|
||||
## 20. Чекліст перед релізом
|
||||
|
||||
**Збирання**
|
||||
- [ ] Версія підвищена в `VERSION` і в змінених пакетах; README оновлено.
|
||||
- [ ] Збирання з чистого дерева git у CI пройшло, тести зелені.
|
||||
- [ ] Розмір образу в межах очікуваного (порівняти з попереднім релізом).
|
||||
- [ ] В образі немає секретів, ключів SSH хоста, `machine-id`.
|
||||
|
||||
**Live**
|
||||
- [ ] Завантаження UEFI і BIOS, з `dd`-флешки і Ventoy.
|
||||
- [ ] Меню показує правильну назву й версію; пункти `nomodeset` і RAM працюють.
|
||||
- [ ] Мережа, звук, Bluetooth, розкладки, мова інтерфейсу.
|
||||
|
||||
**Встановлення**
|
||||
- [ ] Автоматична й ручна розмітка; UEFI і BIOS; Btrfs і ext4.
|
||||
- [ ] Перше завантаження встановленої системи, вхід, sudo.
|
||||
- [ ] Live-користувач, інсталятор і непотрібні гостьові інструменти прибрані.
|
||||
- [ ] Унікальні `machine-id` і ключі SSH.
|
||||
- [ ] Оновлення зі знімками, завантаження у знімок, відкат.
|
||||
- [ ] Подвійне завантаження з Windows (якщо підтримується).
|
||||
|
||||
**Сервер**
|
||||
- [ ] Вхід по SSH за ключем, заборона паролів, файрвол, fail2ban.
|
||||
- [ ] Послідовна консоль.
|
||||
- [ ] Встановлення без запитань (`--config`).
|
||||
|
||||
**Публікація**
|
||||
- [ ] Тег збігається з `VERSION`.
|
||||
- [ ] ISO, `SHA256SUMS`, підпис опубліковано й перевірено завантаженням.
|
||||
- [ ] Нотатки до релізу з відомими проблемами.
|
||||
- [ ] Вихідні коди доступні.
|
||||
|
||||
---
|
||||
|
||||
## 21. Корисні посилання
|
||||
|
||||
- Void: [void-mklive](https://github.com/void-linux/void-mklive), [void-packages / xbps-src](https://github.com/void-linux/void-packages), [Void Handbook](https://docs.voidlinux.org)
|
||||
- Arch: [archiso](https://wiki.archlinux.org/title/Archiso)
|
||||
- Debian: [Debian Live Manual](https://live-team.pages.debian.net/live-manual/)
|
||||
- Fedora: [Kiwi](https://osinside.github.io/kiwi/), [Fedora Remix](https://fedoraproject.org/wiki/Remix)
|
||||
- openSUSE: [Open Build Service](https://openbuildservice.org)
|
||||
- Ubuntu: [Cubic](https://github.com/PJ-Singh-001/Cubic), [Trademark Policy](https://ubuntu.com/legal/trademarks)
|
||||
- [Calamares](https://calamares.io) і його [вікі розробника](https://github.com/calamares/calamares/wiki)
|
||||
- [Linux From Scratch](https://www.linuxfromscratch.org)
|
||||
- [os-release(5)](https://www.freedesktop.org/software/systemd/man/latest/os-release.html), [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/latest/)
|
||||
- [Arch Wiki: Unified Extensible Firmware Interface/Secure Boot](https://wiki.archlinux.org/title/Unified_Extensible_Firmware_Interface/Secure_Boot), [sbctl](https://github.com/Foxboron/sbctl)
|
||||
- [Reproducible Builds](https://reproducible-builds.org), [SOURCE_DATE_EPOCH](https://reproducible-builds.org/docs/source-date-epoch/)
|
||||
- [openQA](https://open.qa)
|
||||
- [DistroWatch](https://distrowatch.com)
|
||||
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,2 @@
|
||||
Das OS @VERSION@ (\l)
|
||||
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
# Replaces the file from virtualbox-ose-guest: start the VirtualBox clients
|
||||
# only inside VirtualBox, other hypervisors get a "kernel service is not
|
||||
# running" notification at every login.
|
||||
Type=Application
|
||||
Name=VirtualBox User Session
|
||||
Exec=sh -c "grep -qs VirtualBox /sys/class/dmi/id/product_name && exec /usr/bin/VBoxClient-all"
|
||||
NoDisplay=true
|
||||
@@ -40,15 +40,21 @@ firefox-i18n-uk
|
||||
udisks2
|
||||
xdg-user-dirs
|
||||
xdg-utils
|
||||
ncdu
|
||||
|
||||
# System tools for the live medium
|
||||
# Boot loader and disk tools, also kept by the installed system
|
||||
btrfs-progs
|
||||
cryptsetup
|
||||
dosfstools
|
||||
efibootmgr
|
||||
gptfdisk
|
||||
grub
|
||||
grub-x86_64-efi
|
||||
ntfs-3g
|
||||
os-prober
|
||||
parted
|
||||
sudo
|
||||
xfsprogs
|
||||
|
||||
# Virtual machine guests
|
||||
qemu-ga
|
||||
@@ -56,5 +62,15 @@ spice-vdagent
|
||||
virtualbox-ose-guest
|
||||
|
||||
# Das OS (local repository)
|
||||
das-installer
|
||||
das-settings
|
||||
das-snapshots
|
||||
das-theme
|
||||
das-tools
|
||||
das-helpers
|
||||
das-shell
|
||||
nano
|
||||
curl
|
||||
publisher
|
||||
deployer
|
||||
xtools-minimal
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs a command inside the Void Linux builder container.
|
||||
# The project is mounted at /work; downloaded packages are cached in out/cache/xbps.
|
||||
# DASOS_OUT puts out/ elsewhere (CI keeps it between checkouts).
|
||||
set -euo pipefail
|
||||
|
||||
root=$(cd "$(dirname "$0")/.." && pwd)
|
||||
out=${DASOS_OUT:-$root/out}
|
||||
image=dasos-builder
|
||||
|
||||
docker build --quiet --tag "$image" "$root/docker/builder" >/dev/null
|
||||
mkdir -p "$root/out/cache/xbps"
|
||||
mkdir -p "$out/cache/xbps"
|
||||
|
||||
# --privileged: mklive mounts /proc, /sys, /dev and a loop device for the root image
|
||||
exec docker run --rm --privileged \
|
||||
--volume "$root:/work" \
|
||||
--volume "$root/out/cache/xbps:/var/cache/xbps" \
|
||||
${DASOS_OUT:+--volume "$out:/work/out"} \
|
||||
--volume "$out/cache/xbps:/var/cache/xbps" \
|
||||
--env HOST_UID="$(id -u)" \
|
||||
--env HOST_GID="$(id -g)" \
|
||||
--env DASOS_REBUILD \
|
||||
"$image" "$@"
|
||||
|
||||
@@ -1,11 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds the Das OS live ISO with void-mklive from the profile in live/.
|
||||
# Builds a Das OS live ISO with void-mklive from a profile directory:
|
||||
# build-iso.sh the desktop image from live/
|
||||
# build-iso.sh server the console server image from server/
|
||||
# Runs inside the builder container, see scripts/builder.sh.
|
||||
set -euo pipefail
|
||||
|
||||
profile=${1:-desktop}
|
||||
ignore=""
|
||||
repo=/work/out/hostdir/binpkgs
|
||||
version=$(< /work/VERSION)
|
||||
|
||||
case $profile in
|
||||
desktop)
|
||||
dir=/work/live
|
||||
iso=/work/out/iso/dasos-${version}-x86_64.iso
|
||||
title="Das OS"
|
||||
locale=uk_UA.UTF-8
|
||||
cmdline="live.user=live quiet loglevel=3"
|
||||
;;
|
||||
server)
|
||||
dir=/work/server
|
||||
iso=/work/out/iso/dasos-server-${version}-x86_64.iso
|
||||
title="Das OS Server"
|
||||
locale=en_US.UTF-8
|
||||
# Both consoles: a screen and a serial line (IPMI, VPS consoles, QEMU -nographic).
|
||||
# mklive starts a login on ttyS0 when it sees console=ttyS0.
|
||||
cmdline="live.user=live loglevel=4 console=tty0 console=ttyS0,115200"
|
||||
# das-server keeps these packages out of the installed system; the image
|
||||
# is built before das-server is installed, so tell mklive as well
|
||||
ignore=$(sed -n 's/^ignorepkg=//p' /work/srcpkgs/das-server/files/xbps-ignore.conf | xargs)
|
||||
;;
|
||||
*)
|
||||
echo "build-iso: unknown profile '$profile' (desktop, server)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! compgen -G "$repo/*-repodata" >/dev/null; then
|
||||
echo "build-iso: the local package repository is missing, run 'make packages' first" >&2
|
||||
@@ -21,10 +50,12 @@ work=$(mktemp -d)
|
||||
mkdir -p /work/out/iso
|
||||
trap 'rm -rf "$work"; chown -R "$HOST_UID:$HOST_GID" /work/out/iso' EXIT
|
||||
|
||||
# Root file system overlay: static files from live/include plus generated links
|
||||
# Root file system overlay: static files from <profile>/include plus generated links
|
||||
include=$work/include
|
||||
cp -a /work/live/include "$include"
|
||||
mkdir -p "$include/etc"
|
||||
[[ -d $dir/include ]] && cp -a "$dir/include/." "$include"
|
||||
ln -sf /usr/share/zoneinfo/Europe/Kyiv "$include/etc/localtime"
|
||||
if [[ $profile == desktop ]]; then
|
||||
# PipeWire as the sound server, wired up the same way as Void's own live images
|
||||
mkdir -p "$include"/etc/xdg/autostart "$include"/etc/pipewire/pipewire.conf.d "$include"/etc/alsa/conf.d
|
||||
ln -sf /usr/share/applications/pipewire.desktop "$include"/etc/xdg/autostart/
|
||||
@@ -32,6 +63,12 @@ ln -sf /usr/share/examples/wireplumber/10-wireplumber.conf "$include"/etc/pipewi
|
||||
ln -sf /usr/share/examples/pipewire/20-pipewire-pulse.conf "$include"/etc/pipewire/pipewire.conf.d/
|
||||
ln -sf /usr/share/alsa/alsa.conf.d/50-pipewire.conf "$include"/etc/alsa/conf.d/
|
||||
ln -sf /usr/share/alsa/alsa.conf.d/99-pipewire-default.conf "$include"/etc/alsa/conf.d/
|
||||
# The installer shows the Das OS version, which lives in VERSION, not in the package
|
||||
branding=usr/share/calamares/branding/dasos
|
||||
mkdir -p "$include/$branding"
|
||||
sed -e "s/@VERSION@/$version/g" -e "s/@SHORT_VERSION@/${version%.*}/g" \
|
||||
"/work/srcpkgs/das-installer/files/calamares/branding/dasos/branding.desc" >"$include/$branding/branding.desc"
|
||||
fi
|
||||
|
||||
# mklive's dracut module creates the live user, hostname and passwords at boot.
|
||||
# Rebrand its defaults in this container's copy (the image itself stays untouched).
|
||||
@@ -43,6 +80,38 @@ for pattern in 'echo void-live >' 'voidlinux'; do
|
||||
fi
|
||||
done
|
||||
sed -i -e 's/echo void-live >/echo dasos >/' -e 's/voidlinux/live/g' "$adduser"
|
||||
# mklive copies its /etc/issue (Void live logins) into the image, and the
|
||||
# installers copy the image to disk, so use a Das OS one (the server's tells
|
||||
# how to log in to the live system; its installer keeps only the first line).
|
||||
# It names the Das OS version instead of agetty's \r (the kernel version).
|
||||
sed "s/@VERSION@/$version/g" "$dir/branding/issue" >/opt/void-mklive/data/issue
|
||||
|
||||
# Size, also patched into this container's mklive copy:
|
||||
# - the live initrd only has to find and mount the image, the GPU drivers and
|
||||
# their firmware (~150 MB) load from the root file system after switch_root;
|
||||
# - squashfs with 1 MB blocks and the x86 BCJ filter packs binaries tighter.
|
||||
mklive=/opt/void-mklive/mklive.sh
|
||||
for pattern in '--add-drivers "ahci"' '-comp "${SQUASHFS_COMPRESSION}"'; do
|
||||
if ! grep -qF -- "$pattern" "$mklive"; then
|
||||
echo "build-iso: '$pattern' not found in $mklive, mklive changed; update this script" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
sed -i \
|
||||
-e 's/--add-drivers "ahci"/& --omit-drivers "amdgpu radeon nouveau i915 xe"/' \
|
||||
-e 's/-comp "${SQUASHFS_COMPRESSION}"/& -b 1M -Xbcj x86/' \
|
||||
"$mklive"
|
||||
|
||||
# Boot menu entries name the Das OS version instead of mklive's
|
||||
# "<title> <kernel version> <arch>" (GRUB for UEFI, isolinux for BIOS).
|
||||
isolinux=/opt/void-mklive/isolinux/isolinux.cfg.in
|
||||
entry_title='ENTRY_TITLE="${BOOT_TITLE} ${KERNELVERSION} ${title_sfx}(${TARGET_ARCH})"'
|
||||
if ! grep -qF -- "$entry_title" "$mklive" || ! grep -qF '@@BOOT_TITLE@@ @@KERNVER@@ @@ARCH@@' "$isolinux"; then
|
||||
echo "build-iso: boot menu titles not found in mklive, mklive changed; update this script" >&2
|
||||
exit 1
|
||||
fi
|
||||
sed -i 's/ENTRY_TITLE="${BOOT_TITLE} ${KERNELVERSION} ${title_sfx}(${TARGET_ARCH})"/ENTRY_TITLE="${BOOT_TITLE}${title_sfx:+ ${title_sfx% }}"/' "$mklive"
|
||||
sed -i 's/@@BOOT_TITLE@@ @@KERNVER@@ @@ARCH@@/@@BOOT_TITLE@@/' "$isolinux"
|
||||
|
||||
rsvg-convert /work/live/branding/splash.svg --output "$work/splash.png"
|
||||
rm -f "$iso"
|
||||
@@ -52,11 +121,12 @@ SPLASH_IMAGE=$work/splash.png ./mklive.sh \
|
||||
-a x86_64 \
|
||||
-r "$repo" \
|
||||
-c /var/cache/xbps \
|
||||
-p "$(read_list /work/live/packages.x86_64)" \
|
||||
-S "$(read_list /work/live/services)" \
|
||||
-p "$(read_list "$dir/packages.x86_64")" \
|
||||
-S "$(read_list "$dir/services")" \
|
||||
${ignore:+-g "$ignore"} \
|
||||
-I "$include" \
|
||||
-l uk_UA.UTF-8 \
|
||||
-l "$locale" \
|
||||
-k us \
|
||||
-T "Das OS" \
|
||||
-C "live.user=live quiet loglevel=3" \
|
||||
-T "$title $version" \
|
||||
-C "$cmdline" \
|
||||
-o "$iso"
|
||||
|
||||
@@ -34,6 +34,30 @@ XBPS_CHROOT_CMD=uchroot
|
||||
XBPS_MAKEJOBS=$(nproc)
|
||||
CONF
|
||||
|
||||
# A package is rebuilt only when its directory in srcpkgs/ changed since the last
|
||||
# build, its binary package is gone, or Void no longer provides a shared library
|
||||
# it links to (a soname bump there needs a rebuild here). DASOS_REBUILD=1 rebuilds all.
|
||||
stamps=$hostdir/stamps
|
||||
mkdir -p "$stamps"
|
||||
srchash() {
|
||||
(cd "$vp/srcpkgs/$1" && find . -type f -print0 | sort -z | xargs -0 sha256sum) | sha256sum | cut -d' ' -f1
|
||||
}
|
||||
# An empty root to dry-run installs into: xbps refuses unresolved shared libraries
|
||||
checkroot=$(mktemp -d)
|
||||
mkdir -p "$checkroot/var/db/xbps/keys"
|
||||
cp -a /var/db/xbps/keys/. "$checkroot/var/db/xbps/keys/"
|
||||
voidrepo=$(sed -n 's/^repository=//p' /usr/share/xbps.d/*-repository-main.conf | head -1)
|
||||
xbps-install -S -r "$checkroot" -R "$voidrepo" >/dev/null
|
||||
up_to_date() {
|
||||
local info version revision
|
||||
[[ ${DASOS_REBUILD:-} != 1 && -f $stamps/$1 && $(< "$stamps/$1") == "$(srchash "$1")" ]] || return 1
|
||||
info=$(as_builder ./xbps-src show "$1")
|
||||
version=$(sed -n 's/^version:[[:space:]]*//p' <<<"$info")
|
||||
revision=$(sed -n 's/^revision:[[:space:]]*//p' <<<"$info")
|
||||
compgen -G "$hostdir/binpkgs/$1-${version}_$revision.*.xbps" >/dev/null || return 1
|
||||
xbps-install -n -y -r "$checkroot" -R "$hostdir/binpkgs" -R "$voidrepo" "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
names=()
|
||||
for dir in /work/srcpkgs/*/; do
|
||||
name=$(basename "$dir")
|
||||
@@ -41,6 +65,11 @@ for dir in /work/srcpkgs/*/; do
|
||||
echo "warning: $name is now in void-packages, consider dropping our template" >&2
|
||||
fi
|
||||
rm -rf "$vp/srcpkgs/$name"
|
||||
# A subpackage is a link to its source package's directory and is built with it
|
||||
if [[ -L ${dir%/} ]]; then
|
||||
as_builder ln -s "$(readlink "${dir%/}")" "$vp/srcpkgs/$name"
|
||||
continue
|
||||
fi
|
||||
as_builder cp -rL "$dir" "$vp/srcpkgs/$name"
|
||||
names+=("$name")
|
||||
done
|
||||
@@ -50,10 +79,20 @@ cd "$vp"
|
||||
if [[ ! -f masterdir-x86_64/.xbps_chroot_init ]]; then
|
||||
as_builder ./xbps-src -H "$hostdir" binary-bootstrap
|
||||
fi
|
||||
# A failed build leaves its destdir behind, and the next build of that package
|
||||
# (also as a dependency of another one) would package the stale files
|
||||
rm -rf masterdir-x86_64/destdir/*
|
||||
for name in "${names[@]}"; do
|
||||
if up_to_date "$name"; then
|
||||
echo "==> $name is up to date"
|
||||
continue
|
||||
fi
|
||||
echo "==> Building $name"
|
||||
as_builder ./xbps-src -H "$hostdir" -f pkg "$name"
|
||||
srchash "$name" >"$stamps/$name"
|
||||
done
|
||||
rm -rf "$checkroot"
|
||||
chown -R "$HOST_UID:$HOST_GID" "$stamps"
|
||||
|
||||
echo "==> Repository: $hostdir/binpkgs"
|
||||
ls -1 "$hostdir"/binpkgs/*.xbps
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Boots a Das OS ISO in QEMU (KVM when available).
|
||||
#
|
||||
# Usage: scripts/run-qemu.sh [--bios] [--disk] [path/to/image.iso]
|
||||
# Usage: scripts/run-qemu.sh [--bios] [--disk] [--serial] [path/to/image.iso]
|
||||
# --bios boot with legacy BIOS instead of UEFI
|
||||
# --disk attach out/qemu/disk.qcow2 (20 GB, created on first use) to test installing
|
||||
# Without a path the newest ISO in out/iso is used.
|
||||
# --serial no window: the serial console in this terminal (server ISO), Ctrl-A X quits
|
||||
# Without a path the newest desktop ISO in out/iso is used (server ISO with --serial).
|
||||
# The guest's SSH is on localhost:2222 (DASOS_SSH_PORT changes it): ssh -p 2222 user@localhost
|
||||
set -euo pipefail
|
||||
|
||||
root=$(cd "$(dirname "$0")/.." && pwd)
|
||||
state=$root/out/qemu
|
||||
firmware=uefi
|
||||
disk=false
|
||||
serial=false
|
||||
ssh_port=${DASOS_SSH_PORT:-2222}
|
||||
iso=""
|
||||
|
||||
die() {
|
||||
@@ -22,8 +26,9 @@ for arg in "$@"; do
|
||||
case $arg in
|
||||
--bios) firmware=bios ;;
|
||||
--disk) disk=true ;;
|
||||
--serial) serial=true ;;
|
||||
-h | --help)
|
||||
sed -n '2,8s/^# \{0,1\}//p' "$0"
|
||||
sed -n '2,9s/^# \{0,1\}//p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
-*) die "unknown option: $arg" ;;
|
||||
@@ -32,17 +37,23 @@ for arg in "$@"; do
|
||||
done
|
||||
|
||||
if [[ -z $iso ]]; then
|
||||
iso=$(ls -t "$root"/out/iso/*.iso 2>/dev/null | head -n 1) || true
|
||||
pattern="dasos-[0-9]*.iso"
|
||||
[[ $serial == true ]] && pattern="dasos-server-*.iso"
|
||||
iso=$(ls -t "$root"/out/iso/$pattern 2>/dev/null | head -n 1) || true
|
||||
fi
|
||||
[[ -n $iso && -f $iso ]] || die "no ISO found, run 'make iso' first"
|
||||
|
||||
args=(
|
||||
-machine q35 -smp 4 -m 4096
|
||||
-device virtio-vga -display gtk,zoom-to-fit=on
|
||||
-device virtio-net-pci,netdev=net0 -netdev user,id=net0
|
||||
-device qemu-xhci -device usb-tablet
|
||||
-device virtio-net-pci,netdev=net0 -netdev "user,id=net0,hostfwd=tcp:127.0.0.1:${ssh_port}-:22"
|
||||
-cdrom "$iso" -boot order=d
|
||||
)
|
||||
if [[ $serial == true ]]; then
|
||||
# The guest's ttyS0 and the QEMU monitor share this terminal
|
||||
args+=(-nographic -serial mon:stdio)
|
||||
else
|
||||
args+=(-device virtio-vga -display gtk,zoom-to-fit=on -device qemu-xhci -device usb-tablet)
|
||||
fi
|
||||
|
||||
if [[ -w /dev/kvm ]]; then
|
||||
args+=(-accel kvm -cpu host)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Das OS Server @VERSION@ (\l)
|
||||
|
||||
Log in as "live" with the password "live", then run: sudo das-server-install
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Packages added to the Void base-system in the server live image.
|
||||
|
||||
# Das OS Server: SSH, firewall, Docker, tools (see srcpkgs/das-server)
|
||||
das-server
|
||||
|
||||
# Installer and the disk tools it uses, also kept by the installed system
|
||||
das-server-installer
|
||||
btrfs-progs
|
||||
cryptsetup
|
||||
dosfstools
|
||||
efibootmgr
|
||||
gptfdisk
|
||||
grub
|
||||
grub-x86_64-efi
|
||||
parted
|
||||
sudo
|
||||
xfsprogs
|
||||
|
||||
# Virtual machine guests
|
||||
qemu-ga
|
||||
@@ -0,0 +1,3 @@
|
||||
# runit services enabled in the live image (/etc/sv/<name>). The installer
|
||||
# enables the server's own services on the installed system.
|
||||
dhcpcd
|
||||
@@ -0,0 +1,22 @@
|
||||
# Template file for 'calamares'
|
||||
pkgname=calamares
|
||||
version=3.4.3
|
||||
revision=2
|
||||
build_style=cmake
|
||||
configure_args="-DWITH_QT6=ON -DWITH_QML=OFF -DWITH_PYTHON=ON -DWITH_PYBIND11=ON
|
||||
-DINSTALL_CONFIG=OFF -DBUILD_TESTING=OFF -DBUILD_SCHEMA_TESTING=OFF
|
||||
-DBUILD_CRASH_REPORTING=OFF -DCMAKE_INSTALL_LIBEXECDIR=libexec
|
||||
-DUSE_services=none
|
||||
-DSKIP_MODULES=dummycpp;dummyprocess;dummypython;initcpio;initcpiocfg;mkinitfs;openrcdmcryptcfg;plasmalnf;plymouthcfg;tracking;zfs;zfshostid;interactiveterminal"
|
||||
hostmakedepends="extra-cmake-modules gettext pkg-config python3 qt6-base qt6-tools"
|
||||
makedepends="icu-devel kf6-kcoreaddons-devel kpmcore-devel libparted-devel
|
||||
libpwquality-devel libxcrypt-devel polkit-qt6-devel python3-devel qt6-base-devel
|
||||
qt6-svg-devel qt6-tools-devel yaml-cpp-devel"
|
||||
depends="ckbcomp kpmcore polkit python3 qt6-plugin-tls-openssl qt6-svg rsync
|
||||
squashfs-tools xkeyboard-config"
|
||||
short_desc="Distribution-independent installer framework"
|
||||
maintainer="Das <anry.das@gmail.com>"
|
||||
license="GPL-3.0-or-later"
|
||||
homepage="https://calamares.codeberg.page"
|
||||
distfiles="https://codeberg.org/Calamares/calamares/releases/download/v${version}/calamares-${version}.tar.gz"
|
||||
checksum=144cbbf6bdcebfb21685950db4f0777218519df095f62f4aa392f28348110d18
|
||||
@@ -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.
|
||||
@@ -0,0 +1,240 @@
|
||||
#!/bin/bash
|
||||
# dfw - open and close ports in the Das OS server firewall (nftables).
|
||||
#
|
||||
# /etc/nftables.conf defines the "das" table with the sets tcp_ports and
|
||||
# udp_ports; dfw keeps their elements in /etc/nftables.d/dfw.nft, which that
|
||||
# file includes, and reloads only the "das" table. SSH is always open.
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=helpers.sh
|
||||
. "${DAS_HELPERS:-/usr/lib/das/helpers.sh}"
|
||||
|
||||
conf=/etc/nftables.conf
|
||||
ports_file=/etc/nftables.d/dfw.nft
|
||||
|
||||
usage() {
|
||||
if [[ -n $uk ]]; then
|
||||
cat <<'EOF'
|
||||
Використання: dfw КОМАНДА [ПОРТ...]
|
||||
|
||||
status стан брандмауера й відкриті порти (типова команда)
|
||||
allow ПОРТ... відкрити порти для вхідних з'єднань
|
||||
deny ПОРТ... закрити порти, відкриті через allow
|
||||
reload перечитати /etc/nftables.conf після редагування
|
||||
|
||||
ПОРТ: 80, 80/tcp, 53/udp, 8000-8010/tcp або назва служби (http, https).
|
||||
Без /udp мається на увазі tcp. SSH відкритий завжди.
|
||||
|
||||
Порти, які публікують контейнери Docker (-p 8080:80), відкриває сам Docker,
|
||||
dfw на них не впливає.
|
||||
EOF
|
||||
else
|
||||
cat <<'EOF'
|
||||
Usage: dfw COMMAND [PORT...]
|
||||
|
||||
status firewall state and open ports (the default)
|
||||
allow PORT... open ports for incoming connections
|
||||
deny PORT... close ports opened with allow
|
||||
reload load /etc/nftables.conf again after editing it
|
||||
|
||||
PORT: 80, 80/tcp, 53/udp, 8000-8010/tcp or a service name (http, https).
|
||||
Without /udp it means tcp. SSH is always open.
|
||||
|
||||
Ports that Docker containers publish (-p 8080:80) are opened by Docker
|
||||
itself, dfw does not affect them.
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
check_config() {
|
||||
grep -qs 'set tcp_ports' "$conf" ||
|
||||
die "$conf не з Das OS Server (немає набору tcp_ports); dfw працює лише з ним" \
|
||||
"$conf is not the Das OS Server one (no tcp_ports set); dfw only works with that"
|
||||
}
|
||||
|
||||
# Opened ports of one protocol from the ports file, one per line
|
||||
saved_ports() {
|
||||
[[ -r $ports_file ]] || return 0
|
||||
sed -n "s/^add element inet das $1_ports { \(.*\) }$/\1/p" "$ports_file" | tr -d ' ' | tr ',' '\n'
|
||||
}
|
||||
|
||||
# parse_port SPEC: prints "PORT PROTO" with PORT a number or a FIRST-LAST range
|
||||
parse_port() {
|
||||
local spec=$1 port proto=tcp first last
|
||||
if [[ $spec == */* ]]; then
|
||||
port=${spec%/*}
|
||||
proto=${spec##*/}
|
||||
else
|
||||
port=$spec
|
||||
fi
|
||||
[[ $proto == tcp || $proto == udp ]] || die "'$spec': протокол tcp або udp" "'$spec': the protocol is tcp or udp"
|
||||
if [[ $port =~ ^[a-z][a-z0-9-]*$ ]]; then
|
||||
# A service name from /etc/services
|
||||
first=$(getent services "$port/$proto" | awk '{ print $2 }')
|
||||
[[ -n $first ]] || die "'$spec': невідома служба $port/$proto" "'$spec': unknown service $port/$proto"
|
||||
port=${first%/*}
|
||||
fi
|
||||
if [[ $port =~ ^([0-9]+)-([0-9]+)$ ]]; then
|
||||
first=${BASH_REMATCH[1]}
|
||||
last=${BASH_REMATCH[2]}
|
||||
elif [[ $port =~ ^[0-9]+$ ]]; then
|
||||
first=$port
|
||||
last=$port
|
||||
else
|
||||
die "'$spec': порт — число, діапазон 8000-8010 або назва служби" \
|
||||
"'$spec': a port is a number, a range like 8000-8010 or a service name"
|
||||
fi
|
||||
((first >= 1 && last <= 65535 && first <= last)) ||
|
||||
die "'$spec': порти від 1 до 65535" "'$spec': ports are 1 to 65535"
|
||||
if ((first == last)); then
|
||||
echo "$first $proto"
|
||||
else
|
||||
echo "$first-$last $proto"
|
||||
fi
|
||||
}
|
||||
|
||||
firewall_on() {
|
||||
[[ -e /etc/runit/runsvdir/default/nftables ]]
|
||||
}
|
||||
|
||||
# save TCP_PORTS UDP_PORTS (newline-separated): writes the ports file, checks
|
||||
# the whole configuration and loads it, restoring the old file on an error
|
||||
save() {
|
||||
local tcp udp backup=""
|
||||
# sort -n -u would take 80 and 80-90 for duplicates
|
||||
tcp=$(sort -u <<<"$1" | sort -n | grep . | paste -sd, - | sed 's/,/, /g' || true)
|
||||
udp=$(sort -u <<<"$2" | sort -n | grep . | paste -sd, - | sed 's/,/, /g' || true)
|
||||
mkdir -p "${ports_file%/*}"
|
||||
if [[ -e $ports_file ]]; then
|
||||
backup=$(mktemp)
|
||||
cp "$ports_file" "$backup"
|
||||
fi
|
||||
{
|
||||
echo "# Ports opened with dfw; change them with dfw allow/deny"
|
||||
[[ -z $tcp ]] || echo "add element inet das tcp_ports { $tcp }"
|
||||
[[ -z $udp ]] || echo "add element inet das udp_ports { $udp }"
|
||||
} >"$ports_file"
|
||||
if ! nft -c -f "$conf"; then
|
||||
if [[ -n $backup ]]; then mv "$backup" "$ports_file"; else rm -f "$ports_file"; fi
|
||||
die "nftables не прийняв правила, нічого не змінено" "nftables rejected the rules, nothing changed"
|
||||
fi
|
||||
rm -f "$backup"
|
||||
if firewall_on; then
|
||||
nft -f "$conf"
|
||||
else
|
||||
warn "брандмауер вимкнений, зміни збережено; увімкнути: dsv enable nftables" \
|
||||
"the firewall is off, changes are saved; to turn it on: dsv enable nftables"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_allow() {
|
||||
local spec port proto tcp udp
|
||||
tcp=$(saved_ports tcp)
|
||||
udp=$(saved_ports udp)
|
||||
for spec in "$@"; do
|
||||
read -r port proto < <(parse_port "$spec")
|
||||
if [[ $port == 22 && $proto == tcp ]]; then
|
||||
say "22/tcp (SSH) відкритий завжди" "22/tcp (SSH) is always open"
|
||||
continue
|
||||
fi
|
||||
if grep -qx "$port" <<<"$(if [[ $proto == tcp ]]; then echo "$tcp"; else echo "$udp"; fi)"; then
|
||||
say "$port/$proto: уже відкритий" "$port/$proto: already open"
|
||||
continue
|
||||
fi
|
||||
if [[ $proto == tcp ]]; then tcp+=$'\n'$port; else udp+=$'\n'$port; fi
|
||||
say "$port/$proto: відкрито" "$port/$proto: open"
|
||||
done
|
||||
save "$tcp" "$udp"
|
||||
}
|
||||
|
||||
cmd_deny() {
|
||||
local spec port proto tcp udp list
|
||||
tcp=$(saved_ports tcp)
|
||||
udp=$(saved_ports udp)
|
||||
for spec in "$@"; do
|
||||
read -r port proto < <(parse_port "$spec")
|
||||
if [[ $port == 22 && $proto == tcp ]]; then
|
||||
die "SSH не закривається через dfw, щоб не втратити доступ до сервера; правило — у $conf" \
|
||||
"SSH is not closed with dfw, so as not to lose access to the server; the rule is in $conf"
|
||||
fi
|
||||
if [[ $proto == tcp ]]; then list=$tcp; else list=$udp; fi
|
||||
if ! grep -qx "$port" <<<"$list"; then
|
||||
say "$port/$proto: не був відкритий через dfw" "$port/$proto: was not opened with dfw"
|
||||
continue
|
||||
fi
|
||||
list=$(grep -vx "$port" <<<"$list" || true)
|
||||
if [[ $proto == tcp ]]; then tcp=$list; else udp=$list; fi
|
||||
say "$port/$proto: закрито" "$port/$proto: closed"
|
||||
done
|
||||
save "$tcp" "$udp"
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
local p state banned ports
|
||||
if firewall_on && nft list table inet das >/dev/null 2>&1; then
|
||||
state=$(say "увімкнений" "on")
|
||||
else
|
||||
state=$(say "ВИМКНЕНИЙ (dsv enable nftables)" "OFF (dsv enable nftables)")
|
||||
fi
|
||||
say "Брандмауер: $state" "Firewall: $state"
|
||||
say "Вхідні з'єднання закриті, крім:" "Incoming connections are closed except:"
|
||||
echo " 22/tcp (ssh)"
|
||||
for p in tcp udp; do
|
||||
while read -r ports; do
|
||||
[[ -n $ports ]] && echo " $ports/$p"
|
||||
done < <(saved_ports "$p")
|
||||
done
|
||||
|
||||
# Docker opens its published ports itself
|
||||
if command -v docker >/dev/null && ports=$(docker ps --format '{{.Names}}: {{.Ports}}' 2>/dev/null) &&
|
||||
grep -q -- '->' <<<"$ports"; then
|
||||
say "Опубліковані контейнерами Docker (відкриті незалежно від dfw):" \
|
||||
"Published by Docker containers (open regardless of dfw):"
|
||||
grep -- '->' <<<"$ports" | sed 's/^/ /'
|
||||
fi
|
||||
|
||||
if banned=$(fail2ban-client status sshd 2>/dev/null | sed -n 's/.*Currently banned:[[:space:]]*//p') &&
|
||||
[[ -n $banned && $banned != 0 ]]; then
|
||||
say "fail2ban: заблоковано адрес за невдалі входи SSH: $banned" \
|
||||
"fail2ban: addresses banned for failed SSH logins: $banned"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd=${1:-status}
|
||||
[[ $# -gt 0 ]] && shift
|
||||
|
||||
case $cmd in
|
||||
status | list | ls)
|
||||
need_root status
|
||||
check_config
|
||||
cmd_status
|
||||
;;
|
||||
allow | open)
|
||||
[[ $# -gt 0 ]] || die "вкажіть порт: dfw allow 80/tcp" "give a port: dfw allow 80/tcp"
|
||||
need_root allow "$@"
|
||||
check_config
|
||||
cmd_allow "$@"
|
||||
;;
|
||||
deny | close)
|
||||
[[ $# -gt 0 ]] || die "вкажіть порт: dfw deny 80/tcp" "give a port: dfw deny 80/tcp"
|
||||
need_root deny "$@"
|
||||
check_config
|
||||
cmd_deny "$@"
|
||||
;;
|
||||
reload)
|
||||
need_root reload
|
||||
nft -c -f "$conf"
|
||||
if firewall_on; then
|
||||
nft -f "$conf"
|
||||
say "правила завантажено" "rules loaded"
|
||||
else
|
||||
say "правила правильні, але брандмауер вимкнений: dsv enable nftables" \
|
||||
"the rules are valid, but the firewall is off: dsv enable nftables"
|
||||
fi
|
||||
;;
|
||||
-h | --help | help) usage ;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,22 @@
|
||||
# bash completion for dfw
|
||||
# shellcheck shell=bash
|
||||
_dfw() {
|
||||
local cur=${COMP_WORDS[COMP_CWORD]}
|
||||
if ((COMP_CWORD == 1)); then
|
||||
mapfile -t COMPREPLY < <(compgen -W "status allow deny reload help" -- "$cur")
|
||||
return
|
||||
fi
|
||||
case ${COMP_WORDS[1]} in
|
||||
deny)
|
||||
# The ports opened with dfw
|
||||
local ports
|
||||
ports=$(sed -n 's/^add element inet das \(tcp\|udp\)_ports { \(.*\) }$/\2 \1/p' /etc/nftables.d/dfw.nft 2>/dev/null |
|
||||
awk '{ for (i = 1; i < NF; i++) { p = $i; sub(/,$/, "", p); print p "/" $NF } }')
|
||||
mapfile -t COMPREPLY < <(compgen -W "$ports" -- "$cur")
|
||||
;;
|
||||
allow)
|
||||
mapfile -t COMPREPLY < <(compgen -W "http https 80/tcp 443/tcp 53/udp" -- "$cur")
|
||||
;;
|
||||
esac
|
||||
}
|
||||
complete -F _dfw dfw
|
||||
@@ -0,0 +1,416 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,32 @@
|
||||
# bash completion for dprofile
|
||||
# shellcheck shell=bash
|
||||
_dprofile() {
|
||||
local cur=${COMP_WORDS[COMP_CWORD]} cmd=${COMP_WORDS[1]:-} dir=/usr/share/das/profiles
|
||||
local profiles
|
||||
profiles=$(find "$dir" -maxdepth 1 -name '*.conf' -printf '%f\n' 2>/dev/null | sed 's/\.conf$//')
|
||||
if ((COMP_CWORD == 1)); then
|
||||
mapfile -t COMPREPLY < <(compgen -W "list show install apps app help" -- "$cur")
|
||||
return
|
||||
fi
|
||||
case $cmd in
|
||||
show | apps | check)
|
||||
((COMP_CWORD == 2)) && mapfile -t COMPREPLY < <(compgen -W "$profiles" -- "$cur")
|
||||
;;
|
||||
install)
|
||||
if [[ ${COMP_WORDS[COMP_CWORD - 1]} == --apps ]]; then
|
||||
mapfile -t COMPREPLY < <(compgen -W "all none" -- "$cur")
|
||||
else
|
||||
mapfile -t COMPREPLY < <(compgen -W "$profiles --yes --apps" -- "$cur")
|
||||
fi
|
||||
;;
|
||||
app)
|
||||
if ((COMP_CWORD == 2)); then
|
||||
mapfile -t COMPREPLY < <(compgen -W "add rm" -- "$cur")
|
||||
else
|
||||
# the last part of the IDs in the profiles: AndroidStudio, GoLand
|
||||
mapfile -t COMPREPLY < <(compgen -W "$(sed -n 's/^[a-z]*\.[A-Za-z0-9.-]*\.\([A-Za-z0-9-]*\) .*/\1/p' "$dir"/*.conf 2>/dev/null)" -- "$cur")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
complete -F _dprofile dprofile
|
||||
@@ -0,0 +1,429 @@
|
||||
#!/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
|
||||
|
||||
# shellcheck source=helpers.sh
|
||||
. "${DAS_HELPERS:-/usr/lib/das/helpers.sh}"
|
||||
|
||||
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
|
||||
|
||||
usage() {
|
||||
if [[ -n $uk ]]; then
|
||||
cat <<'EOF'
|
||||
Використання: dsv КОМАНДА [СЛУЖБА...]
|
||||
|
||||
list усі служби: чи ввімкнена, стан, PID, час роботи
|
||||
status СЛУЖБА... стан служби (sv status)
|
||||
enable СЛУЖБА... запускати під час завантаження й запустити зараз
|
||||
disable СЛУЖБА... не запускати під час завантаження й зупинити
|
||||
start|stop|restart СЛУЖБА...
|
||||
запустити, зупинити, перезапустити
|
||||
logs СЛУЖБА [-f] журнал служби (-f: стежити за новими рядками)
|
||||
new [СЛУЖБА] [ПАРАМЕТРИ]
|
||||
створити службу для своєї програми; чого не
|
||||
вказано, те запитає:
|
||||
--cmd КОМАНДА що запускати (програма не має йти у фон)
|
||||
--user КОРИСТУВАЧ від чийого імені
|
||||
--dir КАТАЛОГ робочий каталог
|
||||
--env ІМ'Я=ЗНАЧЕННЯ змінна середовища, можна кілька разів
|
||||
--no-log без власного журналу в /var/log/СЛУЖБА
|
||||
--enable одразу ввімкнути й запустити
|
||||
rm СЛУЖБА вимкнути й видалити службу, створену через new
|
||||
|
||||
Служби — каталоги в /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)
|
||||
new [SERVICE] [OPTIONS] create a service for your own program; asks for
|
||||
what is not given:
|
||||
--cmd COMMAND what to run (the program must stay in the foreground)
|
||||
--user USER who runs it
|
||||
--dir DIR working directory
|
||||
--env NAME=VALUE an environment variable, may be repeated
|
||||
--no-log no log of its own in /var/log/SERVICE
|
||||
--enable enable and start it right away
|
||||
rm SERVICE disable and delete a service made with new
|
||||
|
||||
Services are the directories in /etc/sv. Without a command, runs list.
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
check_service() {
|
||||
[[ -d $sv_dir/$1 ]] || die "немає служби '$1' (див. dsv list)" "no service '$1' (see dsv list)"
|
||||
}
|
||||
|
||||
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
|
||||
row() {
|
||||
pad "$1" 24
|
||||
pad "$2" 9
|
||||
pad "$3" 6
|
||||
pad "$4" 8
|
||||
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
|
||||
}
|
||||
|
||||
# check_running NAME: after a start, tells whether the service stays up or
|
||||
# keeps restarting (runsv restarts a program that exits, once a second)
|
||||
check_running() {
|
||||
local line up='^run:[^;]*\) ([0-9]+)s'
|
||||
sleep 3
|
||||
line=$(sv status "$service_dir/$1" 2>/dev/null || true)
|
||||
if [[ $line =~ $up ]] && ((BASH_REMATCH[1] >= 2)); then
|
||||
say "$1: працює" "$1: running"
|
||||
else
|
||||
warn "$1 не працює стабільно (${line%%;*}); див. dsv logs $1" \
|
||||
"$1 does not stay up (${line%%;*}); see dsv logs $1"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_new() {
|
||||
local name="" run_cmd="" user="" dir="" log=1 enable="" envs=() e home bin
|
||||
while (($#)); do
|
||||
case $1 in
|
||||
--cmd) run_cmd=${2:?}; shift ;;
|
||||
--user) user=${2:?}; shift ;;
|
||||
--dir) dir=${2:?}; shift ;;
|
||||
--env) envs+=("${2:?}"); shift ;;
|
||||
--no-log) log="" ;;
|
||||
--enable) enable=1 ;;
|
||||
-*) die "невідомий параметр $1 (dsv help)" "unknown option $1 (dsv help)" ;;
|
||||
*) [[ -z $name ]] || die "зайвий аргумент $1" "extra argument $1"; name=$1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ -z $name ]]; then
|
||||
interactive || die "вкажіть назву служби" "name the service"
|
||||
ask name "Назва служби" "Service name"
|
||||
fi
|
||||
[[ $name =~ ^[A-Za-z0-9][A-Za-z0-9_.@-]*$ ]] ||
|
||||
die "назва '$name': лише латинські літери, цифри, '.', '_', '-', '@'" \
|
||||
"name '$name': only Latin letters, digits, '.', '_', '-', '@'"
|
||||
[[ ! -e $sv_dir/$name ]] || die "служба $name уже є" "service $name already exists"
|
||||
|
||||
if [[ -z $run_cmd ]]; then
|
||||
interactive || die "вкажіть команду: --cmd" "give the command: --cmd"
|
||||
say "Команда запуску. Програма має працювати на передньому плані (без -d/--daemon)." \
|
||||
"The command to run. The program must stay in the foreground (no -d/--daemon)."
|
||||
ask run_cmd "Команда" "Command"
|
||||
[[ -n $run_cmd ]] || die "команду не вказано" "no command given"
|
||||
fi
|
||||
read -r bin _ <<<"$run_cmd"
|
||||
if [[ $bin == */* ]]; then
|
||||
[[ -x $bin ]] || die "$bin не існує або не виконуваний" "$bin does not exist or is not executable"
|
||||
elif ! command -v "$bin" >/dev/null; then
|
||||
die "команду $bin не знайдено; вкажіть повний шлях" "command $bin not found; give the full path"
|
||||
fi
|
||||
|
||||
if [[ -z $user ]] && interactive; then
|
||||
ask user "Від імені користувача" "Run as user" "${SUDO_USER:-root}"
|
||||
fi
|
||||
user=${user:-root}
|
||||
id -u "$user" >/dev/null 2>&1 || die "немає користувача $user" "no user $user"
|
||||
home=$(getent passwd "$user" | cut -d: -f6)
|
||||
|
||||
if [[ -z $dir ]] && interactive; then
|
||||
ask dir "Робочий каталог" "Working directory" "$home"
|
||||
fi
|
||||
dir=${dir:-$home}
|
||||
[[ -d $dir ]] || die "немає каталогу $dir" "no directory $dir"
|
||||
|
||||
if ((${#envs[@]} == 0)) && interactive; then
|
||||
ask e "Змінні середовища ІМ'Я=ЗНАЧЕННЯ через пробіл (Enter — без них)" \
|
||||
"Environment variables NAME=VALUE separated by spaces (Enter for none)"
|
||||
read -ra envs <<<"$e"
|
||||
fi
|
||||
for e in "${envs[@]}"; do
|
||||
[[ $e =~ ^[A-Za-z_][A-Za-z0-9_]*= ]] || die "'$e' не має вигляду ІМ'Я=ЗНАЧЕННЯ" "'$e' is not NAME=VALUE"
|
||||
done
|
||||
|
||||
# Build it aside, then move it in place in one step
|
||||
local tmp=$sv_dir/.$name.new
|
||||
rm -rf "$tmp"
|
||||
mkdir -p "$tmp/env"
|
||||
# chpst does not set these for the new user
|
||||
printf '%s\n' "$home" >"$tmp/env/HOME"
|
||||
printf '%s\n' "$user" >"$tmp/env/USER"
|
||||
for e in "${envs[@]}"; do
|
||||
printf '%s\n' "${e#*=}" >"$tmp/env/${e%%=*}"
|
||||
done
|
||||
cat >"$tmp/run" <<EOF
|
||||
#!/bin/sh
|
||||
# Created by dsv new. After editing: dsv restart $name
|
||||
# Environment variables: one file per variable in $sv_dir/$name/env
|
||||
exec 2>&1
|
||||
cd '$dir' || exit 1
|
||||
exec chpst -u $user -e $sv_dir/$name/env $run_cmd
|
||||
EOF
|
||||
chmod 755 "$tmp/run"
|
||||
if [[ -n $log ]]; then
|
||||
mkdir -p "$tmp/log" "/var/log/$name"
|
||||
cat >"$tmp/log/run" <<EOF
|
||||
#!/bin/sh
|
||||
# Log of $name, read with: dsv logs $name
|
||||
mkdir -p /var/log/$name
|
||||
exec svlogd -tt /var/log/$name
|
||||
EOF
|
||||
chmod 755 "$tmp/log/run"
|
||||
fi
|
||||
mv "$tmp" "$sv_dir/$name"
|
||||
say "$name: створена в $sv_dir/$name" "$name: created in $sv_dir/$name"
|
||||
|
||||
if [[ -z $enable ]] && interactive; then
|
||||
confirm "Увімкнути й запустити зараз?" "Enable and start it now?" && enable=1
|
||||
fi
|
||||
if [[ -n $enable ]]; then
|
||||
cmd_enable "$name"
|
||||
[[ $service_dir == /var/service ]] && check_running "$name"
|
||||
else
|
||||
say "Запустити: dsv enable $name" "To start it: dsv enable $name"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_rm() {
|
||||
local name=${1:-} i
|
||||
[[ -n $name ]] || die "вкажіть службу" "name a service"
|
||||
check_service "$name"
|
||||
# Services from packages come back with the next update; disable them instead
|
||||
# (xbps-query -o succeeds even when no package owns the file)
|
||||
if [[ -n $(xbps-query -o "$sv_dir/$name/run" 2>/dev/null) ]]; then
|
||||
die "$name встановлена пакетом, її можна лише вимкнути: dsv disable $name" \
|
||||
"$name comes from a package, it can only be disabled: dsv disable $name"
|
||||
fi
|
||||
if interactive && ! confirm "Видалити службу $name?" "Delete service $name?"; then
|
||||
return 1
|
||||
fi
|
||||
if is_enabled "$name"; then
|
||||
cmd_disable "$name"
|
||||
# runsvdir stops its runsv at the next scan; wait for it before deleting
|
||||
for ((i = 0; i < 20; i++)); do
|
||||
pgrep -x -f "runsv $name" >/dev/null || break
|
||||
sleep 0.5
|
||||
done
|
||||
fi
|
||||
rm -rf "${sv_dir:?}/$name"
|
||||
say "$name: видалена" "$name: deleted"
|
||||
[[ -d /var/log/$name ]] && say "Журнал залишився в /var/log/$name" "The log is kept in /var/log/$name"
|
||||
return 0
|
||||
}
|
||||
|
||||
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 "$@"
|
||||
;;
|
||||
new | create)
|
||||
need_root new "$@"
|
||||
cmd_new "$@"
|
||||
;;
|
||||
rm | remove | delete)
|
||||
need_root rm "$@"
|
||||
cmd_rm "$@"
|
||||
;;
|
||||
-h | --help | help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,41 @@
|
||||
# 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 new rm 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")
|
||||
;;
|
||||
new)
|
||||
case ${COMP_WORDS[COMP_CWORD - 1]} in
|
||||
--user) mapfile -t COMPREPLY < <(compgen -u -- "$cur") ;;
|
||||
--dir) mapfile -t COMPREPLY < <(compgen -d -- "$cur") ;;
|
||||
--cmd) mapfile -t COMPREPLY < <(compgen -c -- "$cur") ;;
|
||||
--env) ;;
|
||||
*) mapfile -t COMPREPLY < <(compgen -W "--cmd --user --dir --env --no-log --enable" -- "$cur") ;;
|
||||
esac
|
||||
;;
|
||||
status | logs | log | rm)
|
||||
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
|
||||
@@ -0,0 +1,247 @@
|
||||
#!/bin/bash
|
||||
# dsys - system settings on Das OS: host name, time zone, language and
|
||||
# console keyboard, in place of hostnamectl, timedatectl and localectl.
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=helpers.sh
|
||||
. "${DAS_HELPERS:-/usr/lib/das/helpers.sh}"
|
||||
|
||||
zoneinfo=/usr/share/zoneinfo
|
||||
libc_locales=/etc/default/libc-locales
|
||||
keymaps=/usr/share/kbd/keymaps
|
||||
|
||||
usage() {
|
||||
if [[ -n $uk ]]; then
|
||||
cat <<'EOF'
|
||||
Використання: dsys [КОМАНДА] [ЗНАЧЕННЯ]
|
||||
|
||||
info огляд системи (типова команда)
|
||||
hostname [ІМ'Я] показати або змінити ім'я хоста
|
||||
timezone [ЗОНА] показати або змінити часовий пояс (Europe/Kyiv,
|
||||
достатньо й частини назви: Kyiv)
|
||||
timezone list [ФІЛЬТР] усі часові пояси
|
||||
locale [МОВА] показати або змінити мову системи (uk_UA, en_US)
|
||||
locale list [ФІЛЬТР] доступні мови; * — уже зібрані
|
||||
keymap [РОЗКЛАДКА] показати або змінити розкладку консолі (us, ua-utf)
|
||||
keymap list [ФІЛЬТР] доступні розкладки консолі
|
||||
|
||||
Мову й розкладку графічного сеансу кожен користувач задає в налаштуваннях
|
||||
Xfce і в das-welcome; dsys змінює типові для системи й консолі.
|
||||
EOF
|
||||
else
|
||||
cat <<'EOF'
|
||||
Usage: dsys [COMMAND] [VALUE]
|
||||
|
||||
info system overview (the default)
|
||||
hostname [NAME] show or change the host name
|
||||
timezone [ZONE] show or change the time zone (Europe/Kyiv,
|
||||
part of the name will do: Kyiv)
|
||||
timezone list [FILTER] all time zones
|
||||
locale [LANGUAGE] show or change the system language (uk_UA, en_US)
|
||||
locale list [FILTER] available languages; * marks the built ones
|
||||
keymap [MAP] show or change the console keyboard map (us, ua-utf)
|
||||
keymap list [FILTER] available console keyboard maps
|
||||
|
||||
In the graphical session every user picks the language and keyboard in the
|
||||
Xfce settings and in das-welcome; dsys changes the system and console defaults.
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
current_hostname() {
|
||||
cat /etc/hostname 2>/dev/null || hostname
|
||||
}
|
||||
|
||||
current_timezone() {
|
||||
local tz
|
||||
tz=$(readlink /etc/localtime 2>/dev/null || true)
|
||||
echo "${tz#*zoneinfo/}"
|
||||
}
|
||||
|
||||
current_locale() {
|
||||
sed -n 's/^LANG=//p' /etc/locale.conf 2>/dev/null | tr -d '"'
|
||||
}
|
||||
|
||||
rc_value() {
|
||||
sed -n "s/^$1=//p" /etc/rc.conf 2>/dev/null | tr -d '"' | tail -n 1
|
||||
}
|
||||
|
||||
# A graphical session keeps its X authority under the old host name
|
||||
session_warning() {
|
||||
if pgrep -x lightdm >/dev/null || [[ -n ${DISPLAY:-} ]]; then
|
||||
say "Перезавантажте комп'ютер: графічний сеанс не відкриватиме нових вікон зі старим іменем." \
|
||||
"Reboot: the graphical session will not open new windows under the old name."
|
||||
fi
|
||||
}
|
||||
|
||||
# 3 д 4 год 5 хв / 3 d 4 h 5 min
|
||||
uptime_text() {
|
||||
local s d h m
|
||||
read -r s _ </proc/uptime
|
||||
s=${s%.*}
|
||||
d=$((s / 86400))
|
||||
h=$((s % 86400 / 3600))
|
||||
m=$((s % 3600 / 60))
|
||||
if ((d > 0)); then
|
||||
say "$d д $h год $m хв" "$d d $h h $m min"
|
||||
elif ((h > 0)); then
|
||||
say "$h год $m хв" "$h h $m min"
|
||||
else
|
||||
say "$m хв" "$m min"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_info() {
|
||||
local ntp line
|
||||
pad "$(say "Ім'я хоста:" "Host name:")" 16; current_hostname
|
||||
pad "$(say "Система:" "System:")" 16; echo "$(uname -sr), $(uname -m)"
|
||||
pad "$(say "Працює:" "Up:")" 16; uptime_text
|
||||
pad "$(say "Час:" "Time:")" 16; date '+%Y-%m-%d %H:%M:%S %Z'
|
||||
pad "$(say "Часовий пояс:" "Time zone:")" 16; current_timezone
|
||||
if ntp=$(chronyc -n tracking 2>/dev/null); then
|
||||
if grep -q '^Leap status *: Normal' <<<"$ntp"; then
|
||||
ntp=$(say "синхронізовано (chrony)" "synchronized (chrony)")
|
||||
else
|
||||
ntp=$(say "не синхронізовано (chrony)" "not synchronized (chrony)")
|
||||
fi
|
||||
else
|
||||
ntp=$(say "не працює (dsv enable chronyd)" "not running (dsv enable chronyd)")
|
||||
fi
|
||||
pad "$(say "Синхронізація:" "Time sync:")" 16; echo "$ntp"
|
||||
pad "$(say "Мова:" "Language:")" 16; current_locale
|
||||
pad "$(say "Консоль:" "Console:")" 16
|
||||
echo "$(say розкладка keymap) $(rc_value KEYMAP || true), $(say шрифт font) $(rc_value FONT || true)"
|
||||
say "Мережа:" "Network:"
|
||||
ip -brief address 2>/dev/null | while read -r line; do
|
||||
[[ $line == lo\ * ]] || echo " $line"
|
||||
done
|
||||
}
|
||||
|
||||
cmd_hostname() {
|
||||
local new=${1:-} old
|
||||
if [[ -z $new ]]; then
|
||||
current_hostname
|
||||
return
|
||||
fi
|
||||
[[ $new =~ ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$ ]] ||
|
||||
die "'$new': малі латинські літери, цифри й дефіс (не на початку й не в кінці)" \
|
||||
"'$new': lowercase Latin letters, digits and hyphens (not first or last)"
|
||||
need_root hostname "$new"
|
||||
old=$(current_hostname)
|
||||
echo "$new" >/etc/hostname
|
||||
# The installers add "127.0.1.1 <name>"; keep the line in step
|
||||
if grep -qw -- "$old" /etc/hosts; then
|
||||
sed -i "/^[^#]/s/\([[:space:]]\)${old//./\\.}\([[:space:]]\|$\)/\1$new\2/g" /etc/hosts
|
||||
fi
|
||||
grep -qw -- "$new" /etc/hosts || printf '127.0.1.1\t%s\n' "$new" >>/etc/hosts
|
||||
hostname "$new"
|
||||
say "Ім'я хоста: $new" "Host name: $new"
|
||||
session_warning
|
||||
}
|
||||
|
||||
# All time zones, from tzdata's table plus UTC
|
||||
timezones() {
|
||||
{ awk '!/^#/ { print $3 }' "$zoneinfo/zone1970.tab"; echo UTC; } | sort -u
|
||||
}
|
||||
|
||||
cmd_timezone() {
|
||||
local zone=${1:-} matches line
|
||||
if [[ -z $zone ]]; then
|
||||
current_timezone
|
||||
return
|
||||
fi
|
||||
if [[ $zone == list ]]; then
|
||||
timezones | grep -i -- "${2:-}" || true
|
||||
return
|
||||
fi
|
||||
if ! timezones | grep -qx -- "$zone"; then
|
||||
# Kyiv -> Europe/Kyiv, when only one zone matches
|
||||
matches=$(timezones | grep -i -- "$zone" || true)
|
||||
if [[ $(grep -c . <<<"$matches") -eq 1 ]]; then
|
||||
zone=$matches
|
||||
elif [[ -n $matches ]]; then
|
||||
say "Кілька часових поясів підходять, уточніть:" "Several time zones match, be more specific:" >&2
|
||||
head -n 20 <<<"$matches" | while read -r line; do echo " $line"; done >&2
|
||||
(($(grep -c . <<<"$matches") <= 20)) || echo " ..." >&2
|
||||
exit 1
|
||||
else
|
||||
die "невідомий часовий пояс '$zone' (dsys timezone list)" "unknown time zone '$zone' (dsys timezone list)"
|
||||
fi
|
||||
fi
|
||||
need_root timezone "$zone"
|
||||
ln -sfn "$zoneinfo/$zone" /etc/localtime
|
||||
say "Часовий пояс: $zone, зараз $(date '+%H:%M %Z')" "Time zone: $zone, now $(date '+%H:%M %Z')"
|
||||
}
|
||||
|
||||
# Every UTF-8 locale glibc can build, with * for the enabled ones
|
||||
locales() {
|
||||
sed -n 's/^\(#\?\)\([^ ]*\.UTF-8\) UTF-8[[:space:]]*$/\2 \1/p' "$libc_locales" |
|
||||
awk '{ print ($2 == "#" ? " " : "* ") $1 }'
|
||||
}
|
||||
|
||||
cmd_locale() {
|
||||
local lang=${1:-} re
|
||||
if [[ -z $lang ]]; then
|
||||
current_locale
|
||||
return
|
||||
fi
|
||||
if [[ $lang == list ]]; then
|
||||
locales | grep -i -- "${2:-}" || true
|
||||
return
|
||||
fi
|
||||
[[ $lang == *.* ]] || lang+=.UTF-8
|
||||
re=${lang//./\\.}
|
||||
locales | grep -q "^. $re\$" ||
|
||||
die "невідома мова '$lang' (dsys locale list)" "unknown language '$lang' (dsys locale list)"
|
||||
need_root locale "$lang"
|
||||
if grep -q "^#$re " "$libc_locales"; then
|
||||
sed -i "s/^#\($re \)/\1/" "$libc_locales"
|
||||
say "Збираю $lang..." "Building $lang..."
|
||||
xbps-reconfigure -f glibc-locales >/dev/null
|
||||
fi
|
||||
# Keep the other settings, Void's default file also has LC_COLLATE=C
|
||||
if grep -q '^LANG=' /etc/locale.conf 2>/dev/null; then
|
||||
sed -i "s/^LANG=.*/LANG=$lang/" /etc/locale.conf
|
||||
else
|
||||
echo "LANG=$lang" >>/etc/locale.conf
|
||||
fi
|
||||
say "Мова системи: $lang (діє з наступного входу)" "System language: $lang (from the next login)"
|
||||
}
|
||||
|
||||
cmd_keymap() {
|
||||
local map=${1:-}
|
||||
if [[ -z $map ]]; then
|
||||
rc_value KEYMAP
|
||||
return
|
||||
fi
|
||||
if [[ $map == list ]]; then
|
||||
find "$keymaps" -name '*.map.gz' -printf '%f\n' | sed 's/\.map\.gz$//' | sort -u | grep -i -- "${2:-}" || true
|
||||
return
|
||||
fi
|
||||
[[ -n $(find "$keymaps" -name "$map.map.gz" -print -quit) ]] ||
|
||||
die "невідома розкладка '$map' (dsys keymap list)" "unknown keyboard map '$map' (dsys keymap list)"
|
||||
need_root keymap "$map"
|
||||
set_rc KEYMAP "$map"
|
||||
# The default console font has no Cyrillic letters
|
||||
if [[ $map == @(ua|ru|by|bg|mk|sr)* && -z $(rc_value FONT) ]]; then
|
||||
set_rc FONT cyr-sun16
|
||||
fi
|
||||
loadkeys "$map" >/dev/null 2>&1 || true
|
||||
say "Розкладка консолі: $map" "Console keyboard map: $map"
|
||||
}
|
||||
|
||||
cmd=${1:-info}
|
||||
[[ $# -gt 0 ]] && shift
|
||||
|
||||
case $cmd in
|
||||
info | status) cmd_info ;;
|
||||
hostname | host) cmd_hostname "$@" ;;
|
||||
timezone | tz) cmd_timezone "$@" ;;
|
||||
locale | lang) cmd_locale "$@" ;;
|
||||
keymap | keyboard) cmd_keymap "$@" ;;
|
||||
-h | --help | help) usage ;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,22 @@
|
||||
# bash completion for dsys
|
||||
# shellcheck shell=bash
|
||||
_dsys() {
|
||||
local cur=${COMP_WORDS[COMP_CWORD]}
|
||||
if ((COMP_CWORD == 1)); then
|
||||
mapfile -t COMPREPLY < <(compgen -W "info hostname timezone locale keymap help" -- "$cur")
|
||||
return
|
||||
fi
|
||||
((COMP_CWORD == 2)) || return
|
||||
case ${COMP_WORDS[1]} in
|
||||
timezone | tz)
|
||||
mapfile -t COMPREPLY < <(compgen -W "list UTC $(awk '!/^#/ { print $3 }' /usr/share/zoneinfo/zone1970.tab 2>/dev/null)" -- "$cur")
|
||||
;;
|
||||
locale | lang)
|
||||
mapfile -t COMPREPLY < <(compgen -W "list $(sed -n 's/^#\?\([^ ]*\)\.UTF-8 UTF-8[[:space:]]*$/\1/p' /etc/default/libc-locales 2>/dev/null)" -- "$cur")
|
||||
;;
|
||||
keymap | keyboard)
|
||||
mapfile -t COMPREPLY < <(compgen -W "list $(find /usr/share/kbd/keymaps -name '*.map.gz' -printf '%f\n' 2>/dev/null | sed 's/\.map\.gz$//')" -- "$cur")
|
||||
;;
|
||||
esac
|
||||
}
|
||||
complete -F _dsys dsys
|
||||
@@ -0,0 +1,396 @@
|
||||
#!/bin/bash
|
||||
# duser - manage user accounts on Das OS: create them with the right groups,
|
||||
# set passwords, lock them, add SSH keys.
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=helpers.sh
|
||||
. "${DAS_HELPERS:-/usr/lib/das/helpers.sh}"
|
||||
|
||||
# Groups of every new user, as the desktop installer gives them; only the ones
|
||||
# that exist on this system are used
|
||||
user_groups="users audio video input storage network optical scanner lp kvm plugdev bluetooth"
|
||||
# Administrators also get sudo, Docker (root-equivalent) and the system logs
|
||||
admin_groups="wheel docker socklog"
|
||||
|
||||
uid_min=$(awk '$1 == "UID_MIN" { print $2 }' /etc/login.defs 2>/dev/null)
|
||||
uid_max=$(awk '$1 == "UID_MAX" { print $2 }' /etc/login.defs 2>/dev/null)
|
||||
uid_min=${uid_min:-1000}
|
||||
uid_max=${uid_max:-60000}
|
||||
|
||||
usage() {
|
||||
if [[ -n $uk ]]; then
|
||||
cat <<'EOF'
|
||||
Використання: duser КОМАНДА [КОРИСТУВАЧ] [...]
|
||||
|
||||
list користувачі: адміністратор, вхід, SSH-ключі, групи
|
||||
add [КОРИСТУВАЧ] створити; чого не вказано, те запитає:
|
||||
--admin адміністратор (sudo, docker, журнали)
|
||||
--key КЛЮЧ|ФАЙЛ публічний SSH-ключ
|
||||
--no-password без пароля, вхід лише за ключем
|
||||
--password-stdin пароль зі стандартного входу (для скриптів)
|
||||
del КОРИСТУВАЧ [--home] видалити (--home: разом із домашнім каталогом)
|
||||
passwd [КОРИСТУВАЧ] змінити пароль (без імені — свій)
|
||||
lock КОРИСТУВАЧ заборонити вхід (і за паролем, і за ключем)
|
||||
unlock КОРИСТУВАЧ дозволити вхід знову
|
||||
key КОРИСТУВАЧ [list] SSH-ключі користувача
|
||||
key КОРИСТУВАЧ add [КЛЮЧ|ФАЙЛ|-]
|
||||
додати ключ (рядок, файл .pub або stdin)
|
||||
key КОРИСТУВАЧ rm НОМЕР видалити ключ за номером зі списку
|
||||
groups КОРИСТУВАЧ [+ГРУПА|-ГРУПА...]
|
||||
показати групи, додати (+) чи прибрати (-);
|
||||
+wheel робить адміністратором
|
||||
|
||||
Зміни груп діють із наступного входу користувача.
|
||||
EOF
|
||||
else
|
||||
cat <<'EOF'
|
||||
Usage: duser COMMAND [USER] [...]
|
||||
|
||||
list users: administrator, login, SSH keys, groups
|
||||
add [USER] create one; asks for what is not given:
|
||||
--admin an administrator (sudo, docker, system logs)
|
||||
--key KEY|FILE a public SSH key
|
||||
--no-password no password, key login only
|
||||
--password-stdin read the password from stdin (for scripts)
|
||||
del USER [--home] delete (--home: with the home directory)
|
||||
passwd [USER] change the password (without a name: your own)
|
||||
lock USER forbid logins (by password and by key)
|
||||
unlock USER allow logins again
|
||||
key USER [list] the user's SSH keys
|
||||
key USER add [KEY|FILE|-]
|
||||
add a key (a line, a .pub file or stdin)
|
||||
key USER rm NUMBER remove a key by its number in the list
|
||||
groups USER [+GROUP|-GROUP...]
|
||||
show the groups, add (+) or remove (-) some;
|
||||
+wheel makes an administrator
|
||||
|
||||
Group changes apply from the user's next login.
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
# A regular (human) account, not a system one
|
||||
check_user() {
|
||||
local uid
|
||||
uid=$(id -u "$1" 2>/dev/null) || die "немає користувача $1" "no user $1"
|
||||
((uid >= uid_min && uid <= uid_max)) ||
|
||||
die "$1 — системний обліковий запис, duser його не змінює" "$1 is a system account, duser leaves it alone"
|
||||
}
|
||||
|
||||
in_group() {
|
||||
id -nG "$1" | tr ' ' '\n' | grep -qx "$2"
|
||||
}
|
||||
|
||||
# existing GROUPS...: the given groups that exist, comma-separated
|
||||
existing() {
|
||||
local g out=()
|
||||
for g in "$@"; do
|
||||
getent group "$g" >/dev/null && out+=("$g")
|
||||
done
|
||||
local IFS=,
|
||||
echo "${out[*]}"
|
||||
}
|
||||
|
||||
# login_state USER: how the user can log in, from /etc/shadow
|
||||
login_state() {
|
||||
local hash expire today
|
||||
IFS=: read -r _ hash _ _ _ _ _ expire _ < <(getent shadow "$1")
|
||||
today=$(($(date +%s) / 86400))
|
||||
if [[ -n $expire ]] && ((expire <= today)); then
|
||||
say заблоковано locked
|
||||
elif [[ $hash == \$* ]]; then
|
||||
say пароль password
|
||||
else
|
||||
# "!", "*" or a "!"-locked hash: SSH keys still work
|
||||
say "лише ключ" "key only"
|
||||
fi
|
||||
}
|
||||
|
||||
keys_file() {
|
||||
echo "$(getent passwd "$1" | cut -d: -f6)/.ssh/authorized_keys"
|
||||
}
|
||||
|
||||
# The keys of a file, one per line, without comments and blank lines
|
||||
key_lines() {
|
||||
[[ -r $1 ]] || return 0
|
||||
grep -v '^[[:space:]]*\(#\|$\)' "$1" || true
|
||||
}
|
||||
|
||||
cmd_list() {
|
||||
local name uid home admin keys
|
||||
pad "$(say КОРИСТУВАЧ USER)" 16
|
||||
pad UID 6
|
||||
pad "$(say АДМІН ADMIN)" 6
|
||||
pad "$(say ВХІД LOGIN)" 12
|
||||
pad "$(say КЛЮЧІ KEYS)" 6
|
||||
say ГРУПИ GROUPS
|
||||
while IFS=: read -r name _ uid _ _ home _; do
|
||||
((uid >= uid_min && uid <= uid_max)) || continue
|
||||
admin=$(say ні no)
|
||||
in_group "$name" wheel && admin=$(say так yes)
|
||||
keys=$(key_lines "$home/.ssh/authorized_keys" | wc -l)
|
||||
pad "$name" 16
|
||||
pad "$uid" 6
|
||||
pad "$admin" 6
|
||||
pad "$(login_state "$name")" 12
|
||||
pad "$keys" 6
|
||||
id -nG "$name" | sed "s/^$name //; s/ /,/g"
|
||||
done < <(getent passwd | sort -t: -k3n)
|
||||
}
|
||||
|
||||
# add_key USER KEY|FILE|-: appends a validated key to the user's authorized_keys
|
||||
add_key() {
|
||||
local user=$1 src=$2 key file tmp group
|
||||
if [[ $src == - ]]; then
|
||||
key=$(cat)
|
||||
elif [[ -f $src ]]; then
|
||||
key=$(cat "$src")
|
||||
else
|
||||
key=$src
|
||||
fi
|
||||
key=$(printf '%s\n' "$key" | grep -v '^[[:space:]]*$' | head -n 1)
|
||||
tmp=$(mktemp)
|
||||
printf '%s\n' "$key" >"$tmp"
|
||||
if ! ssh-keygen -l -f "$tmp" >/dev/null 2>&1; then
|
||||
rm -f "$tmp"
|
||||
die "це не публічний SSH-ключ (потрібен рядок на кшталт 'ssh-ed25519 AAAA... ім'я')" \
|
||||
"this is not a public SSH key (expected a line like 'ssh-ed25519 AAAA... name')"
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
|
||||
file=$(keys_file "$user")
|
||||
# Same key type and body, whatever the comment
|
||||
if key_lines "$file" | awk -v k="$(awk '{ print $1, $2 }' <<<"$key")" '$1 " " $2 == k { found = 1 } END { exit !found }'; then
|
||||
say "$user: цей ключ уже є" "$user: this key is already there"
|
||||
return 0
|
||||
fi
|
||||
group=$(id -gn "$user")
|
||||
install -d -m 700 -o "$user" -g "$group" "$(dirname "$file")"
|
||||
printf '%s\n' "$key" >>"$file"
|
||||
chown "$user:$group" "$file"
|
||||
chmod 600 "$file"
|
||||
say "$user: ключ додано" "$user: key added"
|
||||
}
|
||||
|
||||
# set_password USER: asks on a terminal, reads one line from stdin otherwise
|
||||
set_password() {
|
||||
local password
|
||||
if interactive; then
|
||||
passwd "$1"
|
||||
else
|
||||
IFS= read -r password || die "пароль не передано" "no password given"
|
||||
[[ -n $password ]] || die "порожній пароль" "empty password"
|
||||
# -c: Void's PAM stack for chpasswd does not hash passwords itself
|
||||
printf '%s:%s\n' "$1" "$password" | chpasswd -c SHA512
|
||||
say "$1: пароль змінено" "$1: password changed"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_add() {
|
||||
local name="" admin="" key="" password=ask groups
|
||||
while (($#)); do
|
||||
case $1 in
|
||||
--admin) admin=1 ;;
|
||||
--key) key=${2:?}; shift ;;
|
||||
--no-password) password=none ;;
|
||||
--password-stdin) password=stdin ;;
|
||||
-*) die "невідомий параметр $1 (duser help)" "unknown option $1 (duser help)" ;;
|
||||
*) [[ -z $name ]] || die "зайвий аргумент $1" "extra argument $1"; name=$1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ -z $name ]]; then
|
||||
interactive || die "вкажіть ім'я користувача" "name the user"
|
||||
ask name "Ім'я користувача (латиницею)" "User name"
|
||||
fi
|
||||
[[ $name =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] ||
|
||||
die "ім'я '$name': малі латинські літери, цифри, '_', '-', до 32 символів" \
|
||||
"name '$name': lowercase Latin letters, digits, '_', '-', up to 32 characters"
|
||||
! id "$name" >/dev/null 2>&1 || die "користувач $name уже є" "user $name already exists"
|
||||
|
||||
if [[ -z $admin ]] && interactive && [[ $password != stdin ]]; then
|
||||
confirm "Адміністратор (sudo, docker, журнали)?" "Administrator (sudo, docker, system logs)?" && admin=1
|
||||
fi
|
||||
if [[ -z $key ]] && interactive && [[ $password != stdin ]]; then
|
||||
ask key "Публічний SSH-ключ: рядок або файл .pub (Enter — без ключа)" \
|
||||
"Public SSH key: a line or a .pub file (Enter for none)"
|
||||
fi
|
||||
[[ $password == ask ]] && ! interactive && password=none
|
||||
|
||||
# shellcheck disable=SC2086 # word lists
|
||||
groups=$(existing $user_groups ${admin:+$admin_groups})
|
||||
useradd -m -s /bin/bash -G "$groups" "$name"
|
||||
say "$name: створено, групи $groups" "$name: created, groups $groups"
|
||||
|
||||
# From here on the account exists: on a failure, say so instead of leaving
|
||||
# it half set up silently
|
||||
# shellcheck disable=SC2064 # $name is fixed by now
|
||||
trap "[[ \$? -eq 0 ]] || warn '$name створено не повністю; виправте через duser або видаліть: duser del $name --home' \
|
||||
'$name is only partly set up; fix it with duser or delete it: duser del $name --home'" EXIT
|
||||
[[ -n $key ]] && add_key "$name" "$key"
|
||||
case $password in
|
||||
ask | stdin) set_password "$name" ;;
|
||||
none)
|
||||
[[ -n $key ]] || warn "$name не має ні пароля, ні ключа і поки не зможе увійти: duser passwd $name" \
|
||||
"$name has neither a password nor a key and cannot log in yet: duser passwd $name"
|
||||
;;
|
||||
esac
|
||||
trap - EXIT
|
||||
}
|
||||
|
||||
# The current user (the one who ran sudo)
|
||||
me() {
|
||||
echo "${SUDO_USER:-$(id -un)}"
|
||||
}
|
||||
|
||||
cmd_del() {
|
||||
local name=${1:-} home="" others
|
||||
[[ -n $name ]] || die "вкажіть користувача" "name the user"
|
||||
[[ ${2:-} == --home ]] && home=1
|
||||
check_user "$name"
|
||||
[[ $name != "$(me)" ]] || die "не можна видалити себе" "you cannot delete yourself"
|
||||
if in_group "$name" wheel; then
|
||||
others=$(getent group wheel | cut -d: -f4 | tr ',' '\n' | grep -vx "$name" || true)
|
||||
[[ -n $others ]] || die "$name — єдиний адміністратор" "$name is the only administrator"
|
||||
fi
|
||||
if interactive; then
|
||||
confirm "Видалити користувача $name?" "Delete user $name?" || return 1
|
||||
if [[ -z $home ]] && confirm "Видалити й домашній каталог $(getent passwd "$name" | cut -d: -f6)?" \
|
||||
"Delete the home directory $(getent passwd "$name" | cut -d: -f6) too?"; then
|
||||
home=1
|
||||
fi
|
||||
fi
|
||||
# userdel refuses while the user has running processes, which is right.
|
||||
# Void creates no mail spools, so drop userdel's warning about it.
|
||||
local out
|
||||
if ! out=$(userdel ${home:+-r} "$name" 2>&1); then
|
||||
echo "$out" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -v 'mail spool' <<<"$out" >&2 || true
|
||||
say "$name: видалено" "$name: deleted"
|
||||
}
|
||||
|
||||
cmd_passwd() {
|
||||
local name=${1:-}
|
||||
if [[ -z $name || $name == "$(id -un)" ]] && [[ $EUID -ne 0 ]]; then
|
||||
# Your own password: no sudo, passwd asks for the current one
|
||||
exec passwd
|
||||
fi
|
||||
need_root passwd "$@"
|
||||
name=${name:-$(me)}
|
||||
check_user "$name"
|
||||
set_password "$name"
|
||||
}
|
||||
|
||||
cmd_lock() {
|
||||
local name=${1:-}
|
||||
[[ -n $name ]] || die "вкажіть користувача" "name the user"
|
||||
check_user "$name"
|
||||
[[ $name != "$(me)" ]] || die "не можна заблокувати себе" "you cannot lock yourself out"
|
||||
# -L disables the password; an expired account also stops SSH key logins
|
||||
usermod -L -e 1 "$name"
|
||||
say "$name: вхід заборонено (поточні сеанси тривають; завершити: pkill -KILL -u $name)" \
|
||||
"$name: logins forbidden (open sessions go on; to end them: pkill -KILL -u $name)"
|
||||
}
|
||||
|
||||
cmd_unlock() {
|
||||
local name=${1:-} hash
|
||||
[[ -n $name ]] || die "вкажіть користувача" "name the user"
|
||||
check_user "$name"
|
||||
# (usermod -e '' complains "no changes" when the account never expired)
|
||||
chage -E -1 "$name"
|
||||
# Re-enable the password only when there is one under the "!"
|
||||
hash=$(getent shadow "$name" | cut -d: -f2)
|
||||
[[ $hash == \!\$* ]] && usermod -U "$name"
|
||||
say "$name: вхід дозволено" "$name: logins allowed"
|
||||
}
|
||||
|
||||
cmd_key() {
|
||||
local name=${1:-} action=${2:-list} file n i line
|
||||
[[ -n $name ]] || die "вкажіть користувача" "name the user"
|
||||
check_user "$name"
|
||||
file=$(keys_file "$name")
|
||||
case $action in
|
||||
list | ls)
|
||||
if [[ -z $(key_lines "$file") ]]; then
|
||||
say "$name: SSH-ключів немає" "$name: no SSH keys"
|
||||
return 0
|
||||
fi
|
||||
i=0
|
||||
while IFS= read -r line; do
|
||||
i=$((i + 1))
|
||||
printf '%2d %s\n' "$i" "$(ssh-keygen -l -f - <<<"$line" 2>/dev/null || echo "? $line")"
|
||||
done < <(key_lines "$file")
|
||||
;;
|
||||
add)
|
||||
local src=${3:-}
|
||||
if [[ -z $src ]]; then
|
||||
interactive || die "вкажіть ключ, файл або -" "give a key, a file or -"
|
||||
ask src "Публічний SSH-ключ: рядок або файл .pub" "Public SSH key: a line or a .pub file"
|
||||
fi
|
||||
add_key "$name" "$src"
|
||||
;;
|
||||
rm | del)
|
||||
n=${3:-}
|
||||
[[ $n =~ ^[1-9][0-9]*$ ]] || die "вкажіть номер ключа (duser key $name)" "give the key number (duser key $name)"
|
||||
line=$(key_lines "$file" | sed -n "${n}p")
|
||||
[[ -n $line ]] || die "немає ключа номер $n" "no key number $n"
|
||||
# Remove that exact line, keeping comments and the other keys
|
||||
awk -v k="$line" '$0 != k' "$file" >"$file.new"
|
||||
chown --reference="$file" "$file.new"
|
||||
chmod 600 "$file.new"
|
||||
mv "$file.new" "$file"
|
||||
say "$name: ключ $n видалено" "$name: key $n removed"
|
||||
;;
|
||||
*) die "невідома дія $action (list, add, rm)" "unknown action $action (list, add, rm)" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd_groups() {
|
||||
local name=${1:-} arg g
|
||||
[[ -n $name ]] || die "вкажіть користувача" "name the user"
|
||||
check_user "$name"
|
||||
shift
|
||||
for arg in "$@"; do
|
||||
g=${arg:1}
|
||||
case $arg in
|
||||
+?*)
|
||||
getent group "$g" >/dev/null || die "немає групи $g" "no group $g"
|
||||
gpasswd -a "$name" "$g" >/dev/null
|
||||
;;
|
||||
-?*)
|
||||
if [[ $g == wheel && $name == "$(me)" ]]; then
|
||||
die "не можна позбавити себе прав адміністратора" "you cannot drop your own administrator rights"
|
||||
fi
|
||||
gpasswd -d "$name" "$g" >/dev/null
|
||||
;;
|
||||
*) die "'$arg': потрібно +ГРУПА або -ГРУПА" "'$arg': expected +GROUP or -GROUP" ;;
|
||||
esac
|
||||
done
|
||||
echo "$name: $(id -nG "$name" | sed "s/^$name //; s/ /, /g")"
|
||||
}
|
||||
|
||||
cmd=${1:-list}
|
||||
[[ $# -gt 0 ]] && shift
|
||||
|
||||
case $cmd in
|
||||
list | ls)
|
||||
# /etc/shadow is root-only
|
||||
need_root list
|
||||
cmd_list
|
||||
;;
|
||||
add | new) need_root add "$@"; cmd_add "$@" ;;
|
||||
del | rm | remove) need_root del "$@"; cmd_del "$@" ;;
|
||||
passwd | password) cmd_passwd "$@" ;;
|
||||
lock) need_root lock "$@"; cmd_lock "$@" ;;
|
||||
unlock) need_root unlock "$@"; cmd_unlock "$@" ;;
|
||||
key | keys) need_root key "$@"; cmd_key "$@" ;;
|
||||
groups | group) need_root groups "$@"; cmd_groups "$@" ;;
|
||||
-h | --help | help) usage ;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,42 @@
|
||||
# bash completion for duser
|
||||
# shellcheck shell=bash
|
||||
_duser_users() {
|
||||
local min max
|
||||
min=$(awk '$1 == "UID_MIN" { print $2 }' /etc/login.defs 2>/dev/null)
|
||||
max=$(awk '$1 == "UID_MAX" { print $2 }' /etc/login.defs 2>/dev/null)
|
||||
getent passwd | awk -F: -v min="${min:-1000}" -v max="${max:-60000}" '$3 >= min && $3 <= max { print $1 }'
|
||||
}
|
||||
|
||||
_duser() {
|
||||
local cur=${COMP_WORDS[COMP_CWORD]} cmd=${COMP_WORDS[1]:-}
|
||||
if ((COMP_CWORD == 1)); then
|
||||
mapfile -t COMPREPLY < <(compgen -W "list add del passwd lock unlock key groups help" -- "$cur")
|
||||
return
|
||||
fi
|
||||
case $cmd in
|
||||
add)
|
||||
if [[ ${COMP_WORDS[COMP_CWORD - 1]} == --key ]]; then
|
||||
mapfile -t COMPREPLY < <(compgen -f -- "$cur")
|
||||
else
|
||||
mapfile -t COMPREPLY < <(compgen -W "--admin --key --no-password --password-stdin" -- "$cur")
|
||||
fi
|
||||
;;
|
||||
del | passwd | lock | unlock | key | groups)
|
||||
if ((COMP_CWORD == 2)); then
|
||||
mapfile -t COMPREPLY < <(compgen -W "$(_duser_users)" -- "$cur")
|
||||
elif [[ $cmd == del ]]; then
|
||||
mapfile -t COMPREPLY < <(compgen -W "--home" -- "$cur")
|
||||
elif [[ $cmd == key ]] && ((COMP_CWORD == 3)); then
|
||||
mapfile -t COMPREPLY < <(compgen -W "list add rm" -- "$cur")
|
||||
elif [[ $cmd == key && ${COMP_WORDS[3]} == add ]]; then
|
||||
mapfile -t COMPREPLY < <(compgen -f -- "$cur")
|
||||
elif [[ $cmd == groups ]]; then
|
||||
# +GROUP and -GROUP
|
||||
local sign=${cur:0:1}
|
||||
[[ $sign == [+-] ]] || sign=+
|
||||
mapfile -t COMPREPLY < <(compgen -P "$sign" -W "$(getent group | cut -d: -f1)" -- "${cur#[+-]}")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
complete -F _duser duser
|
||||
@@ -0,0 +1,75 @@
|
||||
# Shared functions of the Das OS helpers (dsv, duser, dfw, dsys).
|
||||
# Sourced, not run: each helper sets "set -euo pipefail" itself.
|
||||
# shellcheck shell=bash
|
||||
|
||||
self=${0##*/}
|
||||
|
||||
if [[ ${LC_ALL:-} == uk* || ${LC_MESSAGES:-} == uk* || ${LANG:-} == 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
|
||||
}
|
||||
|
||||
warn() {
|
||||
say "$self: $1" "$self: $2" >&2
|
||||
}
|
||||
|
||||
die() {
|
||||
warn "$1" "$2"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# need_root ARGS...: runs the helper again through sudo with the same arguments
|
||||
need_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
exec sudo "$0" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Questions are only asked on a terminal; scripts must pass every argument
|
||||
interactive() {
|
||||
[[ -t 0 ]]
|
||||
}
|
||||
|
||||
# ask VAR UKRAINIAN ENGLISH [DEFAULT]: reads a line into VAR; the prompt shows
|
||||
# the default, which an empty answer keeps
|
||||
ask() {
|
||||
local -n _answer=$1
|
||||
local prompt
|
||||
prompt=$(say "$2" "$3")
|
||||
if [[ -n ${4:-} ]]; then
|
||||
prompt+=" [$4]"
|
||||
fi
|
||||
read -r -e -p "$prompt: " _answer || exit 1
|
||||
[[ -n $_answer ]] || _answer=${4:-}
|
||||
}
|
||||
|
||||
# confirm UKRAINIAN ENGLISH: a yes/no question, "no" by default
|
||||
confirm() {
|
||||
local reply
|
||||
read -r -p "$(say "$1 [т/Н]" "$2 [y/N]") " reply || return 1
|
||||
[[ ${reply,,} == @(y|yes|т|так) ]]
|
||||
}
|
||||
|
||||
# pad TEXT WIDTH: TEXT padded with spaces to WIDTH characters. printf pads by
|
||||
# bytes, ${#s} counts characters, so Cyrillic columns stay aligned.
|
||||
pad() {
|
||||
local width=$(($2 - ${#1}))
|
||||
((width > 0)) || width=0
|
||||
printf '%s%*s ' "$1" "$width" ''
|
||||
}
|
||||
|
||||
# set_rc VAR VALUE: sets VAR in /etc/rc.conf, uncommenting Void's example line
|
||||
set_rc() {
|
||||
local rc=${RC_CONF:-/etc/rc.conf}
|
||||
if grep -q "^#\?$1=" "$rc"; then
|
||||
sed -i "s|^#\?$1=.*|$1=\"$2\"|" "$rc"
|
||||
else
|
||||
echo "$1=\"$2\"" >>"$rc"
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
# Das OS developer profile, installed with "dprofile install dev".
|
||||
# shellcheck shell=bash disable=SC2034
|
||||
|
||||
title_uk="Розробник"
|
||||
title_en="Developer"
|
||||
desc_uk="Go, Python, Node.js, Git, Docker і Podman, VS Code; середовища JetBrains і Android Studio з Flathub"
|
||||
desc_en="Go, Python, Node.js, Git, Docker and Podman, VS Code; JetBrains IDEs and Android Studio from Flathub"
|
||||
# Without a graphical session
|
||||
server_desc_uk="Go, Python, Node.js, Git, Docker і Podman, консольні інструменти"
|
||||
server_desc_en="Go, Python, Node.js, Git, Docker and Podman, command-line tools"
|
||||
|
||||
# Packages from the Void repository, on every system
|
||||
packages="
|
||||
base-devel gdb strace
|
||||
git git-lfs github-cli lazygit
|
||||
go gopls delve golangci-lint
|
||||
python3-pip python3-virtualenv uv
|
||||
nodejs
|
||||
docker docker-compose docker-buildx lazydocker
|
||||
podman podman-compose
|
||||
jq ripgrep fd fzf direnv shellcheck sqlite"
|
||||
|
||||
# Only with a graphical session
|
||||
desktop_packages="
|
||||
vscode meld
|
||||
flatpak xdg-desktop-portal xdg-desktop-portal-gtk"
|
||||
|
||||
# runit services to enable
|
||||
services="docker"
|
||||
|
||||
# Groups the user who installs the profile joins (docker is root-equivalent)
|
||||
groups="docker"
|
||||
|
||||
# Rootless Podman needs subordinate user and group IDs, and a shared root
|
||||
# mount (runit leaves / private)
|
||||
subids=yes
|
||||
shared_root=yes
|
||||
|
||||
# Optional applications from Flathub (desktop only): ID and name
|
||||
apps="
|
||||
com.google.AndroidStudio Android Studio
|
||||
com.jetbrains.IntelliJ-IDEA-Community IntelliJ IDEA Open
|
||||
com.jetbrains.IntelliJ-IDEA-Ultimate IntelliJ IDEA Ultimate
|
||||
com.jetbrains.PyCharm-Professional PyCharm
|
||||
com.jetbrains.GoLand GoLand
|
||||
com.jetbrains.WebStorm WebStorm
|
||||
com.visualstudio.code Visual Studio Code (Microsoft)"
|
||||
@@ -0,0 +1,22 @@
|
||||
# Template file for 'das-helpers'
|
||||
pkgname=das-helpers
|
||||
version=0.2.0
|
||||
revision=1
|
||||
depends="bash runit sudo shadow openssh nftables kbd"
|
||||
short_desc="Das OS helpers: services, users, firewall, system settings, profiles"
|
||||
maintainer="Das <anry.das@gmail.com>"
|
||||
license="MIT"
|
||||
homepage="https://git.das.org.ua/das/Das-OS"
|
||||
# dsv used to be a package of its own
|
||||
replaces="dsv>=0"
|
||||
provides="dsv-${version}_${revision}"
|
||||
|
||||
do_install() {
|
||||
vinstall ${FILESDIR}/helpers.sh 644 usr/lib/das
|
||||
vinstall ${FILESDIR}/profiles/dev.conf 644 usr/share/das/profiles
|
||||
for tool in dsv duser dfw dsys dprofile; do
|
||||
vbin ${FILESDIR}/${tool}
|
||||
vcompletion ${FILESDIR}/${tool}.bash-completion bash ${tool}
|
||||
done
|
||||
vlicense ${FILESDIR}/LICENSE
|
||||
}
|
||||
@@ -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.
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
# Mount the clean live image for Calamares' unpackfs at /run/das-installer/root.
|
||||
#
|
||||
# Dracut runs the live root as a device-mapper snapshot (live-rw) that also
|
||||
# holds the live user and boot-time tweaks. Its origin, live-base, is the
|
||||
# untouched LiveOS/rootfs.img; mount it read-only without journal replay.
|
||||
set -eu
|
||||
|
||||
root=/run/das-installer/root
|
||||
mountpoint -q "$root" && exit 0
|
||||
|
||||
if [ ! -b /dev/mapper/live-base ]; then
|
||||
echo "mount-source: /dev/mapper/live-base not found, is this the live system?" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$root"
|
||||
mount -o ro,noload /dev/mapper/live-base "$root"
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/bin/sh
|
||||
# Finish a Das OS installation. The installers (Calamares, das-server-install)
|
||||
# run this inside the target system after users and locale are configured,
|
||||
# before GRUB.
|
||||
set -eu
|
||||
|
||||
msg() { echo "das-postinstall: $*"; }
|
||||
|
||||
# Calamares writes LANG to /etc/locale.conf, but glibc on Void only builds the
|
||||
# locales enabled in /etc/default/libc-locales.
|
||||
lang=$(sed -n 's/^LANG=//p' /etc/locale.conf 2>/dev/null | tr -d '"')
|
||||
for l in "$lang" en_US.UTF-8; do
|
||||
[ -n "$l" ] || continue
|
||||
re=$(printf '%s' "$l" | sed 's/[.]/\\./g')
|
||||
sed -i "s/^#\(${re} \)/\1/" /etc/default/libc-locales
|
||||
done
|
||||
msg "building locales"
|
||||
xbps-reconfigure -f glibc-locales
|
||||
|
||||
# Calamares mounts /tmp on tmpfs only when the root disk is an SSD; Void's own
|
||||
# installer always does.
|
||||
if ! grep -q '^[^#]*[[:space:]]/tmp[[:space:]]' /etc/fstab; then
|
||||
echo "tmpfs /tmp tmpfs defaults,nosuid,nodev 0 0" >>/etc/fstab
|
||||
fi
|
||||
|
||||
# mklive's list of packages left out of the image; das-server keeps its own
|
||||
rm -f /etc/xbps.d/mklive-ignore.conf
|
||||
|
||||
# Guest tools only help inside their own hypervisor (VirtualBox's ones even
|
||||
# complain at every login elsewhere).
|
||||
machine=$(cat /sys/class/dmi/id/sys_vendor /sys/class/dmi/id/product_name 2>/dev/null || true)
|
||||
if ! grep -q '^flags.* hypervisor' /proc/cpuinfo; then
|
||||
machine="real hardware"
|
||||
fi
|
||||
case $machine in
|
||||
*VirtualBox* | *innotek*) unused="spice-vdagent qemu-ga" ;;
|
||||
*QEMU* | *KVM*) unused="virtualbox-ose-guest" ;;
|
||||
*) unused="spice-vdagent qemu-ga virtualbox-ose-guest" ;;
|
||||
esac
|
||||
installed=""
|
||||
for pkg in $unused; do
|
||||
xbps-query "$pkg" >/dev/null 2>&1 && installed="$installed $pkg"
|
||||
done
|
||||
case $installed in
|
||||
*spice-vdagent*) rm -f /etc/runit/runsvdir/default/spice-vdagentd ;;
|
||||
esac
|
||||
case $installed in
|
||||
# The live image replaces this autostart file, so xbps keeps it as modified
|
||||
*virtualbox*) rm -f /etc/xdg/autostart/vboxclient.desktop ;;
|
||||
esac
|
||||
if [ -n "$installed" ]; then
|
||||
msg "removing guest tools not needed here:$installed"
|
||||
# shellcheck disable=SC2086
|
||||
xbps-remove -Ry $installed || true
|
||||
fi
|
||||
|
||||
# Snapper, hourly snapshots and the GRUB snapshot menu (Btrfs only).
|
||||
das-snapper-setup
|
||||
|
||||
# The installer copies files with rsync --sparse, which turns the zero tail of
|
||||
# a kernel image into a Btrfs hole that GRUB 2.12 can't read ("premature end
|
||||
# of file"). Rewrite the kernels in full.
|
||||
for f in /boot/vmlinuz-*; do
|
||||
cp --sparse=never "$f" "$f.das-tmp"
|
||||
mv "$f.das-tmp" "$f"
|
||||
done
|
||||
|
||||
# The live image has a generic initramfs. Build the target's own the same way
|
||||
# Void does on kernel updates (host-only), with das-snapshot-overlay included.
|
||||
msg "building the initramfs"
|
||||
for kver in $(ls /usr/lib/modules); do
|
||||
dracut -q --force "/boot/initramfs-$kver.img" "$kver"
|
||||
done
|
||||
msg "done"
|
||||
@@ -0,0 +1,15 @@
|
||||
# Template file for 'das-install-common'
|
||||
pkgname=das-install-common
|
||||
version=0.2.0
|
||||
revision=1
|
||||
depends="das-snapshots dracut rsync"
|
||||
short_desc="Das OS installer steps shared by the desktop and server installers"
|
||||
maintainer="Das <anry.das@gmail.com>"
|
||||
license="MIT"
|
||||
homepage="https://git.das.org.ua/das/Das-OS"
|
||||
|
||||
do_install() {
|
||||
vinstall ${FILESDIR}/mount-source 755 usr/libexec/das/installer
|
||||
vinstall ${FILESDIR}/postinstall 755 usr/libexec/das/installer
|
||||
vlicense ${FILESDIR}/LICENSE
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../LICENSE
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
componentName: dasos
|
||||
|
||||
welcomeStyleCalamares: false
|
||||
welcomeExpandingLogo: true
|
||||
windowExpanding: normal
|
||||
windowSize: 960px,640px
|
||||
windowPlacement: center
|
||||
sidebar: widget
|
||||
navigation: widget
|
||||
|
||||
strings:
|
||||
productName: "Das OS"
|
||||
shortProductName: "Das OS"
|
||||
version: "@VERSION@"
|
||||
shortVersion: "@SHORT_VERSION@"
|
||||
versionedName: "Das OS @VERSION@"
|
||||
shortVersionedName: "Das OS @SHORT_VERSION@"
|
||||
bootloaderEntryName: "Das OS"
|
||||
productUrl: "https://git.das.org.ua/das/Das-OS"
|
||||
supportUrl: "https://git.das.org.ua/das/Das-OS/issues"
|
||||
|
||||
images:
|
||||
productIcon: "/usr/share/icons/hicolor/scalable/apps/das-logo.svg"
|
||||
productLogo: "/usr/share/icons/hicolor/scalable/apps/das-logo.svg"
|
||||
productWelcome: "welcome.svg"
|
||||
|
||||
slideshow:
|
||||
- "slide-1.svg"
|
||||
- "slide-2.svg"
|
||||
- "slide-3.svg"
|
||||
|
||||
style:
|
||||
SidebarBackground: "#1b1f27"
|
||||
SidebarText: "#e3e6ec"
|
||||
SidebarTextCurrent: "#1b1f27"
|
||||
SidebarBackgroundCurrent: "#ffd23f"
|
||||
@@ -0,0 +1,28 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800" height="440" viewBox="0 0 800 440">
|
||||
<defs>
|
||||
<path id="petal" d="M0 0 L19.13 -46.19 L0 -92.39 L-19.13 -46.19 Z"/>
|
||||
<path id="core" d="M0 -25.4 L8.61 -46.19 L0 -66.98 L-8.61 -46.19 Z"/>
|
||||
<g id="pb"><use xlink:href="#petal" fill="#2f6fde"/><use xlink:href="#core" fill="#ffd23f"/></g>
|
||||
<g id="py"><use xlink:href="#petal" fill="#f5c400"/><use xlink:href="#core" fill="#1f4fa8"/></g>
|
||||
<g id="star">
|
||||
<use xlink:href="#pb"/><use xlink:href="#py" transform="rotate(45)"/>
|
||||
<use xlink:href="#pb" transform="rotate(90)"/><use xlink:href="#py" transform="rotate(135)"/>
|
||||
<use xlink:href="#pb" transform="rotate(180)"/><use xlink:href="#py" transform="rotate(225)"/>
|
||||
<use xlink:href="#pb" transform="rotate(270)"/><use xlink:href="#py" transform="rotate(315)"/>
|
||||
</g>
|
||||
<pattern id="stitch" patternUnits="userSpaceOnUse" width="28" height="20">
|
||||
<path d="M14 0 L24 10 L14 20 L4 10 Z" fill="#2f6fde"/>
|
||||
<path d="M14 6 L18 10 L14 14 L10 10 Z" fill="#f5c400"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="800" height="440" fill="#f6f7fa"/>
|
||||
<rect y="420" width="800" height="20" fill="url(#stitch)"/>
|
||||
<use xlink:href="#star" transform="translate(150 210) scale(1.15)"/>
|
||||
<g font-family="Inter, sans-serif" fill="#1f2430">
|
||||
<text x="300" y="150" font-size="34" font-weight="600">Ласкаво просимо!</text>
|
||||
<text x="300" y="205" font-size="19">Das OS — легка система на Void Linux</text>
|
||||
<text x="300" y="235" font-size="19">з робочим столом Xfce, українською</text>
|
||||
<text x="300" y="265" font-size="19">мовою та звичними програмами.</text>
|
||||
<text x="300" y="295" font-size="19"></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,28 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800" height="440" viewBox="0 0 800 440">
|
||||
<defs>
|
||||
<path id="petal" d="M0 0 L19.13 -46.19 L0 -92.39 L-19.13 -46.19 Z"/>
|
||||
<path id="core" d="M0 -25.4 L8.61 -46.19 L0 -66.98 L-8.61 -46.19 Z"/>
|
||||
<g id="pb"><use xlink:href="#petal" fill="#2f6fde"/><use xlink:href="#core" fill="#ffd23f"/></g>
|
||||
<g id="py"><use xlink:href="#petal" fill="#f5c400"/><use xlink:href="#core" fill="#1f4fa8"/></g>
|
||||
<g id="star">
|
||||
<use xlink:href="#pb"/><use xlink:href="#py" transform="rotate(45)"/>
|
||||
<use xlink:href="#pb" transform="rotate(90)"/><use xlink:href="#py" transform="rotate(135)"/>
|
||||
<use xlink:href="#pb" transform="rotate(180)"/><use xlink:href="#py" transform="rotate(225)"/>
|
||||
<use xlink:href="#pb" transform="rotate(270)"/><use xlink:href="#py" transform="rotate(315)"/>
|
||||
</g>
|
||||
<pattern id="stitch" patternUnits="userSpaceOnUse" width="28" height="20">
|
||||
<path d="M14 0 L24 10 L14 20 L4 10 Z" fill="#2f6fde"/>
|
||||
<path d="M14 6 L18 10 L14 14 L10 10 Z" fill="#f5c400"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="800" height="440" fill="#f6f7fa"/>
|
||||
<rect y="420" width="800" height="20" fill="url(#stitch)"/>
|
||||
<use xlink:href="#star" transform="translate(150 210) scale(1.15)"/>
|
||||
<g font-family="Inter, sans-serif" fill="#1f2430">
|
||||
<text x="300" y="150" font-size="34" font-weight="600">Знімки системи</text>
|
||||
<text x="300" y="205" font-size="19">Btrfs і snapper щогодини зберігають</text>
|
||||
<text x="300" y="235" font-size="19">стан системи. Попередній знімок можна</text>
|
||||
<text x="300" y="265" font-size="19">завантажити з меню GRUB і повернути</text>
|
||||
<text x="300" y="295" font-size="19">командою sudo snapper-rollback.</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,28 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800" height="440" viewBox="0 0 800 440">
|
||||
<defs>
|
||||
<path id="petal" d="M0 0 L19.13 -46.19 L0 -92.39 L-19.13 -46.19 Z"/>
|
||||
<path id="core" d="M0 -25.4 L8.61 -46.19 L0 -66.98 L-8.61 -46.19 Z"/>
|
||||
<g id="pb"><use xlink:href="#petal" fill="#2f6fde"/><use xlink:href="#core" fill="#ffd23f"/></g>
|
||||
<g id="py"><use xlink:href="#petal" fill="#f5c400"/><use xlink:href="#core" fill="#1f4fa8"/></g>
|
||||
<g id="star">
|
||||
<use xlink:href="#pb"/><use xlink:href="#py" transform="rotate(45)"/>
|
||||
<use xlink:href="#pb" transform="rotate(90)"/><use xlink:href="#py" transform="rotate(135)"/>
|
||||
<use xlink:href="#pb" transform="rotate(180)"/><use xlink:href="#py" transform="rotate(225)"/>
|
||||
<use xlink:href="#pb" transform="rotate(270)"/><use xlink:href="#py" transform="rotate(315)"/>
|
||||
</g>
|
||||
<pattern id="stitch" patternUnits="userSpaceOnUse" width="28" height="20">
|
||||
<path d="M14 0 L24 10 L14 20 L4 10 Z" fill="#2f6fde"/>
|
||||
<path d="M14 6 L18 10 L14 14 L10 10 Z" fill="#f5c400"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="800" height="440" fill="#f6f7fa"/>
|
||||
<rect y="420" width="800" height="20" fill="url(#stitch)"/>
|
||||
<use xlink:href="#star" transform="translate(150 210) scale(1.15)"/>
|
||||
<g font-family="Inter, sans-serif" fill="#1f2430">
|
||||
<text x="300" y="150" font-size="34" font-weight="600">Ваші файли в безпеці</text>
|
||||
<text x="300" y="205" font-size="19">Домашня тека лежить в окремому</text>
|
||||
<text x="300" y="235" font-size="19">підтомі й не входить до знімків,</text>
|
||||
<text x="300" y="265" font-size="19">тому відкат системи її не зачіпає.</text>
|
||||
<text x="300" y="295" font-size="19"></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="480" height="300" viewBox="0 0 480 300">
|
||||
<defs>
|
||||
<path id="petal" d="M0 0 L19.13 -46.19 L0 -92.39 L-19.13 -46.19 Z"/>
|
||||
<path id="core" d="M0 -25.4 L8.61 -46.19 L0 -66.98 L-8.61 -46.19 Z"/>
|
||||
<g id="pb"><use xlink:href="#petal" fill="#2f6fde"/><use xlink:href="#core" fill="#ffd23f"/></g>
|
||||
<g id="py"><use xlink:href="#petal" fill="#f5c400"/><use xlink:href="#core" fill="#1f4fa8"/></g>
|
||||
<g id="star">
|
||||
<use xlink:href="#pb"/><use xlink:href="#py" transform="rotate(45)"/>
|
||||
<use xlink:href="#pb" transform="rotate(90)"/><use xlink:href="#py" transform="rotate(135)"/>
|
||||
<use xlink:href="#pb" transform="rotate(180)"/><use xlink:href="#py" transform="rotate(225)"/>
|
||||
<use xlink:href="#pb" transform="rotate(270)"/><use xlink:href="#py" transform="rotate(315)"/>
|
||||
</g>
|
||||
<pattern id="stitch" patternUnits="userSpaceOnUse" width="28" height="20">
|
||||
<path d="M14 0 L24 10 L14 20 L4 10 Z" fill="#2f6fde"/>
|
||||
<path d="M14 6 L18 10 L14 14 L10 10 Z" fill="#f5c400"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<use xlink:href="#star" transform="translate(240 130) scale(1.15)"/>
|
||||
<text x="240" y="285" text-anchor="middle" font-family="Inter, sans-serif" font-size="34" font-weight="600" fill="#2f6fde">Das OS</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,13 @@
|
||||
---
|
||||
efiBootLoader: "grub"
|
||||
kernelSearchPath: "/boot"
|
||||
kernelPattern: "^vmlinuz.*"
|
||||
kernelParams: [ "quiet", "loglevel=3" ]
|
||||
grubInstall: "grub-install"
|
||||
grubMkconfig: "grub-mkconfig"
|
||||
grubCfg: "/boot/grub/grub.cfg"
|
||||
grubProbe: "grub-probe"
|
||||
efiBootMgr: "efibootmgr"
|
||||
efiBootloaderId: "dasos"
|
||||
installEFIFallback: true
|
||||
installHybridGRUB: false
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
displaymanagers:
|
||||
- lightdm
|
||||
defaultDesktopEnvironment:
|
||||
executable: "startxfce4"
|
||||
desktopFile: "xfce"
|
||||
basicSetup: false
|
||||
sysconfigSetup: false
|
||||
lightdm:
|
||||
preferred_greeters: ["lightdm-gtk-greeter.desktop"]
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
restartNowMode: user-unchecked
|
||||
restartNowCommand: "loginctl reboot"
|
||||
notifyOnFinished: false
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
crypttabOptions: luks
|
||||
tmpOptions:
|
||||
default:
|
||||
tmpfs: true
|
||||
options: "defaults,nosuid,nodev,mode=1777"
|
||||
ssd:
|
||||
tmpfs: true
|
||||
options: "defaults,noatime,nosuid,nodev,mode=1777"
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
overwrite: false
|
||||
prefer_grub_d: false
|
||||
keep_distributor: false
|
||||
kernel_params: [ "quiet", "loglevel=3" ]
|
||||
defaults:
|
||||
GRUB_TIMEOUT: 5
|
||||
GRUB_DEFAULT: "saved"
|
||||
GRUB_DISABLE_SUBMENU: true
|
||||
GRUB_TERMINAL_OUTPUT: "console"
|
||||
GRUB_DISABLE_RECOVERY: true
|
||||
GRUB_DISABLE_OS_PROBER: false
|
||||
always_use_defaults: false
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
xOrgConfFileName: "/etc/X11/xorg.conf.d/00-keyboard.conf"
|
||||
convertedKeymapPath: "/usr/share/kbd/keymaps/xkb"
|
||||
writeEtcDefaultKeyboard: false
|
||||
configure:
|
||||
kwin: false
|
||||
gnome: false
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
region: "Europe"
|
||||
zone: "Kyiv"
|
||||
geoip:
|
||||
style: none
|
||||
@@ -0,0 +1,6 @@
|
||||
# Void has no systemd: only D-Bus needs a machine id (/var/lib/dbus/machine-id).
|
||||
---
|
||||
systemd: false
|
||||
dbus: true
|
||||
dbus-symlink: false
|
||||
entropy-copy: false
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
extraMounts:
|
||||
- device: proc
|
||||
fs: proc
|
||||
mountPoint: /proc
|
||||
- device: sys
|
||||
fs: sysfs
|
||||
mountPoint: /sys
|
||||
- device: /dev
|
||||
mountPoint: /dev
|
||||
options: [ bind ]
|
||||
- device: tmpfs
|
||||
fs: tmpfs
|
||||
mountPoint: /run
|
||||
- device: /run/udev
|
||||
mountPoint: /run/udev
|
||||
options: [ bind ]
|
||||
- device: efivarfs
|
||||
fs: efivarfs
|
||||
mountPoint: /sys/firmware/efi/efivars
|
||||
efi: true
|
||||
|
||||
# The flat layout that snapper-rollback expects: the root in @ and snapshots
|
||||
# in @snapshots. Home, logs and the package cache stay out of root snapshots,
|
||||
# so a rollback never touches personal files.
|
||||
btrfsSubvolumes:
|
||||
- mountPoint: /
|
||||
subvolume: /@
|
||||
- mountPoint: /home
|
||||
subvolume: /@home
|
||||
- mountPoint: /.snapshots
|
||||
subvolume: /@snapshots
|
||||
- mountPoint: /var/log
|
||||
subvolume: /@var_log
|
||||
- mountPoint: /var/cache/xbps
|
||||
subvolume: /@var_cache_xbps
|
||||
|
||||
mountOptions:
|
||||
- filesystem: default
|
||||
options: [ defaults ]
|
||||
- filesystem: efi
|
||||
options: [ defaults, umask=0077 ]
|
||||
- filesystem: btrfs
|
||||
options: [ defaults, noatime, compress=zstd:1 ]
|
||||
- filesystem: ext4
|
||||
options: [ defaults, noatime ]
|
||||
- filesystem: xfs
|
||||
options: [ defaults, noatime ]
|
||||
@@ -0,0 +1,11 @@
|
||||
# The target is a copy of the live system: drop the installer and whatever was
|
||||
# installed only for it.
|
||||
---
|
||||
backend: xbps
|
||||
skip_if_no_internet: false
|
||||
update_db: false
|
||||
update_system: false
|
||||
operations:
|
||||
- try_remove:
|
||||
- das-installer
|
||||
- das-install-common
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
efi:
|
||||
mountPoint: "/boot/efi"
|
||||
recommendedSize: 512MiB
|
||||
minimumSize: 64MiB
|
||||
label: "EFI"
|
||||
|
||||
userSwapChoices:
|
||||
- none
|
||||
- small
|
||||
- suspend
|
||||
initialSwapChoice: none
|
||||
|
||||
drawNestedPartitions: false
|
||||
alwaysShowPartitionLabels: true
|
||||
allowManualPartitioning: true
|
||||
initialPartitioningChoice: none
|
||||
|
||||
# Btrfs subvolumes (see mount.conf) give snapper snapshots and rollback.
|
||||
defaultFileSystemType: "btrfs"
|
||||
availableFileSystemTypes: [ "btrfs", "ext4", "xfs" ]
|
||||
|
||||
# Disk encryption needs extra initramfs and GRUB setup that Das OS does not
|
||||
# have yet.
|
||||
enableLuksAutomatedPartitioning: false
|
||||
|
||||
essentialMounts: [ "live-*", "control", "ventoy" ]
|
||||
lvm:
|
||||
enable: false
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
dontChroot: false
|
||||
timeout: 900
|
||||
verbose: true
|
||||
script:
|
||||
- command: "/usr/libexec/das/installer/postinstall"
|
||||
timeout: 900
|
||||
@@ -0,0 +1,9 @@
|
||||
# The first snapshot is the freshly installed system. grub-mkconfig then adds
|
||||
# it to the boot menu through grub-btrfs.
|
||||
---
|
||||
dontChroot: false
|
||||
timeout: 300
|
||||
verbose: true
|
||||
script:
|
||||
- "-snapper --no-dbus -c root create -d 'Fresh install' -c number --userdata important=yes"
|
||||
- "-grub-mkconfig -o /boot/grub/grub.cfg"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
dontChroot: true
|
||||
timeout: 60
|
||||
script:
|
||||
- "/usr/libexec/das/installer/mount-source"
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copy the clean live image that shellprocess@source mounts, not the running
|
||||
# live root (see mount-source).
|
||||
---
|
||||
unpack:
|
||||
- source: "/run/das-installer/root"
|
||||
sourcefs: "file"
|
||||
destination: ""
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
defaultGroups:
|
||||
- name: users
|
||||
must_exist: true
|
||||
system: true
|
||||
- name: wheel
|
||||
must_exist: true
|
||||
system: true
|
||||
- audio
|
||||
- video
|
||||
- input
|
||||
- storage
|
||||
- network
|
||||
- optical
|
||||
- scanner
|
||||
- lp
|
||||
- kvm
|
||||
- plugdev
|
||||
- name: bluetooth
|
||||
must_exist: false
|
||||
system: true
|
||||
|
||||
sudoersGroup: wheel
|
||||
sudoersConfigureWithGroup: true
|
||||
autologinGroup: autologin
|
||||
|
||||
setRootPassword: true
|
||||
doReusePassword: true
|
||||
displayAutologin: true
|
||||
doAutologin: false
|
||||
|
||||
passwordRequirements:
|
||||
minLength: 4
|
||||
maxLength: -1
|
||||
allowWeakPasswords: true
|
||||
allowWeakPasswordsDefault: true
|
||||
|
||||
user:
|
||||
shell: /bin/bash
|
||||
forbidden_names: [ root, live ]
|
||||
home_permissions: "o700"
|
||||
|
||||
hostname:
|
||||
location: EtcFile
|
||||
writeHostsFile: true
|
||||
template: "${login}-dasos"
|
||||
forbidden_names: [ localhost ]
|
||||
|
||||
presets:
|
||||
fullName:
|
||||
editable: true
|
||||
loginName:
|
||||
editable: true
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
showSupportUrl: true
|
||||
showKnownIssuesUrl: false
|
||||
showReleaseNotesUrl: false
|
||||
showDonateUrl: false
|
||||
|
||||
requirements:
|
||||
requiredStorage: 15
|
||||
requiredRam: 1.5
|
||||
internetCheckUrl: https://voidlinux.org
|
||||
check:
|
||||
- storage
|
||||
- ram
|
||||
- power
|
||||
- internet
|
||||
- root
|
||||
- screen
|
||||
required:
|
||||
- storage
|
||||
- ram
|
||||
- root
|
||||
|
||||
geoip:
|
||||
style: none
|
||||
@@ -0,0 +1,54 @@
|
||||
# Calamares settings for Das OS (Void Linux, runit).
|
||||
---
|
||||
modules-search: [ local ]
|
||||
|
||||
instances:
|
||||
- id: source
|
||||
module: shellprocess
|
||||
config: shellprocess-source.conf
|
||||
- id: postinstall
|
||||
module: shellprocess
|
||||
config: shellprocess-postinstall.conf
|
||||
- id: snapshot
|
||||
module: shellprocess
|
||||
config: shellprocess-snapshot.conf
|
||||
|
||||
sequence:
|
||||
- show:
|
||||
- welcome
|
||||
- locale
|
||||
- keyboard
|
||||
- partition
|
||||
- users
|
||||
- summary
|
||||
- exec:
|
||||
- partition
|
||||
- mount
|
||||
- shellprocess@source
|
||||
- unpackfs
|
||||
- machineid
|
||||
- locale
|
||||
- keyboard
|
||||
- localecfg
|
||||
- fstab
|
||||
- users
|
||||
- displaymanager
|
||||
- networkcfg
|
||||
- hwclock
|
||||
- grubcfg
|
||||
- shellprocess@postinstall
|
||||
- packages
|
||||
- bootloader
|
||||
- shellprocess@snapshot
|
||||
- umount
|
||||
- show:
|
||||
- finished
|
||||
|
||||
branding: dasos
|
||||
prompt-install: true
|
||||
dont-chroot: false
|
||||
oem-setup: false
|
||||
disable-cancel: false
|
||||
disable-cancel-during-exec: false
|
||||
hide-back-and-next-during-exec: true
|
||||
quit-at-end: false
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
# Start the Das OS installer as root. The live user has passwordless sudo.
|
||||
exec sudo --preserve-env=DISPLAY,XAUTHORITY,LANG,LANGUAGE,LC_ALL \
|
||||
/usr/bin/calamares "$@"
|
||||
@@ -0,0 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Das OS installer icon
|
||||
Comment=Put the installer on the live desktop
|
||||
Exec=/usr/libexec/das/installer/installer-icon
|
||||
OnlyShowIn=XFCE;
|
||||
NoDisplay=true
|
||||
@@ -0,0 +1,12 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Install Das OS
|
||||
Name[uk]=Встановити Das OS
|
||||
Comment=Install Das OS on this computer
|
||||
Comment[uk]=Встановити Das OS на цей комп'ютер
|
||||
Exec=das-install
|
||||
Icon=das-logo
|
||||
Terminal=false
|
||||
Categories=System;
|
||||
Keywords=installer;calamares;
|
||||
Keywords[uk]=встановлення;інсталятор;calamares;
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# Put the installer launcher on the live user's desktop. Xfce runs launchers
|
||||
# from the desktop only when they are executable and marked as trusted.
|
||||
desktop=$(xdg-user-dir DESKTOP 2>/dev/null)
|
||||
[ -n "$desktop" ] && [ "$desktop" != "$HOME" ] || desktop="$HOME/Desktop"
|
||||
launcher="$desktop/das-installer.desktop"
|
||||
[ -e "$launcher" ] && exit 0
|
||||
|
||||
mkdir -p "$desktop"
|
||||
install -m 755 /usr/share/applications/das-installer.desktop "$launcher"
|
||||
gio set -t string "$launcher" metadata::xfce-exe-checksum \
|
||||
"$(sha256sum "$launcher" | cut -d' ' -f1)" 2>/dev/null || true
|
||||
@@ -0,0 +1,20 @@
|
||||
# Template file for 'das-installer'
|
||||
pkgname=das-installer
|
||||
version=0.3.0
|
||||
revision=1
|
||||
depends="calamares das-install-common das-theme sudo"
|
||||
short_desc="Das OS installer: Calamares configuration and branding for the live ISO"
|
||||
maintainer="Das <anry.das@gmail.com>"
|
||||
license="MIT"
|
||||
homepage="https://git.das.org.ua/das/Das-OS"
|
||||
|
||||
do_install() {
|
||||
local f
|
||||
vmkdir usr/share/calamares
|
||||
vcopy "${FILESDIR}/calamares/*" usr/share/calamares
|
||||
vbin ${FILESDIR}/das-install
|
||||
vinstall ${FILESDIR}/installer-icon 755 usr/libexec/das/installer
|
||||
vinstall ${FILESDIR}/das-installer.desktop 644 usr/share/applications
|
||||
vinstall ${FILESDIR}/das-installer-icon.desktop 644 etc/xdg/autostart
|
||||
vlicense ${FILESDIR}/LICENSE
|
||||
}
|
||||
@@ -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.
|
||||
@@ -0,0 +1,585 @@
|
||||
#!/bin/bash
|
||||
# das-server-install - console installer for Das OS Server.
|
||||
#
|
||||
# Usage: das-server-install [--config FILE]
|
||||
#
|
||||
# Asks its questions with dialog and installs the live image on a disk:
|
||||
# GPT with an EFI or BIOS boot partition and Btrfs with the same subvolumes
|
||||
# as the desktop installer, so snapshots and rollback work the same way.
|
||||
# With --config it asks nothing and takes the answers from FILE (see
|
||||
# /usr/share/examples/das-server-installer/install.conf).
|
||||
set -Eeuo pipefail
|
||||
|
||||
log=/tmp/das-server-install.log
|
||||
target=/mnt/das-target
|
||||
source_root=/run/das-installer/root
|
||||
libexec=/usr/libexec/das/installer
|
||||
|
||||
# Answers; a config file sets the same variables
|
||||
DISK=""
|
||||
HOST_NAME=dasos-server
|
||||
USER_NAME=""
|
||||
USER_PASSWORD=""
|
||||
SSH_KEY=""
|
||||
SSH_PASSWORDS=yes
|
||||
TIMEZONE=Europe/Kyiv
|
||||
KEYMAP=us
|
||||
LOCALE=en_US.UTF-8
|
||||
NETWORK=dhcpcd
|
||||
ERASE_DISK=no
|
||||
|
||||
lang=en
|
||||
[[ ${LANG:-} == uk* ]] && lang=uk
|
||||
|
||||
# t KEY: the message in the installer's language
|
||||
t() {
|
||||
local key=$1
|
||||
if [[ $lang == uk ]]; then
|
||||
case $key in
|
||||
title) echo "Встановлення Das OS Server" ;;
|
||||
lang) echo "Мова встановлення:" ;;
|
||||
disk) echo "Диск для встановлення. Усі дані на ньому буде знищено." ;;
|
||||
nodisk) echo "Не знайдено дисків для встановлення." ;;
|
||||
host) echo "Ім'я сервера (латинські літери, цифри, дефіс):" ;;
|
||||
badhost) echo "Неправильне ім'я сервера." ;;
|
||||
user) echo "Ім'я користувача-адміністратора (вхід через sudo, root вимкнено):" ;;
|
||||
baduser) echo "Неправильне ім'я: малі латинські літери, цифри, _ та -, не root." ;;
|
||||
pass) echo "Пароль користувача:" ;;
|
||||
pass2) echo "Ще раз той самий пароль:" ;;
|
||||
badpass) echo "Паролі порожні або не збігаються." ;;
|
||||
key) echo "Публічний ключ SSH для входу (вставте рядок ssh-ed25519 ... або залиште порожнім):" ;;
|
||||
badkey) echo "Це не схоже на публічний ключ SSH." ;;
|
||||
nopass) echo "Заборонити вхід по SSH за паролем (лише за ключем)?" ;;
|
||||
tz) echo "Часовий пояс (наприклад, Europe/Kyiv, UTC):" ;;
|
||||
badtz) echo "Немає такого часового поясу." ;;
|
||||
keymap) echo "Розкладка клавіатури в консолі:" ;;
|
||||
locale) echo "Мова системи:" ;;
|
||||
net) echo "Мережа:" ;;
|
||||
net_dhcpcd) echo "dhcpcd: DHCP на всіх інтерфейсах (просто)" ;;
|
||||
net_nm) echo "NetworkManager: налаштування через nmtui (Wi-Fi, статичні адреси)" ;;
|
||||
summary) echo "Перевірте налаштування:" ;;
|
||||
erase) echo "УСІ ДАНІ НА ДИСКУ %s БУДЕ ЗНИЩЕНО. Продовжити?" ;;
|
||||
cancel) echo "Встановлення скасовано." ;;
|
||||
s_source) echo "Підготовка образу системи..." ;;
|
||||
s_disk) echo "Розмітка диска %s..." ;;
|
||||
s_copy) echo "Копіювання системи (кілька хвилин)..." ;;
|
||||
s_config) echo "Налаштування системи..." ;;
|
||||
s_post) echo "Локалі, знімки, initramfs (кілька хвилин)..." ;;
|
||||
s_boot) echo "Встановлення завантажувача..." ;;
|
||||
finished) echo "Das OS Server встановлено. Вийміть інсталяційний носій і перезавантажте сервер.\n\nВхід: %s, далі sudo. Служби: dsv. Користувачі: duser. Порти: dfw. Налаштування: dsys. Оновлення: das-update. Профіль розробника: dprofile install dev. Запрошення терміналу: dprompt. Банер при вході: dbanner." ;;
|
||||
reboot) echo "Перезавантажити зараз?" ;;
|
||||
failed) echo "Помилка встановлення. Журнал: %s" ;;
|
||||
root) echo "Інсталятор треба запускати від root (sudo das-server-install)." ;;
|
||||
yes) echo "так" ;;
|
||||
no) echo "ні" ;;
|
||||
esac
|
||||
else
|
||||
case $key in
|
||||
title) echo "Das OS Server installation" ;;
|
||||
lang) echo "Installation language:" ;;
|
||||
disk) echo "Disk to install to. All data on it will be erased." ;;
|
||||
nodisk) echo "No disks to install to were found." ;;
|
||||
host) echo "Server name (Latin letters, digits, hyphen):" ;;
|
||||
badhost) echo "Invalid server name." ;;
|
||||
user) echo "Administrator user name (uses sudo, root login is disabled):" ;;
|
||||
baduser) echo "Invalid name: lowercase Latin letters, digits, _ and -, not root." ;;
|
||||
pass) echo "User password:" ;;
|
||||
pass2) echo "The same password again:" ;;
|
||||
badpass) echo "The passwords are empty or do not match." ;;
|
||||
key) echo "SSH public key to log in with (paste the ssh-ed25519 ... line, or leave empty):" ;;
|
||||
badkey) echo "This does not look like an SSH public key." ;;
|
||||
nopass) echo "Disable SSH password login (keys only)?" ;;
|
||||
tz) echo "Time zone (for example Europe/Kyiv, UTC):" ;;
|
||||
badtz) echo "No such time zone." ;;
|
||||
keymap) echo "Console keyboard layout:" ;;
|
||||
locale) echo "System language:" ;;
|
||||
net) echo "Network:" ;;
|
||||
net_dhcpcd) echo "dhcpcd: DHCP on all interfaces (simple)" ;;
|
||||
net_nm) echo "NetworkManager: set up with nmtui (Wi-Fi, static addresses)" ;;
|
||||
summary) echo "Check the settings:" ;;
|
||||
erase) echo "ALL DATA ON %s WILL BE ERASED. Continue?" ;;
|
||||
cancel) echo "Installation cancelled." ;;
|
||||
s_source) echo "Preparing the system image..." ;;
|
||||
s_disk) echo "Partitioning %s..." ;;
|
||||
s_copy) echo "Copying the system (a few minutes)..." ;;
|
||||
s_config) echo "Configuring the system..." ;;
|
||||
s_post) echo "Locales, snapshots, initramfs (a few minutes)..." ;;
|
||||
s_boot) echo "Installing the boot loader..." ;;
|
||||
finished) echo "Das OS Server is installed. Remove the installation medium and reboot.\n\nLog in as %s, then use sudo. Services: dsv. Users: duser. Ports: dfw. Settings: dsys. Updates: das-update. Developer profile: dprofile install dev. Terminal prompt: dprompt. Login banner: dbanner." ;;
|
||||
reboot) echo "Reboot now?" ;;
|
||||
failed) echo "Installation failed. Log: %s" ;;
|
||||
root) echo "The installer must run as root (sudo das-server-install)." ;;
|
||||
yes) echo "yes" ;;
|
||||
no) echo "no" ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
interactive=1
|
||||
dialog_ui() {
|
||||
local labels=()
|
||||
if [[ $lang == uk ]]; then
|
||||
labels=(--ok-label "Гаразд" --cancel-label "Скасувати" --yes-label "Так" --no-label "Ні")
|
||||
fi
|
||||
dialog --backtitle "Das OS Server" --title "$(t title)" "${labels[@]}" "$@" 3>&1 1>&2 2>&3
|
||||
}
|
||||
|
||||
# Shows a step in the dialog, or prints it when installing from a config file
|
||||
step() {
|
||||
if [[ -n $interactive ]]; then
|
||||
dialog --backtitle "Das OS Server" --title "$(t title)" --infobox "$1" 5 70
|
||||
echo "==> $1" >>"$log"
|
||||
else
|
||||
echo "==> $1" | tee -a "$log"
|
||||
fi
|
||||
}
|
||||
|
||||
fail() {
|
||||
trap - ERR
|
||||
local msg
|
||||
# shellcheck disable=SC2059
|
||||
msg=$(printf "$(t failed)" "$log")
|
||||
if [[ -n $interactive ]]; then
|
||||
dialog --backtitle "Das OS Server" --title "$(t title)" --msgbox "$msg\n\n$(tail -n 12 "$log" | sed 's/\\/\\\\/g')" 22 76 || true
|
||||
clear
|
||||
fi
|
||||
echo "das-server-install: $msg" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cancelled() {
|
||||
[[ -n $interactive ]] && clear
|
||||
t cancel
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Questions -------------------------------------------------------------
|
||||
|
||||
# Disks that can be installed to: not read-only, not the live medium
|
||||
list_disks() {
|
||||
local live="" dev
|
||||
if dev=$(blkid -L VOID_LIVE 2>/dev/null); then
|
||||
live=$(lsblk -no PKNAME "$dev" 2>/dev/null | head -n 1)
|
||||
[[ -n $live ]] && live=/dev/$live || live=$dev
|
||||
fi
|
||||
lsblk -dpno NAME,TYPE,RO,SIZE,MODEL | while read -r name type ro size model; do
|
||||
[[ $type == disk && $ro == 0 && $name != "$live" ]] || continue
|
||||
[[ $name == /dev/zram* || $name == /dev/loop* || $name == /dev/fd* ]] && continue
|
||||
printf '%s\t%s %s\n' "$name" "$size" "${model:-}"
|
||||
done
|
||||
}
|
||||
|
||||
ask_language() {
|
||||
local choice
|
||||
# The default console font shows Cyrillic as Latin look-alikes, so load one
|
||||
# with Cyrillic before the first "Українська". sudo runs us on its own pty
|
||||
# (tty says /dev/pts/N), so name the console; serial and ssh have TERM other
|
||||
# than linux and no font to set.
|
||||
[[ $TERM == linux ]] && { setfont -C /dev/tty0 cyr-sun16 2>/dev/null || true; }
|
||||
choice=$(dialog --backtitle "Das OS Server" --title "Das OS Server" \
|
||||
--default-item "$lang" --menu "Language / Мова:" 10 50 2 \
|
||||
en "English" uk "Українська" 3>&1 1>&2 2>&3) || cancelled
|
||||
lang=$choice
|
||||
if [[ $lang == uk ]]; then
|
||||
LOCALE=uk_UA.UTF-8
|
||||
fi
|
||||
}
|
||||
|
||||
ask_disk() {
|
||||
local items=() name desc
|
||||
while IFS=$'\t' read -r name desc; do
|
||||
items+=("$name" "$desc")
|
||||
done < <(list_disks)
|
||||
if [[ ${#items[@]} -eq 0 ]]; then
|
||||
dialog_ui --msgbox "$(t nodisk)" 7 60 || true
|
||||
cancelled
|
||||
fi
|
||||
DISK=$(dialog_ui --menu "$(t disk)" 15 70 6 "${items[@]}") || cancelled
|
||||
}
|
||||
|
||||
valid_host() {
|
||||
[[ $1 =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$ ]]
|
||||
}
|
||||
|
||||
valid_user() {
|
||||
[[ $1 =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] || return 1
|
||||
case $1 in root | live | nobody | daemon | bin | sys) return 1 ;; esac
|
||||
}
|
||||
|
||||
valid_key() {
|
||||
[[ -z $1 || $1 =~ ^(ssh-(ed25519|rsa|dss)|ecdsa-sha2-[a-z0-9]+|sk-[a-z0-9@.-]+)\ [A-Za-z0-9+/=]+ ]]
|
||||
}
|
||||
|
||||
ask_host() {
|
||||
while :; do
|
||||
HOST_NAME=$(dialog_ui --inputbox "$(t host)" 8 60 "$HOST_NAME") || cancelled
|
||||
valid_host "$HOST_NAME" && return
|
||||
dialog_ui --msgbox "$(t badhost)" 6 50 || true
|
||||
done
|
||||
}
|
||||
|
||||
ask_user() {
|
||||
local p1 p2
|
||||
while :; do
|
||||
USER_NAME=$(dialog_ui --inputbox "$(t user)" 9 60 "$USER_NAME") || cancelled
|
||||
valid_user "$USER_NAME" && break
|
||||
dialog_ui --msgbox "$(t baduser)" 7 60 || true
|
||||
done
|
||||
while :; do
|
||||
p1=$(dialog_ui --insecure --passwordbox "$(t pass)" 8 60) || cancelled
|
||||
p2=$(dialog_ui --insecure --passwordbox "$(t pass2)" 8 60) || cancelled
|
||||
if [[ -n $p1 && $p1 == "$p2" ]]; then
|
||||
USER_PASSWORD=$p1
|
||||
return
|
||||
fi
|
||||
dialog_ui --msgbox "$(t badpass)" 6 50 || true
|
||||
done
|
||||
}
|
||||
|
||||
ask_ssh() {
|
||||
while :; do
|
||||
SSH_KEY=$(dialog_ui --inputbox "$(t key)" 10 76 "$SSH_KEY") || cancelled
|
||||
valid_key "$SSH_KEY" && break
|
||||
dialog_ui --msgbox "$(t badkey)" 6 50 || true
|
||||
done
|
||||
SSH_PASSWORDS=yes
|
||||
if [[ -n $SSH_KEY ]] && dialog_ui --yesno "$(t nopass)" 7 60; then
|
||||
SSH_PASSWORDS=no
|
||||
fi
|
||||
}
|
||||
|
||||
ask_system() {
|
||||
while :; do
|
||||
TIMEZONE=$(dialog_ui --inputbox "$(t tz)" 8 60 "$TIMEZONE") || cancelled
|
||||
[[ -f /usr/share/zoneinfo/$TIMEZONE ]] && break
|
||||
dialog_ui --msgbox "$(t badtz)" 6 50 || true
|
||||
done
|
||||
KEYMAP=$(dialog_ui --default-item "$KEYMAP" --menu "$(t keymap)" 15 50 7 \
|
||||
us "English (US)" ua-utf "Українська" uk "English (UK)" de-latin1 "Deutsch" \
|
||||
fr-latin1 "Français" pl2 "Polski") || cancelled
|
||||
LOCALE=$(dialog_ui --default-item "$LOCALE" --menu "$(t locale)" 10 50 2 \
|
||||
en_US.UTF-8 "English" uk_UA.UTF-8 "Українська") || cancelled
|
||||
NETWORK=$(dialog_ui --default-item "$NETWORK" --menu "$(t net)" 10 76 2 \
|
||||
dhcpcd "$(t net_dhcpcd)" NetworkManager "$(t net_nm)") || cancelled
|
||||
}
|
||||
|
||||
confirm() {
|
||||
local key pw msg
|
||||
key=$(t no)
|
||||
pw=$(t yes)
|
||||
[[ -n $SSH_KEY ]] && key=$(t yes)
|
||||
[[ $SSH_PASSWORDS == no ]] && pw=$(t no)
|
||||
dialog_ui --no-collapse --msgbox "$(t summary)
|
||||
|
||||
Disk: $DISK
|
||||
Host: $HOST_NAME
|
||||
User: $USER_NAME
|
||||
SSH key: $key
|
||||
SSH passwords: $pw
|
||||
Time zone: $TIMEZONE
|
||||
Keyboard: $KEYMAP
|
||||
Language: $LOCALE
|
||||
Network: $NETWORK" 18 60 || cancelled
|
||||
# shellcheck disable=SC2059
|
||||
msg=$(printf "$(t erase)" "$DISK")
|
||||
dialog_ui --defaultno --yesno "$msg" 7 60 || cancelled
|
||||
ERASE_DISK=yes
|
||||
}
|
||||
|
||||
# --- Installation ----------------------------------------------------------
|
||||
|
||||
# part N: the device of partition N on $DISK
|
||||
part() {
|
||||
lsblk -lnpo NAME,TYPE "$DISK" | awk '$2 == "part" { print $1 }' | sed -n "${1}p"
|
||||
}
|
||||
|
||||
in_target() {
|
||||
chroot "$target" "$@"
|
||||
}
|
||||
|
||||
# Undo mounts from an earlier attempt or a failure
|
||||
unmount_target() {
|
||||
if mountpoint -q "$target"; then
|
||||
umount -R "$target" || umount -Rl "$target"
|
||||
fi
|
||||
}
|
||||
|
||||
uefi() {
|
||||
[[ -d /sys/firmware/efi ]]
|
||||
}
|
||||
|
||||
serial_console() {
|
||||
grep -q 'console=ttyS0' /proc/cmdline
|
||||
}
|
||||
|
||||
partition() {
|
||||
local efi root
|
||||
unmount_target
|
||||
wipefs -a "$DISK"
|
||||
sgdisk --zap-all "$DISK"
|
||||
if uefi; then
|
||||
sgdisk -n 1:0:+512M -t 1:ef00 -c 1:EFI -n 2:0:0 -t 2:8300 -c 2:dasos "$DISK"
|
||||
else
|
||||
# GRUB on BIOS keeps its core image in a small partition on GPT
|
||||
sgdisk -n 1:0:+1M -t 1:ef02 -c 1:bios -n 2:0:0 -t 2:8300 -c 2:dasos "$DISK"
|
||||
fi
|
||||
partprobe "$DISK" 2>/dev/null || true
|
||||
udevadm settle
|
||||
efi=$(part 1)
|
||||
root=$(part 2)
|
||||
[[ -b $root ]] || return 1
|
||||
|
||||
uefi && mkfs.vfat -F 32 -n EFI "$efi"
|
||||
mkfs.btrfs -f -L dasos "$root"
|
||||
|
||||
# The same subvolumes as the Calamares configuration (mount.conf)
|
||||
mkdir -p "$target"
|
||||
mount "$root" "$target"
|
||||
for sv in @ @home @snapshots @var_log @var_cache_xbps; do
|
||||
btrfs subvolume create "$target/$sv"
|
||||
done
|
||||
umount "$target"
|
||||
|
||||
local opts=noatime,compress=zstd:1
|
||||
mount -o "$opts,subvol=@" "$root" "$target"
|
||||
mkdir -p "$target"/{home,.snapshots,var/log,var/cache/xbps}
|
||||
mount -o "$opts,subvol=@home" "$root" "$target/home"
|
||||
mount -o "$opts,subvol=@snapshots" "$root" "$target/.snapshots"
|
||||
mount -o "$opts,subvol=@var_log" "$root" "$target/var/log"
|
||||
mount -o "$opts,subvol=@var_cache_xbps" "$root" "$target/var/cache/xbps"
|
||||
if uefi; then
|
||||
mkdir -p "$target/boot/efi"
|
||||
mount -o umask=0077 "$efi" "$target/boot/efi"
|
||||
fi
|
||||
}
|
||||
|
||||
copy_system() {
|
||||
# No --sparse: GRUB can't read kernels with Btrfs holes (see postinstall)
|
||||
rsync -aHAX --numeric-ids "$source_root/" "$target/"
|
||||
}
|
||||
|
||||
write_fstab() {
|
||||
local root_uuid efi_uuid opts=noatime,compress=zstd:1
|
||||
root_uuid=$(blkid -s UUID -o value "$(part 2)")
|
||||
{
|
||||
echo "# <file system> <dir> <type> <options> <dump> <pass>"
|
||||
echo "UUID=$root_uuid / btrfs $opts,subvol=@ 0 0"
|
||||
echo "UUID=$root_uuid /home btrfs $opts,subvol=@home 0 0"
|
||||
echo "UUID=$root_uuid /.snapshots btrfs $opts,subvol=@snapshots 0 0"
|
||||
echo "UUID=$root_uuid /var/log btrfs $opts,subvol=@var_log 0 0"
|
||||
echo "UUID=$root_uuid /var/cache/xbps btrfs $opts,subvol=@var_cache_xbps 0 0"
|
||||
if uefi; then
|
||||
efi_uuid=$(blkid -s UUID -o value "$(part 1)")
|
||||
echo "UUID=$efi_uuid /boot/efi vfat defaults,umask=0077 0 2"
|
||||
fi
|
||||
} >"$target/etc/fstab"
|
||||
}
|
||||
|
||||
mount_chroot() {
|
||||
mount -t proc proc "$target/proc"
|
||||
mount -t sysfs sys "$target/sys"
|
||||
mount --rbind /dev "$target/dev"
|
||||
mount -t tmpfs tmpfs "$target/run"
|
||||
if uefi; then
|
||||
mount -t efivarfs efivarfs "$target/sys/firmware/efi/efivars" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
# set_rc VAR VALUE: sets VAR in /etc/rc.conf, uncommenting Void's example line
|
||||
set_rc() {
|
||||
local rc=$target/etc/rc.conf
|
||||
if grep -q "^#\?$1=" "$rc"; then
|
||||
sed -i "s|^#\?$1=.*|$1=\"$2\"|" "$rc"
|
||||
else
|
||||
echo "$1=\"$2\"" >>"$rc"
|
||||
fi
|
||||
}
|
||||
|
||||
enable_service() {
|
||||
ln -sfn "/etc/sv/$1" "$target/etc/runit/runsvdir/default/$1"
|
||||
}
|
||||
|
||||
disable_service() {
|
||||
rm -f "$target/etc/runit/runsvdir/default/$1"
|
||||
}
|
||||
|
||||
configure() {
|
||||
local home
|
||||
|
||||
# The live image's /etc/issue tells how to log in to the live system: keep
|
||||
# its first line, with the Das OS version
|
||||
printf '%s\n\n' "$(head -n1 /etc/issue)" >"$target/etc/issue"
|
||||
echo "$HOST_NAME" >"$target/etc/hostname"
|
||||
if ! grep -q "[[:space:]]$HOST_NAME\$" "$target/etc/hosts"; then
|
||||
printf '127.0.1.1\t%s\n' "$HOST_NAME" >>"$target/etc/hosts"
|
||||
fi
|
||||
echo "LANG=$LOCALE" >"$target/etc/locale.conf"
|
||||
ln -sfn "/usr/share/zoneinfo/$TIMEZONE" "$target/etc/localtime"
|
||||
set_rc KEYMAP "$KEYMAP"
|
||||
set_rc HARDWARECLOCK UTC
|
||||
[[ $LOCALE == uk* || $KEYMAP == ua* ]] && set_rc FONT cyr-sun16
|
||||
|
||||
# The administrator: sudo through wheel, root has no password
|
||||
in_target useradd -m -s /bin/bash -G wheel,docker,socklog "$USER_NAME"
|
||||
# -c: Void's PAM stack for chpasswd only has pam_permit for passwords, so
|
||||
# without it nothing is written (Void's own installer does the same)
|
||||
printf '%s:%s\n' "$USER_NAME" "$USER_PASSWORD" | in_target chpasswd -c SHA512
|
||||
in_target passwd -l root >/dev/null
|
||||
echo "%wheel ALL=(ALL:ALL) ALL" >"$target/etc/sudoers.d/10-wheel"
|
||||
chmod 440 "$target/etc/sudoers.d/10-wheel"
|
||||
|
||||
if [[ -n $SSH_KEY ]]; then
|
||||
home=$target/home/$USER_NAME
|
||||
install -d -m 700 "$home/.ssh"
|
||||
echo "$SSH_KEY" >"$home/.ssh/authorized_keys"
|
||||
chmod 600 "$home/.ssh/authorized_keys"
|
||||
in_target chown -R "$USER_NAME:$USER_NAME" "/home/$USER_NAME/.ssh"
|
||||
fi
|
||||
if [[ $SSH_PASSWORDS == no ]]; then
|
||||
printf '%s\n' "# Written by das-server-install: SSH keys only" \
|
||||
"PasswordAuthentication no" "KbdInteractiveAuthentication no" \
|
||||
>"$target/etc/ssh/sshd_config.d/40-das-keys-only.conf"
|
||||
fi
|
||||
|
||||
for sv in sshd chronyd nftables fail2ban docker socklog-unix nanoklogd; do
|
||||
enable_service "$sv"
|
||||
done
|
||||
if [[ $NETWORK == NetworkManager ]]; then
|
||||
disable_service dhcpcd
|
||||
enable_service dbus
|
||||
enable_service NetworkManager
|
||||
else
|
||||
enable_service dhcpcd
|
||||
fi
|
||||
serial_console && enable_service agetty-ttyS0
|
||||
return 0
|
||||
}
|
||||
|
||||
install_bootloader() {
|
||||
local grub=$target/etc/default/grub cmdline="loglevel=4"
|
||||
serial_console && cmdline+=" console=tty0 console=ttyS0,115200"
|
||||
sed -i -e '/^GRUB_DISTRIBUTOR=/d' -e '/^GRUB_CMDLINE_LINUX_DEFAULT=/d' -e '/^GRUB_TIMEOUT=/d' \
|
||||
-e '/^GRUB_TERMINAL/d' -e '/^GRUB_SERIAL_COMMAND=/d' -e '/^GRUB_DISABLE_OS_PROBER=/d' "$grub"
|
||||
{
|
||||
echo 'GRUB_DISTRIBUTOR="Das OS"'
|
||||
echo "GRUB_CMDLINE_LINUX_DEFAULT=\"$cmdline\""
|
||||
echo 'GRUB_TIMEOUT=5'
|
||||
echo 'GRUB_DISABLE_OS_PROBER=true'
|
||||
if serial_console; then
|
||||
echo 'GRUB_TERMINAL="console serial"'
|
||||
echo 'GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200"'
|
||||
fi
|
||||
} >>"$grub"
|
||||
|
||||
if uefi; then
|
||||
in_target grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=dasos
|
||||
# Also at the fallback path, for firmware that forgets boot entries
|
||||
in_target grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable --no-nvram
|
||||
else
|
||||
in_target grub-install --target=i386-pc "$DISK"
|
||||
fi
|
||||
}
|
||||
|
||||
finish() {
|
||||
# The first snapshot, also shown in the boot menu (like Calamares' shellprocess@snapshot)
|
||||
in_target snapper --no-dbus -c root create -d "Fresh install" -c number --userdata important=yes || true
|
||||
in_target grub-mkconfig -o /boot/grub/grub.cfg
|
||||
# The installer is not needed on the installed system
|
||||
in_target xbps-remove -y das-server-installer das-install-common || true
|
||||
sync
|
||||
}
|
||||
|
||||
# Any failing command stops the installation with the log (set -E: also
|
||||
# inside functions; "f || fail" would switch set -e off within f).
|
||||
install_system() {
|
||||
: >"$log"
|
||||
trap fail ERR
|
||||
step "$(t s_source)"
|
||||
"$libexec/mount-source" >>"$log" 2>&1
|
||||
# shellcheck disable=SC2059
|
||||
step "$(printf "$(t s_disk)" "$DISK")"
|
||||
partition >>"$log" 2>&1
|
||||
step "$(t s_copy)"
|
||||
copy_system >>"$log" 2>&1
|
||||
step "$(t s_config)"
|
||||
write_fstab >>"$log" 2>&1
|
||||
mount_chroot >>"$log" 2>&1
|
||||
configure >>"$log" 2>&1
|
||||
step "$(t s_post)"
|
||||
in_target "$libexec/postinstall" >>"$log" 2>&1
|
||||
step "$(t s_boot)"
|
||||
install_bootloader >>"$log" 2>&1
|
||||
finish >>"$log" 2>&1
|
||||
unmount_target >>"$log" 2>&1
|
||||
trap - ERR
|
||||
}
|
||||
|
||||
load_config() {
|
||||
local file=$1
|
||||
[[ -r $file ]] || {
|
||||
echo "das-server-install: cannot read $file" >&2
|
||||
exit 1
|
||||
}
|
||||
# shellcheck source=/dev/null
|
||||
. "$file"
|
||||
interactive=""
|
||||
local bad=""
|
||||
[[ -b $DISK ]] || bad+=" DISK"
|
||||
valid_host "$HOST_NAME" || bad+=" HOST_NAME"
|
||||
valid_user "$USER_NAME" || bad+=" USER_NAME"
|
||||
[[ -n $USER_PASSWORD ]] || bad+=" USER_PASSWORD"
|
||||
valid_key "$SSH_KEY" || bad+=" SSH_KEY"
|
||||
[[ $SSH_PASSWORDS == yes || ( $SSH_PASSWORDS == no && -n $SSH_KEY ) ]] || bad+=" SSH_PASSWORDS"
|
||||
[[ -f /usr/share/zoneinfo/$TIMEZONE ]] || bad+=" TIMEZONE"
|
||||
[[ $NETWORK == dhcpcd || $NETWORK == NetworkManager ]] || bad+=" NETWORK"
|
||||
[[ $ERASE_DISK == yes ]] || bad+=" ERASE_DISK"
|
||||
if [[ -n $bad ]]; then
|
||||
echo "das-server-install: invalid or missing in $file:$bad" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
exec sudo --preserve-env=LANG "$0" "$@"
|
||||
fi
|
||||
case ${1:-} in
|
||||
--config)
|
||||
load_config "${2:?--config needs a file}"
|
||||
;;
|
||||
-h | --help)
|
||||
sed -n '2,11s/^# \{0,1\}//p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
"") ;;
|
||||
*)
|
||||
echo "das-server-install: unknown option $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n $interactive ]]; then
|
||||
ask_language
|
||||
ask_disk
|
||||
ask_host
|
||||
ask_user
|
||||
ask_ssh
|
||||
ask_system
|
||||
confirm
|
||||
fi
|
||||
install_system
|
||||
|
||||
if [[ -n $interactive ]]; then
|
||||
local msg
|
||||
# shellcheck disable=SC2059
|
||||
msg=$(printf "$(t finished)" "$USER_NAME")
|
||||
if dialog_ui --yesno "$msg\n\n$(t reboot)" 12 70; then
|
||||
reboot
|
||||
fi
|
||||
clear
|
||||
else
|
||||
echo "==> done, log: $log"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,20 @@
|
||||
# Answers for an unattended Das OS Server installation:
|
||||
# sudo das-server-install --config install.conf
|
||||
# The file is read by bash. Every value is checked before anything is done.
|
||||
|
||||
# The disk to install to; it is erased completely
|
||||
DISK=/dev/vda
|
||||
ERASE_DISK=no # must be "yes" to install
|
||||
|
||||
HOST_NAME=dasos-server
|
||||
USER_NAME=admin # gets sudo; root login stays disabled
|
||||
USER_PASSWORD=change-me
|
||||
|
||||
# A public key for SSH logins (optional), and whether passwords stay allowed
|
||||
SSH_KEY=""
|
||||
SSH_PASSWORDS=yes # "no" requires SSH_KEY
|
||||
|
||||
TIMEZONE=Europe/Kyiv
|
||||
KEYMAP=us # console layout: us, ua-utf, uk, de-latin1, ...
|
||||
LOCALE=en_US.UTF-8 # or uk_UA.UTF-8
|
||||
NETWORK=dhcpcd # or NetworkManager
|
||||
@@ -0,0 +1,16 @@
|
||||
# Template file for 'das-server-installer'
|
||||
pkgname=das-server-installer
|
||||
version=0.5.0
|
||||
revision=1
|
||||
depends="bash dialog das-install-common btrfs-progs dosfstools gptfdisk parted
|
||||
rsync util-linux grub grub-x86_64-efi efibootmgr"
|
||||
short_desc="Das OS Server console installer for the live ISO"
|
||||
maintainer="Das <anry.das@gmail.com>"
|
||||
license="MIT"
|
||||
homepage="https://git.das.org.ua/das/Das-OS"
|
||||
|
||||
do_install() {
|
||||
vbin ${FILESDIR}/das-server-install
|
||||
vsconf ${FILESDIR}/install.conf
|
||||
vlicense ${FILESDIR}/LICENSE
|
||||
}
|
||||
@@ -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.
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"log-driver": "local"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# Das OS: ban addresses that keep failing SSH logins, through nftables.
|
||||
[DEFAULT]
|
||||
banaction = nftables-multiport
|
||||
banaction_allports = nftables-allports
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
backend = polling
|
||||
# socklog-void collects auth messages here. svlogd starts each line with a
|
||||
# UTC time stamp; the pattern also consumes the facility and the syslog date
|
||||
# that follow it, so the sshd filter sees "sshd-session[pid]: ...":
|
||||
# 2026-10-19T17:25:46.09500 auth.info: Oct 19 20:25:46 sshd-session[1154]: Invalid user ...
|
||||
logpath = /var/log/socklog/secure/current
|
||||
datepattern = ^%%Y-%%m-%%dT%%H:%%M:%%S\.%%f(?: \S+: \S+ +\d+ \S+)?
|
||||
logtimezone = UTC
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/sbin/nft -f
|
||||
# Das OS server firewall: incoming connections are closed except SSH.
|
||||
#
|
||||
# Open ports with dfw (dfw allow 80/tcp), which keeps them in
|
||||
# /etc/nftables.d/dfw.nft. Ports that Docker containers publish (-p 8080:80)
|
||||
# pass through Docker's own forwarding rules, not the input chain below.
|
||||
#
|
||||
# After editing this file: dfw reload. Only the "das" table is replaced, so the
|
||||
# rules of Docker and fail2ban stay. Do not restart the nftables service for
|
||||
# that: its finish script flushes the whole ruleset, theirs too.
|
||||
|
||||
table inet das
|
||||
delete table inet das
|
||||
|
||||
table inet das {
|
||||
# Filled by dfw
|
||||
set tcp_ports {
|
||||
type inet_service; flags interval; auto-merge;
|
||||
}
|
||||
set udp_ports {
|
||||
type inet_service; flags interval; auto-merge;
|
||||
}
|
||||
|
||||
chain input {
|
||||
type filter hook input priority filter; policy drop;
|
||||
|
||||
ct state established,related accept
|
||||
ct state invalid drop
|
||||
iif lo accept
|
||||
meta l4proto { icmp, ipv6-icmp } accept
|
||||
# DHCPv6 answers come from the router's link-local address
|
||||
ip6 saddr fe80::/10 udp dport dhcpv6-client accept
|
||||
# Containers may reach services on the host (DNS, a database...)
|
||||
iifname "docker0" accept
|
||||
iifname "br-*" accept
|
||||
|
||||
tcp dport ssh accept
|
||||
tcp dport @tcp_ports accept
|
||||
udp dport @udp_ports accept
|
||||
|
||||
# Rules of your own go here, e.g. a port for one network only:
|
||||
# ip saddr 192.168.1.0/24 tcp dport 5432 accept
|
||||
}
|
||||
}
|
||||
|
||||
# dfw's ports and drop-ins of your own
|
||||
include "/etc/nftables.d/*.nft"
|
||||
@@ -0,0 +1,4 @@
|
||||
# Das OS server defaults, see sshd_config(5). sshd keeps the first value it
|
||||
# reads, so a file sorted before this one (40-local.conf) overrides it.
|
||||
PermitRootLogin prohibit-password
|
||||
X11Forwarding no
|
||||
@@ -0,0 +1,5 @@
|
||||
# Firmware a server does not need, otherwise pulled in by linux-base:
|
||||
# NVIDIA GPUs and sound cards. Remove a line to get the package back.
|
||||
ignorepkg=linux-firmware-nvidia
|
||||
ignorepkg=sof-firmware
|
||||
ignorepkg=alsa-firmware
|
||||
@@ -0,0 +1,27 @@
|
||||
# Template file for 'das-server'
|
||||
pkgname=das-server
|
||||
version=0.5.0
|
||||
revision=1
|
||||
depends="openssh chrony dhcpcd NetworkManager docker docker-compose nftables
|
||||
fail2ban socklog-void python3 curl lazydocker lazygit mc tmux htop ncdu nano
|
||||
das-snapshots das-update das-helpers das-shell xtools-minimal"
|
||||
short_desc="Das OS server: packages and secure defaults for SSH, firewall and Docker"
|
||||
maintainer="Das <anry.das@gmail.com>"
|
||||
license="MIT"
|
||||
homepage="https://git.das.org.ua/das/Das-OS"
|
||||
conf_files="
|
||||
/etc/nftables.conf
|
||||
/etc/fail2ban/jail.d/das.conf
|
||||
/etc/ssh/sshd_config.d/50-das.conf
|
||||
/etc/docker/daemon.json
|
||||
/etc/xbps.d/das-server.conf"
|
||||
|
||||
do_install() {
|
||||
vinstall ${FILESDIR}/nftables.conf 644 etc
|
||||
vmkdir etc/nftables.d
|
||||
vinstall ${FILESDIR}/fail2ban-das.conf 644 etc/fail2ban/jail.d das.conf
|
||||
vinstall ${FILESDIR}/sshd-das.conf 644 etc/ssh/sshd_config.d 50-das.conf
|
||||
vinstall ${FILESDIR}/docker-daemon.json 644 etc/docker daemon.json
|
||||
vinstall ${FILESDIR}/xbps-ignore.conf 644 etc/xbps.d das-server.conf
|
||||
vlicense ${FILESDIR}/LICENSE
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# Picks the icon of the menu button on the panel: das-panel-icon star|trident|<icon name>.
|
||||
set -u
|
||||
|
||||
usage() {
|
||||
echo "usage: das-panel-icon star|trident|ICON-NAME" >&2
|
||||
echo "now: $(xfconf-query -c xfce4-panel -p /plugins/plugin-1/button-icon 2>/dev/null || echo das-logo)" >&2
|
||||
}
|
||||
|
||||
case ${1:-} in
|
||||
star) icon=das-logo ;;
|
||||
trident) icon=das-trident ;;
|
||||
help | -h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
"")
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
-*)
|
||||
echo "das-panel-icon: unknown option $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
*) icon=$1 ;;
|
||||
esac
|
||||
|
||||
prop=/plugins/plugin-1/button-icon
|
||||
xfconf-query -c xfce4-panel -p "$prop" -s "$icon" 2>/dev/null ||
|
||||
xfconf-query -c xfce4-panel -p "$prop" -n -t string -s "$icon" || exit 1
|
||||
# whiskermenu keeps its own copy in the rc file, which wins over xfconf
|
||||
rc="${XDG_CONFIG_HOME:-$HOME/.config}/xfce4/panel/whiskermenu-1.rc"
|
||||
[[ -f $rc ]] && sed -i "s|^button-icon=.*|button-icon=$icon|" "$rc"
|
||||
xfce4-panel -r >/dev/null 2>&1 &
|
||||
@@ -4,6 +4,21 @@
|
||||
# which is only known inside the session. Runs once per user.
|
||||
set -u
|
||||
|
||||
# Sets an xfconf property, creating it when it does not exist yet.
|
||||
set_prop() {
|
||||
local channel=$1 prop=$2 type=$3 value=$4
|
||||
xfconf-query -c "$channel" -p "$prop" -s "$value" 2>/dev/null ||
|
||||
xfconf-query -c "$channel" -p "$prop" -n -t "$type" -s "$value"
|
||||
}
|
||||
|
||||
# The live session must not lock the screen: the installer runs for minutes
|
||||
# without input, and the live user's password is not something people know.
|
||||
# Static files would reach the installed system, which copies the live image.
|
||||
if grep -q '\(^\| \)root=live:' /proc/cmdline; then
|
||||
set_prop xfce4-screensaver /lock/enabled bool false
|
||||
set_prop xfce4-power-manager /xfce4-power-manager/lock-screen-suspend-hibernate bool false
|
||||
fi
|
||||
|
||||
marker="${XDG_CONFIG_HOME:-$HOME/.config}/das/session-init.done"
|
||||
[[ -e $marker ]] && exit 0
|
||||
|
||||
@@ -12,13 +27,6 @@ if [[ $(xfconf-query -c xsettings -p /Net/ThemeName 2>/dev/null) == *-Dark ]]; t
|
||||
wallpaper=/usr/share/backgrounds/das/das-dark.svg
|
||||
fi
|
||||
|
||||
# Sets an xfconf property, creating it when it does not exist yet.
|
||||
set_prop() {
|
||||
local prop=$1 type=$2 value=$3
|
||||
xfconf-query -c xfce4-desktop -p "$prop" -s "$value" 2>/dev/null ||
|
||||
xfconf-query -c xfce4-desktop -p "$prop" -n -t "$type" -s "$value"
|
||||
}
|
||||
|
||||
# "xrandr --listactivemonitors" prints a header, then one line per monitor ending in its connector name.
|
||||
monitors=$(xrandr --listactivemonitors 2>/dev/null | awk 'NR > 1 { print $NF }')
|
||||
if [[ -z $monitors ]]; then
|
||||
@@ -28,8 +36,8 @@ fi
|
||||
|
||||
for monitor in $monitors; do
|
||||
base="/backdrop/screen0/monitor${monitor}/workspace0"
|
||||
set_prop "$base/last-image" string "$wallpaper"
|
||||
set_prop "$base/image-style" int 5 # zoomed: fill the screen, keep proportions
|
||||
set_prop xfce4-desktop "$base/last-image" string "$wallpaper"
|
||||
set_prop xfce4-desktop "$base/image-style" int 5 # zoomed: fill the screen, keep proportions
|
||||
done
|
||||
|
||||
mkdir -p "${marker%/*}" && touch "$marker"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
[greeter]
|
||||
background=/usr/share/backgrounds/das/das-light.svg
|
||||
theme-name=DasOS
|
||||
icon-theme-name=Papirus
|
||||
icon-theme-name=Papirus-Light
|
||||
font-name=Inter 11
|
||||
cursor-theme-name=Adwaita
|
||||
cursor-theme-size=24
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Sourced by /etc/sv/lightdm/run (installed by das-settings).
|
||||
# runit starts services with an empty environment, so pass the system language
|
||||
# from /etc/locale.conf to LightDM: the greeter and new sessions use it.
|
||||
if [ -r /etc/locale.conf ]; then
|
||||
set -a
|
||||
. /etc/locale.conf
|
||||
set +a
|
||||
fi
|
||||
@@ -3,7 +3,7 @@
|
||||
<channel name="xsettings" version="1.0">
|
||||
<property name="Net" type="empty">
|
||||
<property name="ThemeName" type="string" value="DasOS"/>
|
||||
<property name="IconThemeName" type="string" value="Papirus"/>
|
||||
<property name="IconThemeName" type="string" value="Papirus-Light"/>
|
||||
</property>
|
||||
<property name="Xft" type="empty">
|
||||
<property name="Antialias" type="int" value="1"/>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Sourced by LightDM's session wrapper before the Xfce session starts.
|
||||
#
|
||||
# xdg-user-dirs 0.20 loads its translations only from the first XDG_DATA_DIRS
|
||||
# entry with a locale directory, the empty /usr/local/share/locale, and so
|
||||
# creates Desktop, Documents... in English whatever the language. Create the
|
||||
# folders here with a lookup path that finds the translations; the later run
|
||||
# from the Xfce session then keeps them.
|
||||
if command -v xdg-user-dirs-update >/dev/null 2>&1; then
|
||||
XDG_DATA_DIRS=/usr/share xdg-user-dirs-update
|
||||
fi
|
||||
@@ -4,7 +4,7 @@
|
||||
# Defaults go to /etc/xdg/das, which /etc/profile.d/das-xdg.sh puts in front of
|
||||
# /etc/xdg: stock Xfce files stay untouched and users' own changes still win.
|
||||
pkgname=das-settings
|
||||
version=0.1.0
|
||||
version=0.6.0
|
||||
revision=1
|
||||
create_wrksrc=yes
|
||||
depends="das-theme lightdm lightdm-gtk-greeter xfce4-notifyd xfce4-panel
|
||||
@@ -14,7 +14,7 @@ short_desc="Das OS desktop defaults: Xfce panel, theme, fonts, login screen"
|
||||
maintainer="Das <anry.das@gmail.com>"
|
||||
license="MIT"
|
||||
homepage="https://git.das.org.ua/das/Das-OS"
|
||||
conf_files="/etc/profile.d/das-xdg.sh /etc/xdg/das/xfce4/xfconf/xfce-perchannel-xml/*.xml"
|
||||
conf_files="/etc/profile.d/das-xdg.sh /etc/sv/lightdm/conf /etc/xdg/das/xfce4/xfconf/xfce-perchannel-xml/*.xml"
|
||||
|
||||
do_install() {
|
||||
local xml
|
||||
@@ -25,9 +25,12 @@ do_install() {
|
||||
done
|
||||
|
||||
vinstall "${FILESDIR}/bin/das-session-init" 755 usr/libexec/das
|
||||
vinstall "${FILESDIR}/bin/das-panel-icon" 755 usr/bin
|
||||
vinstall "${FILESDIR}/autostart/das-session-init.desktop" 644 etc/xdg/autostart
|
||||
vinstall "${FILESDIR}/xinitrc.d/50-das-user-dirs" 755 etc/X11/xinit/xinitrc.d
|
||||
|
||||
vinstall "${FILESDIR}/lightdm/50-das.conf" 644 usr/share/lightdm/lightdm.conf.d
|
||||
vinstall "${FILESDIR}/lightdm/sv-conf" 644 etc/sv/lightdm conf
|
||||
vinstall "${FILESDIR}/lightdm/50-das-greeter.conf" 644 \
|
||||
usr/share/lightdm/lightdm-gtk-greeter.conf.d 50-das.conf
|
||||
vlicense "${FILESDIR}/LICENSE"
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Das OS aliases and functions (lsfunc lists the functions), then the user's
|
||||
# own ~/.bash_aliases and ~/.bash_functions, which win. Off for one user:
|
||||
# touch ~/.config/das/no-aliases (the user's own files are still read).
|
||||
# shellcheck shell=bash
|
||||
|
||||
if [[ $- == *i* ]]; then
|
||||
if [[ ! -e ${XDG_CONFIG_HOME:-$HOME/.config}/das/no-aliases ]]; then
|
||||
for __das_f in /usr/share/das/shell/aliases.sh /usr/share/das/shell/functions.sh; do
|
||||
# shellcheck source=/dev/null
|
||||
[[ -r $__das_f ]] && . "$__das_f"
|
||||
done
|
||||
fi
|
||||
for __das_f in ~/.bash_aliases ~/.bash_functions; do
|
||||
# shellcheck source=/dev/null
|
||||
[[ -r $__das_f ]] && . "$__das_f"
|
||||
done
|
||||
unset __das_f
|
||||
fi
|
||||
@@ -0,0 +1,14 @@
|
||||
# Das OS bash prompt: ~/.config/das/prompt.sh, else /etc/das/prompt.sh (both
|
||||
# written by dprompt), else the stock one.
|
||||
# shellcheck shell=bash
|
||||
|
||||
if [[ $- == *i* && -r /usr/lib/das/prompt-lib.sh ]]; then
|
||||
. /usr/lib/das/prompt-lib.sh
|
||||
for __das_f in "${XDG_CONFIG_HOME:-$HOME/.config}/das/prompt.sh" /etc/das/prompt.sh /usr/share/das/prompt/default.sh; do
|
||||
if [[ -r $__das_f ]]; then
|
||||
. "$__das_f"
|
||||
break
|
||||
fi
|
||||
done
|
||||
unset __das_f
|
||||
fi
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/python3
|
||||
"""dbanner of Das OS; the code is in /usr/lib/das/python/das_shell."""
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "/usr/lib/das/python")
|
||||
|
||||
from das_shell.cli_banner import main # noqa: E402
|
||||
|
||||
sys.exit(main())
|
||||