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:
91
core/internal/greeter/assets/apparmor/usr.bin.dms-greeter
Normal file
91
core/internal/greeter/assets/apparmor/usr.bin.dms-greeter
Normal 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"),
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user