1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-04 21:02:06 -04:00

feat(Greeter): Add install/uninstall/activate cli commands & new UI opts

- AppArmor profile management
- Introduced `dms greeter uninstall` command to remove DMS greeter configuration and restore previous display manager.
- Implemented AppArmor profile installation and uninstallation for enhanced security.
This commit is contained in:
purian23
2026-03-07 20:44:19 -05:00
committed by bbedward
parent 7156e1e299
commit 31b415b086
7 changed files with 865 additions and 52 deletions

View File

@@ -0,0 +1,91 @@
# AppArmor profile for dms-greeter
#
# Managed by DMS — regenerated on every `dms greeter install` / `dms greeter sync`.
# Manual edits will be overwritten on next sync.
#
# Mode: complain (denials are logged, nothing is blocked)
# To switch to enforce after validating with `aa-logprof`:
# sudo aa-enforce /etc/apparmor.d/usr.bin.dms-greeter
#
#include <tunables/global>
profile dms-greeter /usr/bin/dms-greeter flags=(complain) {
#include <abstractions/base>
#include <abstractions/bash>
# The launcher script itself
/usr/bin/dms-greeter r,
# Cache directory — created by dms greeter sync/enable with greeter:greeter ownership
/var/cache/dms-greeter/ rw,
/var/cache/dms-greeter/** rwlk,
# DMS config — packaged path
/usr/share/quickshell/dms-greeter/ r,
/usr/share/quickshell/dms-greeter/** r,
/usr/share/quickshell/ r,
/usr/share/quickshell/** r,
# DMS config — system and user overrides
/etc/dms/ r,
/etc/dms/** r,
/usr/share/dms/ r,
/usr/share/dms/** r,
/home/*/.config/quickshell/ r,
/home/*/.config/quickshell/** r,
/root/.config/quickshell/ r,
/root/.config/quickshell/** r,
# greetd / PAM — read-only for session setup
/etc/greetd/ r,
/etc/greetd/** r,
/etc/pam.d/ r,
/etc/pam.d/** r,
/usr/lib/pam.d/ r,
/usr/lib/pam.d/** r,
# Compositor binaries — run unconfined so each compositor uses its own profile
/usr/bin/niri Ux,
/usr/bin/hyprland Ux,
/usr/bin/Hyprland Ux,
/usr/bin/sway Ux,
/usr/bin/labwc Ux,
/usr/bin/scroll Ux,
/usr/bin/miracle-wm Ux,
/usr/bin/mango Ux,
# Quickshell — run unconfined (has its own compositor profile on some distros)
/usr/bin/qs Ux,
/usr/bin/quickshell Ux,
# Wayland / XDG runtime (pipewire, wireplumber, wayland socket)
/run/user/[0-9]*/ rw,
/run/user/[0-9]*/** rw,
# DRM / GPU devices (required for Wayland compositor startup)
/dev/dri/ r,
/dev/dri/* rw,
/dev/udmabuf rw,
# Input devices
/dev/input/ r,
/dev/input/* r,
# Systemd journal / logging
/run/systemd/journal/socket rw,
/dev/log rw,
# Shell helper binaries invoked by the launcher script
/usr/bin/env ix,
/usr/bin/mkdir ix,
/usr/bin/cat ix,
/usr/bin/grep ix,
/usr/bin/dirname ix,
/usr/bin/basename ix,
/usr/bin/command ix,
/bin/env ix,
/bin/mkdir ix,
# Signal management (compositor lifecycle)
signal (send, receive) set=("term", "int", "hup", "kill"),
}

View File

@@ -3,6 +3,7 @@ package greeter
import (
"bufio"
"context"
_ "embed"
"encoding/json"
"fmt"
"os"
@@ -18,6 +19,10 @@ import (
"github.com/sblinch/kdl-go/document"
)
var appArmorProfileData []byte
const appArmorProfileDest = "/etc/apparmor.d/usr.bin.dms-greeter"
const (
GreeterCacheDir = "/var/cache/dms-greeter"
@@ -527,7 +532,6 @@ func CopyGreeterFiles(dmsPath, compositor string, logFunc func(string), sudoPass
return fmt.Errorf("failed to make wrapper executable: %w", err)
}
// Set SELinux context on Fedora and openSUSE
osInfo, err := distros.GetOSInfo()
if err == nil {
if config, exists := distros.Registry[osInfo.Distribution.ID]; exists && (config.Family == distros.FamilyFedora || config.Family == distros.FamilySUSE) {
@@ -579,6 +583,134 @@ func EnsureGreeterCacheDir(logFunc func(string), sudoPassword string) error {
return nil
}
// InstallAppArmorProfile writes the bundled AppArmor profile for dms-greeter and reloads
// it with apparmor_parser. It is safe to call multiple times (idempotent reload).
//
// Skipped silently when:
// - AppArmor kernel module is absent (/sys/module/apparmor does not exist)
// - Running on NixOS (profiles are managed via security.apparmor.policies)
// - SELinux is active (/sys/fs/selinux/enforce exists and equals "1") — Fedora/RHEL
func InstallAppArmorProfile(logFunc func(string), sudoPassword string) error {
if IsNixOS() {
logFunc(" Skipping AppArmor profile on NixOS (manage via security.apparmor.policies)")
return nil
}
if _, err := os.Stat("/sys/module/apparmor"); os.IsNotExist(err) {
return nil
}
if data, err := os.ReadFile("/sys/fs/selinux/enforce"); err == nil {
if strings.TrimSpace(string(data)) == "1" {
return nil
}
}
if err := runSudoCmd(sudoPassword, "mkdir", "-p", "/etc/apparmor.d"); err != nil {
return fmt.Errorf("failed to create /etc/apparmor.d: %w", err)
}
tmp, err := os.CreateTemp("", "dms-apparmor-*")
if err != nil {
return fmt.Errorf("failed to create temp file for AppArmor profile: %w", err)
}
tmpPath := tmp.Name()
defer os.Remove(tmpPath)
if _, err := tmp.Write(appArmorProfileData); err != nil {
tmp.Close()
return fmt.Errorf("failed to write AppArmor profile: %w", err)
}
tmp.Close()
if err := runSudoCmd(sudoPassword, "cp", tmpPath, appArmorProfileDest); err != nil {
return fmt.Errorf("failed to install AppArmor profile to %s: %w", appArmorProfileDest, err)
}
if err := runSudoCmd(sudoPassword, "chmod", "644", appArmorProfileDest); err != nil {
return fmt.Errorf("failed to set AppArmor profile permissions: %w", err)
}
if utils.CommandExists("apparmor_parser") {
if err := runSudoCmd(sudoPassword, "apparmor_parser", "-r", appArmorProfileDest); err != nil {
logFunc(fmt.Sprintf(" ⚠ AppArmor profile installed but reload failed: %v", err))
logFunc(" Run: sudo apparmor_parser -r " + appArmorProfileDest)
} else {
logFunc(" ✓ AppArmor profile installed and loaded (complain mode)")
}
} else {
logFunc(" ✓ AppArmor profile installed at " + appArmorProfileDest)
logFunc(" apparmor_parser not found — profile will be loaded on next boot")
}
return nil
}
// RemoveGreeterPamManagedBlock strips the DMS managed auth block from /etc/pam.d/greetd
func RemoveGreeterPamManagedBlock(logFunc func(string), sudoPassword string) error {
if IsNixOS() {
return nil
}
const greetdPamPath = "/etc/pam.d/greetd"
data, err := os.ReadFile(greetdPamPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("failed to read %s: %w", greetdPamPath, err)
}
stripped, removed := stripManagedGreeterPamBlock(string(data))
strippedAgain, removedLegacy := stripLegacyGreeterPamLines(stripped)
if !removed && !removedLegacy {
return nil
}
tmp, err := os.CreateTemp("", "dms-pam-greetd-*")
if err != nil {
return fmt.Errorf("failed to create temp PAM file: %w", err)
}
tmpPath := tmp.Name()
defer os.Remove(tmpPath)
if _, err := tmp.WriteString(strippedAgain); err != nil {
tmp.Close()
return fmt.Errorf("failed to write temp PAM file: %w", err)
}
tmp.Close()
if err := runSudoCmd(sudoPassword, "cp", tmpPath, greetdPamPath); err != nil {
return fmt.Errorf("failed to write PAM config: %w", err)
}
if err := runSudoCmd(sudoPassword, "chmod", "644", greetdPamPath); err != nil {
return fmt.Errorf("failed to set PAM config permissions: %w", err)
}
logFunc(" ✓ Removed DMS managed PAM block from " + greetdPamPath)
return nil
}
// UninstallAppArmorProfile removes the DMS AppArmor profile and reloads AppArmor.
// It is a no-op when AppArmor is not active or the profile does not exist.
func UninstallAppArmorProfile(logFunc func(string), sudoPassword string) error {
if IsNixOS() {
return nil
}
if _, err := os.Stat("/sys/module/apparmor"); os.IsNotExist(err) {
return nil
}
if _, err := os.Stat(appArmorProfileDest); os.IsNotExist(err) {
return nil
}
if utils.CommandExists("apparmor_parser") {
_ = runSudoCmd(sudoPassword, "apparmor_parser", "--remove", appArmorProfileDest)
}
if err := runSudoCmd(sudoPassword, "rm", "-f", appArmorProfileDest); err != nil {
return fmt.Errorf("failed to remove AppArmor profile: %w", err)
}
logFunc(" ✓ Removed DMS AppArmor profile")
return nil
}
// EnsureACLInstalled installs the acl package (setfacl/getfacl) if not already present
func EnsureACLInstalled(logFunc func(string), sudoPassword string) error {
if utils.CommandExists("setfacl") {
@@ -661,7 +793,6 @@ func SetupParentDirectoryACLs(logFunc func(string), sudoPassword string) error {
return nil
}
if !utils.CommandExists("setfacl") {
// setfacl still not found after install attempt (e.g. unsupported filesystem)
logFunc("⚠ Warning: setfacl still not available after install attempt; skipping ACL setup.")
return nil
}
@@ -723,7 +854,6 @@ func SetupDMSGroup(logFunc func(string), sudoPassword string) error {
group := DetectGreeterGroup()
// Check if user is already in greeter group
groupsCmd := exec.Command("groups", currentUser)
groupsOutput, err := groupsCmd.Output()
if err == nil && strings.Contains(string(groupsOutput), group) {
@@ -1273,7 +1403,6 @@ func ensureGreetdNiriConfig(logFunc func(string), sudoPassword string, niriConfi
if !strings.Contains(command, "--command niri") {
continue
}
// Strip existing -C or --config and their arguments
command = stripConfigFlag(command)
command = stripCacheDirFlag(command)
command = strings.TrimSpace(command + " --cache-dir " + GreeterCacheDir)