mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-21 02:25:21 -04:00
distros(void linux): initial packaging support
This commit is contained in:
@@ -117,3 +117,9 @@ quickshell/dms-plugins
|
||||
__pycache__
|
||||
|
||||
.vscode/
|
||||
|
||||
# Void (xbps) build artifacts
|
||||
*.xbps
|
||||
distro/void/temp/
|
||||
distro/void/hostdir/
|
||||
distro/void/masterdir*/
|
||||
|
||||
@@ -299,7 +299,7 @@ func installGreeter(nonInteractive bool) error {
|
||||
|
||||
fmt.Println("\n=== Installation Complete ===")
|
||||
fmt.Println("\nTo start the greeter now, run:")
|
||||
fmt.Println(" sudo systemctl start greetd")
|
||||
fmt.Println(startGreeterHint())
|
||||
fmt.Println("\nOr reboot to see the greeter at next boot.")
|
||||
|
||||
return nil
|
||||
@@ -326,7 +326,13 @@ func uninstallGreeter(nonInteractive bool) error {
|
||||
}
|
||||
|
||||
fmt.Println("\nDisabling greetd...")
|
||||
if err := privesc.Run(context.Background(), "", "systemctl", "disable", "greetd"); err != nil {
|
||||
if isRunit() {
|
||||
if err := disableRunitService("greetd"); err != nil {
|
||||
fmt.Printf(" ⚠ Could not disable greetd: %v\n", err)
|
||||
} else {
|
||||
fmt.Println(" ✓ greetd disabled")
|
||||
}
|
||||
} else if err := privesc.Run(context.Background(), "", "systemctl", "disable", "greetd"); err != nil {
|
||||
fmt.Printf(" ⚠ Could not disable greetd: %v\n", err)
|
||||
} else {
|
||||
fmt.Println(" ✓ greetd disabled")
|
||||
@@ -449,6 +455,14 @@ func suggestDisplayManagerRestore(nonInteractive bool) {
|
||||
|
||||
enableDM := func(dm string) {
|
||||
fmt.Printf(" Enabling %s...\n", dm)
|
||||
if isRunit() {
|
||||
if err := enableRunitService(dm); err != nil {
|
||||
fmt.Printf(" ⚠ Failed to enable %s: %v\n", dm, err)
|
||||
} else {
|
||||
fmt.Printf(" ✓ %s enabled (linked into %s).\n", dm, runitServiceDir)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := privesc.Run(context.Background(), "", "systemctl", "enable", "--force", dm); err != nil {
|
||||
fmt.Printf(" ⚠ Failed to enable %s: %v\n", dm, err)
|
||||
} else {
|
||||
@@ -495,6 +509,9 @@ func suggestDisplayManagerRestore(nonInteractive bool) {
|
||||
}
|
||||
|
||||
func isSystemdUnitInstalled(unit string) bool {
|
||||
if isRunit() {
|
||||
return runitServiceInstalled(unit)
|
||||
}
|
||||
cmd := exec.Command("systemctl", "list-unit-files", unit+".service", "--no-legend", "--no-pager")
|
||||
out, err := cmd.Output()
|
||||
return err == nil && strings.Contains(string(out), unit)
|
||||
@@ -943,6 +960,18 @@ func resolveLocalDMSPath() (string, error) {
|
||||
}
|
||||
|
||||
func disableDisplayManager(dmName string) (bool, error) {
|
||||
if isRunit() {
|
||||
if !runitServiceEnabled(dmName) {
|
||||
return false, nil
|
||||
}
|
||||
fmt.Printf("\nDisabling %s (runit)...\n", dmName)
|
||||
if err := disableRunitService(dmName); err != nil {
|
||||
return false, fmt.Errorf("failed to disable %s: %w", dmName, err)
|
||||
}
|
||||
fmt.Printf(" ✓ %s disabled (removed from %s)\n", dmName, runitServiceDir)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
state, err := getSystemdServiceState(dmName)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to check %s state: %w", dmName, err)
|
||||
@@ -996,6 +1025,21 @@ func disableDisplayManager(dmName string) (bool, error) {
|
||||
}
|
||||
|
||||
func ensureGreetdEnabled() error {
|
||||
if isRunit() {
|
||||
fmt.Println("\nEnabling greetd service (runit)...")
|
||||
if !runitServiceInstalled("greetd") {
|
||||
return fmt.Errorf("greetd service not found in %s. Please install greetd first", runitSvDir)
|
||||
}
|
||||
// Seat + runtime-dir setup that logind handles automatically on systemd.
|
||||
ensureRunitSeat("_greeter")
|
||||
ensureGreetdPamRundir()
|
||||
if err := enableRunitService("greetd"); err != nil {
|
||||
return fmt.Errorf("failed to enable greetd: %w", err)
|
||||
}
|
||||
fmt.Printf(" ✓ greetd enabled (%s)\n", runitServiceDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("\nChecking greetd service status...")
|
||||
|
||||
state, err := getSystemdServiceState("greetd")
|
||||
@@ -1043,6 +1087,12 @@ func ensureGreetdEnabled() error {
|
||||
}
|
||||
|
||||
func ensureGraphicalTarget() error {
|
||||
if isRunit() {
|
||||
// runit has no targets; a supervised greetd service is the graphical
|
||||
// login, so there is nothing to set here.
|
||||
return nil
|
||||
}
|
||||
|
||||
getDefaultCmd := exec.Command("systemctl", "get-default")
|
||||
currentTarget, err := getDefaultCmd.Output()
|
||||
if err != nil {
|
||||
@@ -1176,7 +1226,7 @@ func enableGreeter(nonInteractive bool) error {
|
||||
fmt.Println("\n=== Enable Complete ===")
|
||||
fmt.Println("\nGreeter configuration verified and system state corrected.")
|
||||
fmt.Println("To start the greeter now, run:")
|
||||
fmt.Println(" sudo systemctl start greetd")
|
||||
fmt.Println(startGreeterHint())
|
||||
fmt.Println("\nOr reboot to see the greeter at boot time.")
|
||||
|
||||
return nil
|
||||
@@ -1257,7 +1307,7 @@ func enableGreeter(nonInteractive bool) error {
|
||||
|
||||
fmt.Println("\n=== Enable Complete ===")
|
||||
fmt.Println("\nTo start the greeter now, run:")
|
||||
fmt.Println(" sudo systemctl start greetd")
|
||||
fmt.Println(startGreeterHint())
|
||||
fmt.Println("\nOr reboot to see the greeter at boot time.")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||
)
|
||||
|
||||
// runit (Void Linux) service helpers. Services live in /etc/sv and are "enabled"
|
||||
// by symlinking them into the /var/service supervision dir, so the greeter
|
||||
// commands branch on isRunit() instead of shelling systemctl.
|
||||
|
||||
const (
|
||||
runitSvDir = "/etc/sv"
|
||||
runitServiceDir = "/var/service"
|
||||
)
|
||||
|
||||
// isRunit reports whether this system is supervised by runit (Void Linux).
|
||||
func isRunit() bool {
|
||||
if fi, err := os.Stat("/run/runit"); err == nil && fi.IsDir() {
|
||||
return true
|
||||
}
|
||||
if _, err := os.Stat("/run/systemd/system"); err == nil {
|
||||
return false
|
||||
}
|
||||
if fi, err := os.Stat(runitServiceDir); err == nil && fi.IsDir() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func runitServiceInstalled(name string) bool {
|
||||
fi, err := os.Stat(runitSvDir + "/" + name)
|
||||
return err == nil && fi.IsDir()
|
||||
}
|
||||
|
||||
func runitServiceEnabled(name string) bool {
|
||||
_, err := os.Lstat(runitServiceDir + "/" + name)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// enableRunitService links a service into /var/service (idempotent).
|
||||
func enableRunitService(name string) error {
|
||||
if !runitServiceInstalled(name) {
|
||||
return fmt.Errorf("runit service %q not found in %s", name, runitSvDir)
|
||||
}
|
||||
if runitServiceEnabled(name) {
|
||||
return nil
|
||||
}
|
||||
return privesc.Run(context.Background(), "", "ln", "-sf",
|
||||
runitSvDir+"/"+name, runitServiceDir+"/"+name)
|
||||
}
|
||||
|
||||
// disableRunitService removes a service's supervision symlink.
|
||||
func disableRunitService(name string) error {
|
||||
if !runitServiceEnabled(name) {
|
||||
return nil
|
||||
}
|
||||
return privesc.Run(context.Background(), "", "rm", "-f",
|
||||
runitServiceDir+"/"+name)
|
||||
}
|
||||
|
||||
// ensureRunitSeat sets up the seat access a Wayland greeter needs on runit (the
|
||||
// equivalent of logind on systemd): enables seatd and adds the greeter user to
|
||||
// the seat/video/input groups. Failures are reported but non-fatal.
|
||||
func ensureRunitSeat(greeterUser string) {
|
||||
if runitServiceInstalled("seatd") {
|
||||
if err := enableRunitService("seatd"); err != nil {
|
||||
fmt.Printf(" ⚠ could not enable seatd: %v\n", err)
|
||||
} else {
|
||||
fmt.Println(" ✓ seatd enabled")
|
||||
}
|
||||
} else {
|
||||
fmt.Println(" ⚠ seatd not installed — the greeter compositor needs it for GPU/seat access")
|
||||
}
|
||||
if err := privesc.Run(context.Background(), "", "usermod", "-aG", "_seatd,video,input", greeterUser); err != nil {
|
||||
fmt.Printf(" ⚠ could not add %s to seat groups: %v\n", greeterUser, err)
|
||||
} else {
|
||||
fmt.Printf(" ✓ %s added to seat groups (_seatd, video, input)\n", greeterUser)
|
||||
}
|
||||
}
|
||||
|
||||
// ensureGreetdPamRundir adds pam_rundir to the greetd PAM stack so the post-login
|
||||
// session gets an XDG_RUNTIME_DIR on systems without logind (Void with seatd).
|
||||
// Appended outside DMS's managed auth block so it survives `dms greeter sync`.
|
||||
func ensureGreetdPamRundir() {
|
||||
const pamPath = "/etc/pam.d/greetd"
|
||||
data, err := os.ReadFile(pamPath)
|
||||
if err != nil {
|
||||
fmt.Printf(" ⚠ could not read %s: %v\n", pamPath, err)
|
||||
return
|
||||
}
|
||||
if strings.Contains(string(data), "pam_rundir") {
|
||||
return
|
||||
}
|
||||
line := "session optional pam_rundir.so"
|
||||
if err := privesc.Run(context.Background(), "", "sh", "-c",
|
||||
fmt.Sprintf("printf '%%s\\n' %q >> %s", line, pamPath)); err != nil {
|
||||
fmt.Printf(" ⚠ could not add pam_rundir to %s: %v\n", pamPath, err)
|
||||
return
|
||||
}
|
||||
fmt.Println(" ✓ pam_rundir added to greetd PAM (provides XDG_RUNTIME_DIR for the session)")
|
||||
}
|
||||
|
||||
// startGreeterHint returns the init-appropriate "start greetd now" command.
|
||||
func startGreeterHint() string {
|
||||
if isRunit() {
|
||||
return " sudo sv up greetd"
|
||||
}
|
||||
return " sudo systemctl start greetd"
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
# Void Linux packaging
|
||||
|
||||
XBPS templates for DankMaterialShell on [Void Linux](https://voidlinux.org).
|
||||
|
||||
| Package | Source repo | Template |
|
||||
| --- | --- | --- |
|
||||
| `dms` | DankMaterialShell | [`srcpkgs/dms/template`](srcpkgs/dms/template) |
|
||||
| `dms-greeter` (optional) | DankMaterialShell | [`srcpkgs/dms-greeter/template`](srcpkgs/dms-greeter/template) |
|
||||
| `dgop` | AvengeMedia/dgop | maintained in the **danklinux** repo (`distro/void/srcpkgs/dgop`) |
|
||||
| `danksearch` | AvengeMedia/danksearch | maintained in the **danklinux** repo (`distro/void/srcpkgs/danksearch`) |
|
||||
|
||||
All build from source.
|
||||
|
||||
## Distribution
|
||||
|
||||
These packages target the official
|
||||
[`void-linux/void-packages`](https://github.com/void-linux/void-packages)
|
||||
repository, so they install with a plain `xbps-install dms` and no extra setup.
|
||||
Most dependencies (`quickshell`, `matugen`, `cava`, `niri`, `greetd`, …) are
|
||||
already in Void; `dgop` and `danksearch` are packaged alongside in the
|
||||
[danklinux repo](https://github.com/AvengeMedia/danklinux/tree/master/distro/void).
|
||||
|
||||
The templates here are the source of truth: copy each into a void-packages
|
||||
checkout at `srcpkgs/<pkg>/template` to build or submit it. Only tagged releases
|
||||
are packaged (no `-git`/nightly variant).
|
||||
|
||||
## Dependencies
|
||||
|
||||
Installing `dms` automatically pulls in `quickshell`, `accountsservice`, `dgop`,
|
||||
and `matugen` (which drives the Material You theming). The rest are optional —
|
||||
install whichever features you want:
|
||||
|
||||
| Package | Enables |
|
||||
| --- | --- |
|
||||
| `danksearch` | launcher / filesystem search |
|
||||
| `cava` | audio visualiser widget |
|
||||
| `qt6-multimedia` | system sound feedback |
|
||||
| `qt6ct` | Qt app theming |
|
||||
| `wtype` | virtual keyboard input |
|
||||
| `power-profiles-daemon` | power profile control |
|
||||
| `cups-pk-helper` | printer management |
|
||||
| `NetworkManager` | network control |
|
||||
| `i2c-tools` | external-monitor brightness (DDC) |
|
||||
| `niri` / `hyprland` / `sway` | a Wayland compositor (niri is the team's choice) |
|
||||
|
||||
## Building & testing
|
||||
|
||||
Inside a `void-packages` checkout (symlink or copy these `srcpkgs/<pkg>` dirs in):
|
||||
|
||||
```sh
|
||||
# build the dependency packages first (dms requires dgop)
|
||||
./xbps-src pkg dgop
|
||||
./xbps-src pkg danksearch
|
||||
./xbps-src pkg dms
|
||||
./xbps-src pkg dms-greeter # optional
|
||||
|
||||
# lint (xlint ships in the xtools package)
|
||||
xlint srcpkgs/dms/template
|
||||
|
||||
# install the built packages
|
||||
sudo xbps-install --repository=hostdir/binpkgs dms dgop
|
||||
```
|
||||
|
||||
`dms` requires Go ≥ 1.26 in the build environment (per `core/go.mod`).
|
||||
|
||||
## Running the shell
|
||||
|
||||
DMS is a user-level Wayland shell with **no system service** — start it from your
|
||||
compositor's autostart, e.g. niri:
|
||||
|
||||
```kdl
|
||||
spawn-at-startup "dms" "run"
|
||||
```
|
||||
|
||||
or Hyprland: `exec-once = dms run`.
|
||||
|
||||
## Greeter (optional)
|
||||
|
||||
Install `dms-greeter`, then let the CLI do the setup:
|
||||
|
||||
```sh
|
||||
dms greeter enable # configures greetd + the Void seat/PAM bits below
|
||||
dms greeter sync # optional: share theming with the shell
|
||||
```
|
||||
|
||||
`dms greeter enable` handles what logind does automatically on systemd: it points
|
||||
greetd at the greeter, enables `seatd`, adds `_greeter` to the `_seatd`/`video`/
|
||||
`input` groups, and adds `pam_rundir` to `/etc/pam.d/greetd` (so the post-login
|
||||
session gets an `XDG_RUNTIME_DIR`). A Wayland compositor and a working DRM device
|
||||
(`/dev/dri/card*`) are required and not pulled in automatically.
|
||||
@@ -0,0 +1,15 @@
|
||||
dms-greeter installed.
|
||||
|
||||
Configure and enable it with:
|
||||
|
||||
dms greeter enable
|
||||
|
||||
This points greetd at the greeter and sets up everything Void needs that logind
|
||||
would handle on systemd: enables seatd, adds the greeter user to the seat/video/
|
||||
input groups, and adds pam_rundir to the greetd PAM stack. Optionally sync your
|
||||
shell theme into the greeter with:
|
||||
|
||||
dms greeter sync
|
||||
|
||||
Requirements not pulled in automatically: a Wayland compositor (niri, hyprland,
|
||||
sway, …) and a working DRM device (/dev/dri/card*; in a VM, enable virtio-gpu).
|
||||
@@ -0,0 +1,35 @@
|
||||
# Template file for 'dms-greeter'
|
||||
#
|
||||
# greetd greeter for DankMaterialShell
|
||||
# Builds from the same DMS release tarball as 'dms'; keep version/checksum in sync.
|
||||
# Setup is done by `dms greeter enable`, not by this package — see distro/void/README.md.
|
||||
pkgname=dms-greeter
|
||||
version=1.4.6
|
||||
revision=1
|
||||
short_desc="DankMaterialShell greeter for greetd"
|
||||
maintainer="AvengeMedia <AvengeMedia.US@gmail.com>"
|
||||
license="MIT"
|
||||
homepage="https://danklinux.com"
|
||||
distfiles="https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${version}.tar.gz"
|
||||
checksum=f54601e522c883fa9cce02bec070e4321e47389a1cf453e7ad0bb7379ad91b61
|
||||
|
||||
depends="greetd quickshell acl-progs seatd pam_rundir"
|
||||
|
||||
# Cache dir the greeter uses as $HOME (owned by greetd's _greeter user).
|
||||
make_dirs="/var/cache/dms-greeter 0750 _greeter _greeter"
|
||||
|
||||
do_install() {
|
||||
# Launcher wrapper -> /usr/bin/dms-greeter
|
||||
vbin quickshell/Modules/Greetd/assets/dms-greeter
|
||||
|
||||
# Same QML tree as the shell; greeter mode is selected at runtime via DMS_RUN_GREETER.
|
||||
vmkdir usr/share/quickshell/dms-greeter
|
||||
vcopy "quickshell/*" usr/share/quickshell/dms-greeter
|
||||
|
||||
# Sample compositor configs for reference
|
||||
vinstall quickshell/Modules/Greetd/assets/dms-niri.kdl 644 usr/share/dms-greeter
|
||||
vinstall quickshell/Modules/Greetd/assets/dms-hypr.conf 644 usr/share/dms-greeter
|
||||
|
||||
vdoc quickshell/Modules/Greetd/README.md
|
||||
vlicense LICENSE
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# Template file for 'dms'
|
||||
#
|
||||
# DankMaterialShell stable release
|
||||
#
|
||||
# NOTE: the binary is built with the `distro_binary` build tag, which is the
|
||||
# packaged variant upstream ships (it drops the in-app self-update command).
|
||||
pkgname=dms
|
||||
version=1.4.6
|
||||
revision=1
|
||||
build_style=go
|
||||
build_wrksrc="core"
|
||||
go_import_path="github.com/AvengeMedia/DankMaterialShell/core"
|
||||
go_package="${go_import_path}/cmd/dms"
|
||||
go_build_tags="distro_binary"
|
||||
go_ldflags="-X main.Version=${version}"
|
||||
short_desc="DankMaterialShell — Material 3 desktop shell for Wayland"
|
||||
maintainer="AvengeMedia <AvengeMedia.US@gmail.com>"
|
||||
license="MIT"
|
||||
homepage="https://danklinux.com"
|
||||
changelog="https://github.com/AvengeMedia/DankMaterialShell/releases"
|
||||
distfiles="https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${version}.tar.gz"
|
||||
checksum=f54601e522c883fa9cce02bec070e4321e47389a1cf453e7ad0bb7379ad91b61
|
||||
|
||||
# Optional feature deps (XBPS has no "recommends") are listed in distro/void/README.md.
|
||||
depends="quickshell accountsservice dgop matugen"
|
||||
|
||||
post_install() {
|
||||
# QML shell tree (build_style=go already installed the dms binary)
|
||||
vmkdir usr/share/quickshell/dms
|
||||
vcopy "${wrksrc}/quickshell/*" usr/share/quickshell/dms
|
||||
echo "${version}" > "${DESTDIR}/usr/share/quickshell/dms/VERSION"
|
||||
|
||||
# Desktop entry + icon
|
||||
vinstall "${wrksrc}/assets/dms-open.desktop" 644 usr/share/applications
|
||||
vinstall "${wrksrc}/assets/danklogo.svg" 644 usr/share/icons/hicolor/scalable/apps
|
||||
|
||||
# Shell completions (generated by the built binary; skip when cross-building)
|
||||
vmkdir usr/share/bash-completion/completions
|
||||
vmkdir usr/share/zsh/site-functions
|
||||
vmkdir usr/share/fish/vendor_completions.d
|
||||
if [ -z "$CROSS_BUILD" ]; then
|
||||
"${DESTDIR}/usr/bin/dms" completion bash > "${DESTDIR}/usr/share/bash-completion/completions/dms"
|
||||
"${DESTDIR}/usr/bin/dms" completion zsh > "${DESTDIR}/usr/share/zsh/site-functions/_dms"
|
||||
"${DESTDIR}/usr/bin/dms" completion fish > "${DESTDIR}/usr/share/fish/vendor_completions.d/dms.fish"
|
||||
fi
|
||||
|
||||
vlicense "${wrksrc}/LICENSE"
|
||||
}
|
||||
@@ -227,6 +227,15 @@ export XDG_STATE_HOME="$CACHE_DIR/.local/state"
|
||||
export XDG_DATA_HOME="$CACHE_DIR/.local/share"
|
||||
export XDG_CACHE_HOME="$CACHE_DIR/.cache"
|
||||
|
||||
# Fallback runtime dir for systems without logind/pam_rundir (e.g. Void+seatd),
|
||||
# where Wayland compositors abort with "RuntimeDirNotSet". Guarded so logind
|
||||
# systems keep their own.
|
||||
if [[ -z "${XDG_RUNTIME_DIR:-}" || ! -d "${XDG_RUNTIME_DIR:-}" ]]; then
|
||||
export XDG_RUNTIME_DIR="$CACHE_DIR/run"
|
||||
mkdir -p "$XDG_RUNTIME_DIR"
|
||||
chmod 700 "$XDG_RUNTIME_DIR"
|
||||
fi
|
||||
|
||||
|
||||
# Keep greeter VT clean by default; callers can override via env or --debug.
|
||||
if [[ -z "${RUST_LOG:-}" ]]; then
|
||||
|
||||
Reference in New Issue
Block a user