Compare commits
6
Commits
eba4516998
...
v0.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3793d468c | ||
|
|
dbfb104d89 | ||
|
|
125f9027c4 | ||
|
|
fe043711a0 | ||
|
|
acb7532a4f | ||
|
|
84768962fc |
@@ -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"
|
||||
@@ -40,5 +40,5 @@ test:
|
||||
|
||||
# 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,7 +1,7 @@
|
||||
## Das OS
|
||||
### _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://www.docker.com) [](#)
|
||||
[](https://voidlinux.org) [](https://xfce.org) [](https://docs.voidlinux.org/config/services/) [](https://go.dev) [](https://www.docker.com) [](#)
|
||||
|
||||
🇺🇦 [Українська версія](README_ua.md)
|
||||
|
||||
@@ -13,13 +13,15 @@ Void Linux is fast, simple and independent: runit instead of systemd, and its ow
|
||||
- [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 and help
|
||||
- [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
|
||||
- [Service manager](#Services) `dsv`: enable, start and read the logs of runit services, like systemctl
|
||||
- [Command-line helpers](#Helpers) for everyday admin tasks: services (`dsv`), users (`duser`), firewall (`dfw`) and system settings (`dsys`)
|
||||
- [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.
|
||||
@@ -119,6 +121,7 @@ Void Linux is fast, simple and independent: runit instead of systemd, and its ow
|
||||
- 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.
|
||||
|
||||
@@ -129,17 +132,28 @@ Void Linux is fast, simple and independent: runit instead of systemd, and its ow
|
||||
- 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`). 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.
|
||||
- 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, Python 3, curl, `das-update --cli` and `dsv`. There are no graphical libraries.
|
||||
- Tools: Docker with Compose, lazydocker, lazygit, Midnight Commander, tmux, htop, Python 3, curl, `das-update --cli`, the [helpers](#Helpers) 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).
|
||||
|
||||
#### Service manager<a id='Services' />
|
||||
**_`dsv` manages runit services the way systemctl does on systemd distributions._**
|
||||
- `dsv list` shows every service in `/etc/sv`: whether it is enabled, its state, PID and uptime.
|
||||
- `dsv enable NAME` starts a service at boot (a link in the default runlevel) and starts it now. `dsv disable NAME` does the opposite.
|
||||
- `dsv start|stop|restart|status NAME` wrap `sv` and print the new state.
|
||||
- `dsv logs NAME [-f]` shows the service's own svlogd log or, for services that log to syslog, their lines collected by socklog.
|
||||
- It runs itself through sudo when it needs root and has bash completion. It is on the desktop too.
|
||||
#### 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.
|
||||
|
||||
#### 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._**
|
||||
@@ -163,9 +177,10 @@ Void Linux is fast, simple and independent: runit instead of systemd, and its ow
|
||||
- `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/dsv` contains the service manager.
|
||||
- `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/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._**
|
||||
@@ -173,6 +188,14 @@ Void Linux is fast, simple and independent: runit instead of systemd, and its ow
|
||||
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).
|
||||
|
||||
#### 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.24+ with the GTK 3 development files for `make test`. About 10 GB of free disk space.
|
||||
```shell
|
||||
@@ -185,6 +208,6 @@ make clean # remove build output, keep the download cache
|
||||
```
|
||||
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.
|
||||
|
||||
**Note:** Das OS is an early version (0.4.0). The developer profile, smaller ISOs and signed repository hosting are planned for upcoming stages. 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.
|
||||
**Note:** Das OS is an early version (0.8.0). 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:=-_
|
||||
|
||||
+37
-14
@@ -1,7 +1,7 @@
|
||||
## Das OS
|
||||
### _Легкий і дружній десктоп на базі Void Linux з Xfce та українським колоритом, а також консольна серверна версія_
|
||||
|
||||
[](https://voidlinux.org) [](https://xfce.org) [](https://docs.voidlinux.org/config/services/) [](https://go.dev) [](https://www.docker.com) [](#)
|
||||
[](https://voidlinux.org) [](https://xfce.org) [](https://docs.voidlinux.org/config/services/) [](https://go.dev) [](https://www.docker.com) [](#)
|
||||
|
||||
🇬🇧 [English version](README.md)
|
||||
|
||||
@@ -13,13 +13,15 @@ Void Linux швидкий, простий і незалежний: runit зам
|
||||
- [Графічний інсталятор](#Installer) Calamares, налаштований під Void і runit
|
||||
- [Знімки й відкат](#Snapshots): щогодинні знімки Btrfs, які можна завантажити з меню GRUB
|
||||
- [Оновлення системи](#Update) зі знімком перед ним: вікно, значок у треї та консольний режим
|
||||
- [Вікно привітання](#Welcome) під час першого входу: мова, теки, оновлення та довідка
|
||||
- [Вікно привітання](#Welcome) під час першого входу: мова, теки, оновлення, профіль розробника та довідка
|
||||
- [Серверний образ](#Server): консольний live ISO з текстовим інсталятором, SSH, файрволом, fail2ban і Docker
|
||||
- [Керування службами](#Services) `dsv`: вмикати, запускати й читати журнали служб runit, як systemctl
|
||||
- [Консольні помічники](#Helpers) для щоденного адміністрування: служби (`dsv`), користувачі (`duser`), брандмауер (`dfw`) і системні налаштування (`dsys`)
|
||||
- [Профіль розробника](#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 і свіжі програми без ручного налаштування.
|
||||
@@ -119,6 +121,7 @@ Void Linux швидкий, простий і незалежний: runit зам
|
||||
- У live-сесії пропонує встановити Das OS.
|
||||
- Мову інтерфейсу можна вибрати, якщо в системі зібрано більше однієї локалі. Вона зберігається в AccountsService і `~/.dmrc` і діє з наступного входу.
|
||||
- Якщо теки домашнього каталогу лишилися з англійськими назвами (`Documents`, `Downloads`…), пропонує перейменувати їх мовою системи разом із `user-dirs.dirs` і закладками файлового менеджера. Теки, які користувач переніс, і ті, чия нова назва вже зайнята, не чіпає.
|
||||
- Встановлює [профіль розробника](#DevProfile) у вікні термінала, де питає про середовища й пароль, а коли профіль встановлено, додає середовища з Flathub.
|
||||
- Містить посилання на оновлення системи, налаштування Xfce і довідку.
|
||||
- Сам відкривається лише один раз. Прапорець *Показувати під час входу* залишає його на кожен вхід.
|
||||
|
||||
@@ -129,17 +132,28 @@ Void Linux швидкий, простий і незалежний: runit зам
|
||||
- Розмітка 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`). Правила лежать у власній таблиці `inet das`, тож їх перезавантаження не чіпає правил Docker і fail2ban. Порти, які публікують контейнери, проходять через власні правила Docker.
|
||||
- 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, Python 3, curl, `das-update --cli` і `dsv`. Графічних бібліотек немає.
|
||||
- Інструменти: Docker з Compose, lazydocker, lazygit, Midnight Commander, tmux, htop, Python 3, curl, `das-update --cli`, [помічники](#Helpers) і xtools (`xi`, `xq`, `xrs`). Графічних бібліотек немає, як і прошивок для відеокарт NVIDIA та звукових карт (`/etc/xbps.d/das-server.conf` не дає оновленням повернути їх).
|
||||
|
||||
#### Керування службами<a id='Services' />
|
||||
**_`dsv` керує службами runit так, як systemctl у дистрибутивах із systemd._**
|
||||
- `dsv list` показує всі служби з `/etc/sv`: чи ввімкнена, стан, PID і час роботи.
|
||||
- `dsv enable НАЗВА` запускає службу під час завантаження (посилання в типовому рівні запуску) і одразу запускає її. `dsv disable НАЗВА` робить навпаки.
|
||||
- `dsv start|stop|restart|status НАЗВА` — обгортки над `sv`, які показують новий стан.
|
||||
- `dsv logs НАЗВА [-f]` показує власний журнал служби (svlogd) або, для служб, що пишуть у syslog, їхні рядки, зібрані socklog.
|
||||
- Сам перезапускається через sudo, коли потрібні права root, і має автодоповнення bash. Є й на десктопі.
|
||||
#### Консольні помічники<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` знаходить програми, які треба перезапустити після оновлення.
|
||||
|
||||
#### Профіль розробника<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' />
|
||||
**_Сучасний мінімалізм із синьо-жовтою восьмипелюстковою зіркою з української вишивки._**
|
||||
@@ -163,9 +177,10 @@ Void Linux швидкий, простий і незалежний: runit зам
|
||||
- `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/dsv` містить керування службами.
|
||||
- `srcpkgs/das-server` — серверний метапакет із налаштуваннями nftables, fail2ban, sshd і Docker. `srcpkgs/das-helpers` містить `dsv`, `duser`, `dfw`, `dsys`, `dprofile` і профілі.
|
||||
- `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._**
|
||||
@@ -173,6 +188,14 @@ Void Linux швидкий, простий і незалежний: runit зам
|
||||
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).
|
||||
|
||||
#### Збирання в 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.24+ і файли розробки GTK 3 для `make test`. Приблизно 10 ГБ вільного місця.
|
||||
```shell
|
||||
@@ -185,6 +208,6 @@ make clean # видалити результати збиранн
|
||||
```
|
||||
ISO з'являються в `out/iso/dasos-<версія>-x86_64.iso` і `out/iso/dasos-server-<версія>-x86_64.iso`. Записати їх на флешку можна через `dd` або Ventoy.
|
||||
|
||||
**Примітка:** Das OS — рання версія (0.4.0). Профіль розробника, менші ISO та підписаний репозиторій на хостингу заплановані на наступні етапи. Поки репозиторій не розміщено на хостингу, встановлена система отримує оновлення лише з Void. Das OS базується на Void Linux, але не пов'язана з ним офіційно.
|
||||
**Примітка:** Das OS — рання версія (0.8.0). Пакети й ISO збираються в CI; підписаний репозиторій на хостингу заплановано на наступний етап. Поки репозиторій не розміщено на хостингу, встановлена система отримує оновлення лише з Void. Das OS базується на Void Linux, але не пов'язана з ним офіційно.
|
||||
|
||||
###### _Made by -=:dAs:=-_
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Das OS — що зробити далі
|
||||
|
||||
Стан на 2026-10-19: версія 0.4.0, етапи 1–6 готові (live ISO, власний репозиторій, оформлення, інсталятор Calamares зі знімками Btrfs, Go-утиліти `das-update` і `das-welcome`, серверний образ з консольним інсталятором). Далі: рішення щодо розміру ISO, потім профіль розробника (етап 7).
|
||||
Стан на 2026-10-20: версія 0.8.0, етапи 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, підказка про відкат.
|
||||
@@ -12,12 +12,12 @@
|
||||
- [x] Live ISO 0.4.0: екран не блокується під час встановлення.
|
||||
- [ ] Вікно автентифікації polkit (`xfce-polkit`) англійською: перекласти або замінити агент.
|
||||
- [ ] Повідомити авторів xdg-user-dirs про помилку з `XDG_DATA_DIRS`.
|
||||
- [ ] Пункт «Профіль розробника» в `das-welcome` (разом з етапом 7).
|
||||
- [x] Пункт «Профіль розробника» в `das-welcome` (етап 8).
|
||||
|
||||
## Етап 6. Серверний образ (готово, лишився розмір ISO)
|
||||
## Етап 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, dsv.
|
||||
- [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`).
|
||||
@@ -25,25 +25,49 @@ Live ISO без графіки з консольним інсталятором:
|
||||
- [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`.
|
||||
- [ ] Перевірити `das-server-install --config` (код той самий, що й у TUI, окремо не запускався).
|
||||
- [ ] Розмір ISO: огляд зроблено (нижче), чекає рішення, що саме зменшувати.
|
||||
- [x] `das-server-install --config` перевірено (0.5.0, UEFI).
|
||||
- [x] Розмір ISO: див. нижче.
|
||||
|
||||
### Розмір ISO (0.4.0): десктоп 1,96 ГБ, сервер 1,57 ГБ
|
||||
З чого складається: `initrd` 233 МБ і squashfs 1584 / 1215 МБ (xz). Прошивки в Void уже стиснуті zstd, тож xz їх майже не зменшує.
|
||||
- [ ] `initrd` live-образу: 157 МБ у ньому — прошивки GPU (NVIDIA 109 МБ, amdgpu 28 МБ) для раннього KMS. Без драйверів `nouveau`/`amdgpu`/`radeon`/`i915`/`xe` у live-initrd: ≈ −150 МБ в обох ISO. Драйвери все одно завантажаться з кореневої системи.
|
||||
- [ ] Сервер: без `linux-firmware-nvidia`, `sof-firmware`, `alsa-firmware` (GPU і звук): ≈ −160 МБ. Через `mklive -g` і `ignorepkg` у `das-server`, щоб оновлення не повертали їх.
|
||||
### Розмір 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 МБ на десктопі. Мінус: інші мови інтерфейсу доведеться доставляти перевстановленням пакетів.
|
||||
- [ ] squashfs з блоком 1 МБ і фільтром BCJ x86 замість 128 КБ: ≈ −5 %.
|
||||
- [ ] Десктоп: зайві варіанти Papirus (`ePapirus*`, `Papirus-Light`) з 294 МБ іконок; з'ясувати, хто тягне gtk4, flite, ghostscript.
|
||||
- Не чіпати: Firefox (299 МБ), ядро й модулі (180 МБ), LLVM для Mesa (141 МБ), мережеві прошивки (442 МБ, Wi-Fi та мережеві карти).
|
||||
|
||||
## Етап 7. Профіль розробника
|
||||
- [ ] Метапакет `das-dev`: go, git, docker, podman, vscode.
|
||||
- [ ] Android Studio та JetBrains на вимогу через Flatpak.
|
||||
- [ ] Вибір профілю в інсталяторі (модуль packagechooser) або в `das-welcome`.
|
||||
## Етап 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. CI
|
||||
- [ ] Збирання пакетів і обох ISO (десктоп і сервер) в контейнері Void (Gitea Actions на git.das.org.ua або GitHub Actions).
|
||||
## Етап 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` у `D:\DasOS\wsl`, 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`).
|
||||
- [ ] Перевірити першу публікацію на справжньому тегу.
|
||||
|
||||
## Репозиторій пакетів
|
||||
- [ ] Підпис через `xbps-rindex --sign`, ключ поза репозиторієм.
|
||||
@@ -66,7 +90,7 @@ Live ISO без графіки з консольним інсталятором:
|
||||
- [ ] Прибрати значки томів live-носія («Том 6,6 ГБ») з робочого столу live-сесії.
|
||||
- [ ] Тема GRUB у встановленій системі (зараз стандартний чорний екран).
|
||||
- [ ] Перевірити меню Whisker вручну.
|
||||
- [ ] Прискорити `make packages`: не перезбирати Calamares, якщо його версія не змінилася.
|
||||
- [x] Прискорити `make packages`: пакет перезбирається лише після змін у `srcpkgs/<назва>`, без бінарного пакета або коли у Void зникла потрібна йому бібліотека (`xbps-install -n`). `DASOS_REBUILD=1` перезбирає все.
|
||||
|
||||
## Відкладено
|
||||
- [ ] ARM-образ для Raspberry Pi.
|
||||
|
||||
Executable
+41
@@ -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
|
||||
@@ -66,4 +66,5 @@ das-settings
|
||||
das-snapshots
|
||||
das-theme
|
||||
das-tools
|
||||
dsv
|
||||
das-helpers
|
||||
xtools-minimal
|
||||
|
||||
+6
-2
@@ -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" "$@"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
profile=${1:-desktop}
|
||||
ignore=""
|
||||
repo=/work/out/hostdir/binpkgs
|
||||
version=$(< /work/VERSION)
|
||||
|
||||
@@ -25,6 +26,9 @@ case $profile in
|
||||
# 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
|
||||
@@ -81,6 +85,22 @@ sed -i -e 's/echo void-live >/echo dasos >/' -e 's/voidlinux/live/g' "$adduser"
|
||||
# how to log in to the live system; its installer writes a neutral one).
|
||||
cp "$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"
|
||||
|
||||
rsvg-convert /work/live/branding/splash.svg --output "$work/splash.png"
|
||||
rm -f "$iso"
|
||||
|
||||
@@ -91,6 +111,7 @@ SPLASH_IMAGE=$work/splash.png ./mklive.sh \
|
||||
-c /var/cache/xbps \
|
||||
-p "$(read_list "$dir/packages.x86_64")" \
|
||||
-S "$(read_list "$dir/services")" \
|
||||
${ignore:+-g "$ignore"} \
|
||||
-I "$include" \
|
||||
-l "$locale" \
|
||||
-k us \
|
||||
|
||||
@@ -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")
|
||||
@@ -59,9 +83,16 @@ fi
|
||||
# (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
|
||||
|
||||
Executable
+240
@@ -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
|
||||
Executable
+416
@@ -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
|
||||
@@ -7,6 +7,9 @@
|
||||
# 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
|
||||
@@ -14,22 +17,6 @@ service_dir=/var/service
|
||||
[[ -d $service_dir ]] || service_dir=$runlevel
|
||||
socklog=/var/log/socklog
|
||||
|
||||
if [[ ${LANG:-} == uk* || ${LC_ALL:-} == uk* || ${LC_MESSAGES:-} == uk* ]]; then
|
||||
uk=1
|
||||
else
|
||||
uk=""
|
||||
fi
|
||||
|
||||
# say UKRAINIAN ENGLISH: prints the message in the user's language
|
||||
say() {
|
||||
if [[ -n $uk ]]; then echo "$1"; else echo "$2"; fi
|
||||
}
|
||||
|
||||
die() {
|
||||
say "dsv: $1" "dsv: $2" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
if [[ -n $uk ]]; then
|
||||
cat <<'EOF'
|
||||
@@ -42,6 +29,16 @@ usage() {
|
||||
start|stop|restart СЛУЖБА...
|
||||
запустити, зупинити, перезапустити
|
||||
logs СЛУЖБА [-f] журнал служби (-f: стежити за новими рядками)
|
||||
new [СЛУЖБА] [ПАРАМЕТРИ]
|
||||
створити службу для своєї програми; чого не
|
||||
вказано, те запитає:
|
||||
--cmd КОМАНДА що запускати (програма не має йти у фон)
|
||||
--user КОРИСТУВАЧ від чийого імені
|
||||
--dir КАТАЛОГ робочий каталог
|
||||
--env ІМ'Я=ЗНАЧЕННЯ змінна середовища, можна кілька разів
|
||||
--no-log без власного журналу в /var/log/СЛУЖБА
|
||||
--enable одразу ввімкнути й запустити
|
||||
rm СЛУЖБА вимкнути й видалити службу, створену через new
|
||||
|
||||
Служби — каталоги в /etc/sv. Без команди виконується list.
|
||||
EOF
|
||||
@@ -56,21 +53,23 @@ Usage: dsv COMMAND [SERVICE...]
|
||||
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
|
||||
}
|
||||
|
||||
# Service states live in root-only supervise directories
|
||||
need_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
exec sudo "$0" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
check_service() {
|
||||
[[ -d $sv_dir/$1 ]] || die "немає служби '$1' (див. ls $sv_dir)" "no service '$1' (see ls $sv_dir)"
|
||||
[[ -d $sv_dir/$1 ]] || die "немає служби '$1' (див. dsv list)" "no service '$1' (see dsv list)"
|
||||
}
|
||||
|
||||
is_enabled() {
|
||||
@@ -87,14 +86,12 @@ wait_supervised() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# row NAME ENABLED STATE PID UPTIME: one line of the list. printf pads by
|
||||
# bytes, ${#s} counts characters, so Cyrillic columns stay aligned.
|
||||
# row NAME ENABLED STATE PID UPTIME: one line of the list
|
||||
row() {
|
||||
local s widths=(24 9 6 8) i=0
|
||||
for s in "$1" "$2" "$3" "$4"; do
|
||||
printf '%s%*s ' "$s" $((widths[i] - ${#s})) ''
|
||||
i=$((i + 1))
|
||||
done
|
||||
pad "$1" 24
|
||||
pad "$2" 9
|
||||
pad "$3" 6
|
||||
pad "$4" 8
|
||||
printf '%s\n' "$5"
|
||||
}
|
||||
|
||||
@@ -240,6 +237,151 @@ cmd_logs() {
|
||||
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
|
||||
|
||||
@@ -269,6 +411,14 @@ case $cmd in
|
||||
[[ -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
|
||||
;;
|
||||
+11
-2
@@ -3,7 +3,7 @@
|
||||
_dsv() {
|
||||
local cur=${COMP_WORDS[COMP_CWORD]} cmd=${COMP_WORDS[1]:-}
|
||||
if ((COMP_CWORD == 1)); then
|
||||
mapfile -t COMPREPLY < <(compgen -W "list status enable disable start stop restart logs help" -- "$cur")
|
||||
mapfile -t COMPREPLY < <(compgen -W "list status enable disable start stop restart logs new rm help" -- "$cur")
|
||||
return
|
||||
fi
|
||||
case $cmd in
|
||||
@@ -20,7 +20,16 @@ _dsv() {
|
||||
disable | start | stop | restart)
|
||||
mapfile -t COMPREPLY < <(compgen -W "$(ls /etc/runit/runsvdir/default 2>/dev/null)" -- "$cur")
|
||||
;;
|
||||
status | logs | log)
|
||||
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
|
||||
Executable
+247
@@ -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
|
||||
Executable
+396
@@ -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
|
||||
}
|
||||
@@ -23,6 +23,9 @@ 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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Template file for 'das-install-common'
|
||||
pkgname=das-install-common
|
||||
version=0.1.0
|
||||
version=0.2.0
|
||||
revision=1
|
||||
depends="das-snapshots dracut rsync"
|
||||
short_desc="Das OS installer steps shared by the desktop and server installers"
|
||||
|
||||
@@ -66,7 +66,7 @@ t() {
|
||||
s_config) echo "Налаштування системи..." ;;
|
||||
s_post) echo "Локалі, знімки, initramfs (кілька хвилин)..." ;;
|
||||
s_boot) echo "Встановлення завантажувача..." ;;
|
||||
finished) echo "Das OS Server встановлено. Вийміть інсталяційний носій і перезавантажте сервер.\n\nВхід: %s, далі sudo. Служби: dsv list. Оновлення: das-update." ;;
|
||||
finished) echo "Das OS Server встановлено. Вийміть інсталяційний носій і перезавантажте сервер.\n\nВхід: %s, далі sudo. Служби: dsv. Користувачі: duser. Порти: dfw. Налаштування: dsys. Оновлення: das-update. Профіль розробника: dprofile install dev." ;;
|
||||
reboot) echo "Перезавантажити зараз?" ;;
|
||||
failed) echo "Помилка встановлення. Журнал: %s" ;;
|
||||
root) echo "Інсталятор треба запускати від root (sudo das-server-install)." ;;
|
||||
@@ -105,7 +105,7 @@ t() {
|
||||
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 list. Updates: das-update." ;;
|
||||
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." ;;
|
||||
reboot) echo "Reboot now?" ;;
|
||||
failed) echo "Installation failed. Log: %s" ;;
|
||||
root) echo "The installer must run as root (sudo das-server-install)." ;;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Template file for 'das-server-installer'
|
||||
pkgname=das-server-installer
|
||||
version=0.1.0
|
||||
version=0.3.0
|
||||
revision=1
|
||||
depends="bash dialog das-install-common btrfs-progs dosfstools gptfdisk parted
|
||||
rsync util-linux grub grub-x86_64-efi efibootmgr"
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
#!/usr/sbin/nft -f
|
||||
# Das OS server firewall: incoming connections are closed except SSH.
|
||||
#
|
||||
# Only the "das" table is replaced, so loading this file again
|
||||
# (dsv restart nftables) keeps the rules of Docker and fail2ban.
|
||||
# Ports that Docker containers publish (-p 8080:80) pass through Docker's
|
||||
# own forwarding rules, not the input chain below.
|
||||
# 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;
|
||||
|
||||
@@ -24,8 +35,13 @@ table inet das {
|
||||
iifname "br-*" accept
|
||||
|
||||
tcp dport ssh accept
|
||||
tcp dport @tcp_ports accept
|
||||
udp dport @udp_ports accept
|
||||
|
||||
# Open more ports here, then: dsv restart nftables
|
||||
# tcp dport { http, https } 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,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
|
||||
@@ -1,10 +1,10 @@
|
||||
# Template file for 'das-server'
|
||||
pkgname=das-server
|
||||
version=0.1.0
|
||||
version=0.2.0
|
||||
revision=1
|
||||
depends="openssh chrony dhcpcd NetworkManager docker docker-compose nftables
|
||||
fail2ban socklog-void python3 curl lazydocker lazygit mc tmux htop
|
||||
das-snapshots das-update dsv"
|
||||
das-snapshots das-update das-helpers xtools-minimal"
|
||||
short_desc="Das OS server: packages and secure defaults for SSH, firewall and Docker"
|
||||
maintainer="Das <anry.das@gmail.com>"
|
||||
license="MIT"
|
||||
@@ -13,12 +13,15 @@ conf_files="
|
||||
/etc/nftables.conf
|
||||
/etc/fail2ban/jail.d/das.conf
|
||||
/etc/ssh/sshd_config.d/50-das.conf
|
||||
/etc/docker/daemon.json"
|
||||
/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
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Command das-welcome is the Das OS first-login window: interface language,
|
||||
// home folder names, updates, settings and help.
|
||||
// home folder names, updates, the developer profile, settings and help.
|
||||
//
|
||||
// das-welcome show the window
|
||||
// das-welcome --autostart show it only if the user did not turn that off
|
||||
@@ -207,6 +207,9 @@ func newWindow(app *gtk.Application) (*gtk.ApplicationWindow, error) {
|
||||
row, _ := b.row("software-update-available", i18n.T("welcome.update"), i18n.T("welcome.update.desc"),
|
||||
b.button(i18n.T("welcome.update.open"), func() { launch("das-update") }))
|
||||
add(row)
|
||||
if row := profileRow(b); row != nil {
|
||||
add(row)
|
||||
}
|
||||
}
|
||||
row, _ := b.row("preferences-desktop", i18n.T("welcome.settings"), i18n.T("welcome.settings.desc"),
|
||||
b.button(i18n.T("welcome.open"), func() { launch("xfce4-settings-manager") }))
|
||||
@@ -249,6 +252,23 @@ func launch(name string, args ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// profileRow installs the developer profile in a terminal, where dprofile
|
||||
// asks for the password and which IDEs to add. It returns nil without dprofile.
|
||||
func profileRow(b *builder) *gtk.Box {
|
||||
if _, err := exec.LookPath("dprofile"); err != nil {
|
||||
return nil
|
||||
}
|
||||
desc, label := i18n.T("welcome.dev.desc"), i18n.T("welcome.dev.install")
|
||||
if exec.Command("dprofile", "check", "dev").Run() == nil {
|
||||
desc, label = i18n.T("welcome.dev.installed"), i18n.T("welcome.dev.apps")
|
||||
}
|
||||
row, _ := b.row("applications-development", i18n.T("welcome.dev"), desc,
|
||||
b.button(label, func() {
|
||||
launch("xfce4-terminal", "--hold", "--title", i18n.T("welcome.dev"), "-x", "dprofile", "install", "dev")
|
||||
}))
|
||||
return row
|
||||
}
|
||||
|
||||
// languageRow offers the UTF-8 locales the system has built. It returns nil
|
||||
// when there is nothing to choose from.
|
||||
func languageRow(b *builder) *gtk.Box {
|
||||
|
||||
@@ -111,6 +111,11 @@ var messages = map[string]message{
|
||||
"welcome.update": {"Оновлення", "Updates"},
|
||||
"welcome.update.desc": {"Перед кожним оновленням створюється знімок системи для відкату.", "A system snapshot is taken before every update so it can be rolled back."},
|
||||
"welcome.update.open": {"Перевірити", "Check"},
|
||||
"welcome.dev": {"Профіль розробника", "Developer profile"},
|
||||
"welcome.dev.desc": {"Go, Python, Node.js, Git, Docker і Podman, VS Code; за бажанням JetBrains і Android Studio з Flathub.", "Go, Python, Node.js, Git, Docker and Podman, VS Code; JetBrains IDEs and Android Studio from Flathub if you like."},
|
||||
"welcome.dev.installed": {"Встановлено. Середовища JetBrains і Android Studio можна додати з Flathub.", "Installed. JetBrains IDEs and Android Studio can be added from Flathub."},
|
||||
"welcome.dev.install": {"Встановити", "Install"},
|
||||
"welcome.dev.apps": {"Додати середовища", "Add IDEs"},
|
||||
"welcome.settings": {"Налаштування", "Settings"},
|
||||
"welcome.settings.desc": {"Тема, екран, клавіатура, принтери та інше.", "Theme, display, keyboard, printers and more."},
|
||||
"welcome.open": {"Відкрити", "Open"},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Template file for 'das-tools'
|
||||
# In-tree package: Go sources are in files/src (FILESDIR).
|
||||
pkgname=das-tools
|
||||
version=0.3.0
|
||||
version=0.4.0
|
||||
revision=1
|
||||
create_wrksrc=yes
|
||||
# Like build_style=go: the Go linker decides about PIE, xbps-src must not check it
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Template file for 'dsv'
|
||||
pkgname=dsv
|
||||
version=0.1.0
|
||||
revision=1
|
||||
depends="bash runit sudo"
|
||||
short_desc="Das OS service manager: enable, start and read logs of runit services"
|
||||
maintainer="Das <anry.das@gmail.com>"
|
||||
license="MIT"
|
||||
homepage="https://git.das.org.ua/das/Das-OS"
|
||||
|
||||
do_install() {
|
||||
vbin ${FILESDIR}/dsv
|
||||
vcompletion ${FILESDIR}/dsv.bash-completion bash
|
||||
vlicense ${FILESDIR}/LICENSE
|
||||
}
|
||||
Reference in New Issue
Block a user