Compare commits
13 Commits
58d3a1fbc7
...
c1bfe2cd74
Author | SHA1 | Date |
---|---|---|
Ophestra Umiker | c1bfe2cd74 | |
Ophestra Umiker | d813f8e44e | |
Ophestra Umiker | 0e5b85fd42 | |
Ophestra Umiker | cdc08817a7 | |
Ophestra Umiker | e5b3fa02f9 | |
Ophestra Umiker | 8e848366cd | |
Ophestra Umiker | 38ef2b4d0c | |
Ophestra Umiker | 357cc4ce4d | |
Ophestra Umiker | 3242ce3406 | |
Ophestra Umiker | 7450b0b0bb | |
Ophestra Umiker | 83af555c97 | |
Ophestra Umiker | 60e4846542 | |
Ophestra Umiker | 1906853382 |
32
README.md
32
README.md
|
@ -75,16 +75,32 @@ This adds the `environment.fortify` option:
|
||||||
chronos = {
|
chronos = {
|
||||||
launchers = {
|
launchers = {
|
||||||
weechat.method = "sudo";
|
weechat.method = "sudo";
|
||||||
claws-mail.pulse = false;
|
claws-mail.capability.pulse = false;
|
||||||
|
|
||||||
discord = {
|
discord = {
|
||||||
command = "vesktop --ozone-platform-hint=wayland";
|
command = "vesktop --ozone-platform-hint=wayland";
|
||||||
share = pkgs.vesktop;
|
share = pkgs.vesktop;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
chromium.dbus.config = {
|
||||||
|
talk = [
|
||||||
|
"org.freedesktop.DBus"
|
||||||
|
"org.freedesktop.portal.*"
|
||||||
|
"org.freedesktop.FileManager1"
|
||||||
|
"org.freedesktop.Notifications"
|
||||||
|
"org.freedesktop.ScreenSaver"
|
||||||
|
];
|
||||||
|
own = [
|
||||||
|
"org.chromium.Chromium"
|
||||||
|
"org.mpris.MediaPlayer2.chromium.*"
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
weechat
|
weechat
|
||||||
claws-mail
|
claws-mail
|
||||||
vesktop
|
vesktop
|
||||||
|
chromium
|
||||||
];
|
];
|
||||||
persistence.directories = [
|
persistence.directories = [
|
||||||
".config/weechat"
|
".config/weechat"
|
||||||
|
@ -125,7 +141,19 @@ This adds the `environment.fortify` option:
|
||||||
|
|
||||||
* `command`, the command to run as the target user. Defaults to launcher name.
|
* `command`, the command to run as the target user. Defaults to launcher name.
|
||||||
|
|
||||||
* `pulse`, whether to share the PulseAudio socket and cookie.
|
* `dbus.config`, D-Bus proxy custom configuration.
|
||||||
|
|
||||||
|
* `dbus.id`, D-Bus application id, has no effect if `dbus.config` is set.
|
||||||
|
|
||||||
|
* `dbus.mpris`, whether to enable MPRIS defaults, has no effect if `dbus.config` is set.
|
||||||
|
|
||||||
|
* `capability.wayland`, whether to share the Wayland socket.
|
||||||
|
|
||||||
|
* `capability.x11`, whether to share the X11 socket and allow connection.
|
||||||
|
|
||||||
|
* `capability.dbus`, whether to proxy D-Bus.
|
||||||
|
|
||||||
|
* `capability.pulse`, whether to share the PulseAudio socket and cookie.
|
||||||
|
|
||||||
* `share`, package containing desktop/icon files. Defaults to launcher name.
|
* `share`, package containing desktop/icon files. Defaults to launcher name.
|
||||||
|
|
||||||
|
|
23
cli.go
23
cli.go
|
@ -1,23 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
|
|
||||||
"git.ophivana.moe/cat/fortify/internal/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
userName string
|
|
||||||
printVersion bool
|
|
||||||
mustPulse bool
|
|
||||||
flagVerbose bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.StringVar(&userName, "u", "chronos", "Specify a username")
|
|
||||||
flag.BoolVar(&system.MethodFlags[0], "sudo", false, "Use 'sudo' to change user")
|
|
||||||
flag.BoolVar(&system.MethodFlags[1], "bare", false, "Use 'machinectl' but skip xdg-desktop-portal setup")
|
|
||||||
flag.BoolVar(&mustPulse, "pulse", false, "Treat unavailable PulseAudio as fatal")
|
|
||||||
flag.BoolVar(&flagVerbose, "v", false, "Verbose output")
|
|
||||||
flag.BoolVar(&printVersion, "V", false, "Print version")
|
|
||||||
}
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
See []string `json:"see"`
|
||||||
|
Talk []string `json:"talk"`
|
||||||
|
Own []string `json:"own"`
|
||||||
|
|
||||||
|
Log bool `json:"log,omitempty"`
|
||||||
|
Filter bool `json:"filter"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Args(address, path string) (args []string) {
|
||||||
|
argc := 2 + len(c.See) + len(c.Talk) + len(c.Own)
|
||||||
|
if c.Log {
|
||||||
|
argc++
|
||||||
|
}
|
||||||
|
if c.Filter {
|
||||||
|
argc++
|
||||||
|
}
|
||||||
|
|
||||||
|
args = make([]string, 0, argc)
|
||||||
|
args = append(args, address, path)
|
||||||
|
for _, name := range c.See {
|
||||||
|
args = append(args, "--see="+name)
|
||||||
|
}
|
||||||
|
for _, name := range c.Talk {
|
||||||
|
args = append(args, "--talk="+name)
|
||||||
|
}
|
||||||
|
for _, name := range c.Own {
|
||||||
|
args = append(args, "--own="+name)
|
||||||
|
}
|
||||||
|
if c.Log {
|
||||||
|
args = append(args, "--log")
|
||||||
|
}
|
||||||
|
if c.Filter {
|
||||||
|
args = append(args, "--filter")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfig returns a reference to a Config struct with optional defaults.
|
||||||
|
// If id is an empty string own defaults are omitted.
|
||||||
|
func NewConfig(id string, defaults, mpris bool) (c *Config) {
|
||||||
|
c = &Config{Filter: true}
|
||||||
|
|
||||||
|
if defaults {
|
||||||
|
c.Talk = []string{"org.freedesktop.DBus", "org.freedesktop.portal.*", "org.freedesktop.Notifications"}
|
||||||
|
|
||||||
|
if id != "" {
|
||||||
|
c.Own = []string{id}
|
||||||
|
if mpris {
|
||||||
|
c.Own = append(c.Own, "org.mpris.MediaPlayer2."+id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start launches the D-Bus proxy and sets up the Wait method.
|
||||||
|
// ready should be buffered and should only be received from once.
|
||||||
|
func (p *Proxy) Start(ready *chan bool) error {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
if p.seal == nil {
|
||||||
|
return errors.New("proxy not sealed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// acquire pipes
|
||||||
|
if pr, pw, err := os.Pipe(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
p.statP[0], p.statP[1] = pr, pw
|
||||||
|
}
|
||||||
|
if pr, pw, err := os.Pipe(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
p.argsP[0], p.argsP[1] = pr, pw
|
||||||
|
}
|
||||||
|
|
||||||
|
p.cmd = exec.Command(p.path,
|
||||||
|
// ExtraFiles: If non-nil, entry i becomes file descriptor 3+i.
|
||||||
|
"--fd=3",
|
||||||
|
"--args=4",
|
||||||
|
)
|
||||||
|
p.cmd.Env = []string{}
|
||||||
|
p.cmd.ExtraFiles = []*os.File{p.statP[1], p.argsP[0]}
|
||||||
|
p.cmd.Stdout = os.Stdout
|
||||||
|
p.cmd.Stderr = os.Stderr
|
||||||
|
if err := p.cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
statsP, argsP := p.statP[0], p.argsP[1]
|
||||||
|
|
||||||
|
if _, err := argsP.Write([]byte(*p.seal)); err != nil {
|
||||||
|
if err1 := p.cmd.Process.Kill(); err1 != nil {
|
||||||
|
panic(err1)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
if err = argsP.Close(); err != nil {
|
||||||
|
if err1 := p.cmd.Process.Kill(); err1 != nil {
|
||||||
|
panic(err1)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wait := make(chan error)
|
||||||
|
go func() {
|
||||||
|
// live out the lifespan of the process
|
||||||
|
wait <- p.cmd.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
read := make(chan error)
|
||||||
|
go func() {
|
||||||
|
n, err := statsP.Read(make([]byte, 1))
|
||||||
|
switch n {
|
||||||
|
case -1:
|
||||||
|
if err1 := p.cmd.Process.Kill(); err1 != nil {
|
||||||
|
panic(err1)
|
||||||
|
}
|
||||||
|
read <- err
|
||||||
|
case 0:
|
||||||
|
read <- err
|
||||||
|
case 1:
|
||||||
|
*ready <- true
|
||||||
|
read <- nil
|
||||||
|
default:
|
||||||
|
panic("unreachable") // unexpected read count
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
p.wait = &wait
|
||||||
|
p.read = &read
|
||||||
|
p.ready = ready
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait waits for xdg-dbus-proxy to exit or fault.
|
||||||
|
func (p *Proxy) Wait() error {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
|
||||||
|
if p.wait == nil || p.read == nil {
|
||||||
|
return errors.New("proxy not running")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err1 := p.statP[0].Close(); err1 != nil && !errors.Is(err1, os.ErrClosed) {
|
||||||
|
panic(err1)
|
||||||
|
}
|
||||||
|
if err1 := p.statP[1].Close(); err1 != nil && !errors.Is(err1, os.ErrClosed) {
|
||||||
|
panic(err1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err1 := p.argsP[0].Close(); err1 != nil && !errors.Is(err1, os.ErrClosed) {
|
||||||
|
panic(err1)
|
||||||
|
}
|
||||||
|
if err1 := p.argsP[1].Close(); err1 != nil && !errors.Is(err1, os.ErrClosed) {
|
||||||
|
panic(err1)
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-*p.wait:
|
||||||
|
*p.ready <- false
|
||||||
|
return err
|
||||||
|
case err := <-*p.read:
|
||||||
|
if err != nil {
|
||||||
|
*p.ready <- false
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-*p.wait
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the status file descriptor passed to xdg-dbus-proxy, causing it to stop.
|
||||||
|
func (p *Proxy) Close() error {
|
||||||
|
return p.statP[0].Close()
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Proxy holds references to a xdg-dbus-proxy process, and should never be copied.
|
||||||
|
// Once sealed, configuration changes will no longer be possible and attempting to do so will result in a panic.
|
||||||
|
type Proxy struct {
|
||||||
|
cmd *exec.Cmd
|
||||||
|
|
||||||
|
statP [2]*os.File
|
||||||
|
argsP [2]*os.File
|
||||||
|
|
||||||
|
address [2]string
|
||||||
|
path string
|
||||||
|
|
||||||
|
wait *chan error
|
||||||
|
read *chan error
|
||||||
|
ready *chan bool
|
||||||
|
|
||||||
|
seal *string
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) String() string {
|
||||||
|
if p.cmd != nil {
|
||||||
|
return p.cmd.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.seal != nil {
|
||||||
|
return *p.seal
|
||||||
|
}
|
||||||
|
|
||||||
|
return "(unsealed dbus proxy)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seal seals the Proxy instance.
|
||||||
|
func (p *Proxy) Seal(c *Config) error {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
if p.seal != nil {
|
||||||
|
panic("dbus proxy sealed twice")
|
||||||
|
}
|
||||||
|
args := c.Args(p.address[0], p.address[1])
|
||||||
|
|
||||||
|
seal := strings.Builder{}
|
||||||
|
for _, arg := range args {
|
||||||
|
// reject argument strings containing null
|
||||||
|
for _, b := range arg {
|
||||||
|
if b == '\x00' {
|
||||||
|
return errors.New("argument contains null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write null terminated argument
|
||||||
|
seal.WriteString(arg)
|
||||||
|
seal.WriteByte('\x00')
|
||||||
|
}
|
||||||
|
v := seal.String()
|
||||||
|
p.seal = &v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a reference to a new unsealed Proxy.
|
||||||
|
func New(binPath, address, path string) *Proxy {
|
||||||
|
return &Proxy{path: binPath, address: [2]string{address, path}}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
userName string
|
||||||
|
dbusConfig string
|
||||||
|
dbusID string
|
||||||
|
mpris bool
|
||||||
|
|
||||||
|
mustWayland bool
|
||||||
|
mustX bool
|
||||||
|
mustDBus bool
|
||||||
|
mustPulse bool
|
||||||
|
|
||||||
|
flagVerbose bool
|
||||||
|
printVersion bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&userName, "u", "chronos", "Passwd name of user to run as")
|
||||||
|
flag.StringVar(&dbusConfig, "dbus-config", "builtin", "Path to D-Bus proxy config file, or \"builtin\" for defaults")
|
||||||
|
flag.StringVar(&dbusID, "dbus-id", "", "D-Bus ID of application, leave empty to disable own paths, has no effect if custom config is available")
|
||||||
|
flag.BoolVar(&mpris, "mpris", false, "Allow owning MPRIS D-Bus path, has no effect if custom config is available")
|
||||||
|
|
||||||
|
flag.BoolVar(&mustWayland, "wayland", false, "Share Wayland socket")
|
||||||
|
flag.BoolVar(&mustX, "X", false, "Share X11 socket and allow connection")
|
||||||
|
flag.BoolVar(&mustDBus, "dbus", false, "Proxy D-Bus connection")
|
||||||
|
flag.BoolVar(&mustPulse, "pulse", false, "Share PulseAudio socket and cookie")
|
||||||
|
|
||||||
|
flag.BoolVar(&app.LaunchOptions[app.LaunchMethodSudo], "sudo", false, "Use 'sudo' to switch user")
|
||||||
|
flag.BoolVar(&app.LaunchOptions[app.LaunchMethodMachineCtl], "machinectl", true, "Use 'machinectl' to switch user")
|
||||||
|
|
||||||
|
flag.BoolVar(&flagVerbose, "v", false, "Verbose output")
|
||||||
|
flag.BoolVar(&printVersion, "V", false, "Print version")
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/dbus"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/acl"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/system"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dbusSessionBusAddress = "DBUS_SESSION_BUS_ADDRESS"
|
||||||
|
|
||||||
|
var dbusAddress string
|
||||||
|
|
||||||
|
func (a *App) ShareDBus(c *dbus.Config) {
|
||||||
|
a.setEnablement(state.EnableDBus)
|
||||||
|
|
||||||
|
var binPath, address string
|
||||||
|
target := path.Join(system.V.Share, strconv.Itoa(os.Getpid()))
|
||||||
|
dbusAddress = "unix:path=" + target
|
||||||
|
|
||||||
|
if b, ok := util.Which("xdg-dbus-proxy"); !ok {
|
||||||
|
state.Fatal("D-Bus: Did not find 'xdg-dbus-proxy' in PATH")
|
||||||
|
} else {
|
||||||
|
binPath = b
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr, ok := os.LookupEnv(dbusSessionBusAddress); !ok {
|
||||||
|
state.Fatal("D-Bus: DBUS_SESSION_BUS_ADDRESS not set")
|
||||||
|
} else {
|
||||||
|
address = addr
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Log = system.V.Verbose
|
||||||
|
p := dbus.New(binPath, address, target)
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Println("D-Bus: sealing proxy", c.Args(address, target))
|
||||||
|
}
|
||||||
|
if err := p.Seal(c); err != nil {
|
||||||
|
state.Fatal("D-Bus: invalid config when sealing proxy,", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ready := make(chan bool, 1)
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Printf("Starting session bus proxy '%s' for address '%s'\n", dbusAddress, address)
|
||||||
|
}
|
||||||
|
if err := p.Start(&ready); err != nil {
|
||||||
|
state.Fatal("D-Bus: error starting proxy,", err)
|
||||||
|
}
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Println("D-Bus proxy launch:", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := p.Wait(); err != nil {
|
||||||
|
fmt.Println("warn: D-Bus proxy returned error,", err)
|
||||||
|
} else {
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Println("D-Bus proxy uneventful wait")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := os.Remove(target); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
fmt.Println("Error removing dangling D-Bus socket:", err)
|
||||||
|
}
|
||||||
|
done <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// register early to enable Fatal cleanup
|
||||||
|
state.RegisterDBus(p, &done)
|
||||||
|
|
||||||
|
if !<-ready {
|
||||||
|
state.Fatal("D-Bus: proxy did not start correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.AppendEnv(dbusSessionBusAddress, dbusAddress)
|
||||||
|
if err := acl.UpdatePerm(target, a.UID(), acl.Read, acl.Write); err != nil {
|
||||||
|
state.Fatal(fmt.Sprintf("Error preparing D-Bus proxy '%s':", dbusAddress), err)
|
||||||
|
} else {
|
||||||
|
state.RegisterRevertPath(target)
|
||||||
|
}
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Printf("Session bus proxy '%s' for address '%s' configured\n", dbusAddress, address)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,14 +10,10 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.ophivana.moe/cat/fortify/internal/state"
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
"git.ophivana.moe/cat/fortify/internal/system"
|
|
||||||
"git.ophivana.moe/cat/fortify/internal/util"
|
"git.ophivana.moe/cat/fortify/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const launcherPayload = "FORTIFY_LAUNCHER_PAYLOAD"
|
||||||
sudoAskPass = "SUDO_ASKPASS"
|
|
||||||
launcherPayload = "FORTIFY_LAUNCHER_PAYLOAD"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (a *App) launcherPayloadEnv() string {
|
func (a *App) launcherPayloadEnv() string {
|
||||||
r := &bytes.Buffer{}
|
r := &bytes.Buffer{}
|
||||||
|
@ -75,80 +71,3 @@ func Early(printVersion bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) launchBySudo() (args []string) {
|
|
||||||
args = make([]string, 0, 4+len(a.env)+len(a.command))
|
|
||||||
|
|
||||||
// -Hiu $USER
|
|
||||||
args = append(args, "-Hiu", a.Username)
|
|
||||||
|
|
||||||
// -A?
|
|
||||||
if _, ok := os.LookupEnv(sudoAskPass); ok {
|
|
||||||
if system.V.Verbose {
|
|
||||||
fmt.Printf("%s set, adding askpass flag\n", sudoAskPass)
|
|
||||||
}
|
|
||||||
args = append(args, "-A")
|
|
||||||
}
|
|
||||||
|
|
||||||
// environ
|
|
||||||
args = append(args, a.env...)
|
|
||||||
|
|
||||||
// -- $@
|
|
||||||
args = append(args, "--")
|
|
||||||
args = append(args, a.command...)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) launchByMachineCtl(bare bool) (args []string) {
|
|
||||||
args = make([]string, 0, 9+len(a.env))
|
|
||||||
|
|
||||||
// shell --uid=$USER
|
|
||||||
args = append(args, "shell", "--uid="+a.Username)
|
|
||||||
|
|
||||||
// --quiet
|
|
||||||
if !system.V.Verbose {
|
|
||||||
args = append(args, "--quiet")
|
|
||||||
}
|
|
||||||
|
|
||||||
// environ
|
|
||||||
envQ := make([]string, len(a.env)+1)
|
|
||||||
for i, e := range a.env {
|
|
||||||
envQ[i] = "-E" + e
|
|
||||||
}
|
|
||||||
envQ[len(a.env)] = "-E" + a.launcherPayloadEnv()
|
|
||||||
args = append(args, envQ...)
|
|
||||||
|
|
||||||
// -- .host
|
|
||||||
args = append(args, "--", ".host")
|
|
||||||
|
|
||||||
// /bin/sh -c
|
|
||||||
if sh, ok := util.Which("sh"); !ok {
|
|
||||||
state.Fatal("Did not find 'sh' in PATH")
|
|
||||||
} else {
|
|
||||||
args = append(args, sh, "-c")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(a.command) == 0 { // execute shell if command is not provided
|
|
||||||
a.command = []string{"$SHELL"}
|
|
||||||
}
|
|
||||||
|
|
||||||
innerCommand := strings.Builder{}
|
|
||||||
|
|
||||||
if !bare {
|
|
||||||
innerCommand.WriteString("dbus-update-activation-environment --systemd")
|
|
||||||
for _, e := range a.env {
|
|
||||||
innerCommand.WriteString(" " + strings.SplitN(e, "=", 2)[0])
|
|
||||||
}
|
|
||||||
innerCommand.WriteString("; systemctl --user start xdg-desktop-portal-gtk; ")
|
|
||||||
}
|
|
||||||
|
|
||||||
if executable, err := os.Executable(); err != nil {
|
|
||||||
state.Fatal("Error reading executable path:", err)
|
|
||||||
} else {
|
|
||||||
innerCommand.WriteString("exec " + executable + " -V")
|
|
||||||
}
|
|
||||||
args = append(args, innerCommand.String())
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/acl"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/system"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) SharePulse() {
|
||||||
|
a.setEnablement(state.EnablePulse)
|
||||||
|
|
||||||
|
// ensure PulseAudio directory ACL (e.g. `/run/user/%d/pulse`)
|
||||||
|
pulse := path.Join(system.V.Runtime, "pulse")
|
||||||
|
pulseS := path.Join(pulse, "native")
|
||||||
|
if s, err := os.Stat(pulse); err != nil {
|
||||||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
state.Fatal("Error accessing PulseAudio directory:", err)
|
||||||
|
}
|
||||||
|
state.Fatal(fmt.Sprintf("PulseAudio dir '%s' not found", pulse))
|
||||||
|
} else {
|
||||||
|
// add environment variable for new process
|
||||||
|
a.AppendEnv(util.PulseServer, "unix:"+pulseS)
|
||||||
|
if err = acl.UpdatePerm(pulse, a.UID(), acl.Execute); err != nil {
|
||||||
|
state.Fatal("Error preparing PulseAudio:", err)
|
||||||
|
} else {
|
||||||
|
state.RegisterRevertPath(pulse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`)
|
||||||
|
if s, err = os.Stat(pulseS); err != nil {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
state.Fatal("PulseAudio directory found but socket does not exist")
|
||||||
|
}
|
||||||
|
state.Fatal("Error accessing PulseAudio socket:", err)
|
||||||
|
} else {
|
||||||
|
if m := s.Mode(); m&0o006 != 0o006 {
|
||||||
|
state.Fatal(fmt.Sprintf("Unexpected permissions on '%s':", pulseS), m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish current user's pulse-cookie for target user
|
||||||
|
pulseCookieSource := util.DiscoverPulseCookie()
|
||||||
|
pulseCookieFinal := path.Join(system.V.Share, "pulse-cookie")
|
||||||
|
a.AppendEnv(util.PulseCookie, pulseCookieFinal)
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Printf("Publishing PulseAudio cookie '%s' to '%s'\n", pulseCookieSource, pulseCookieFinal)
|
||||||
|
}
|
||||||
|
if err = util.CopyFile(pulseCookieFinal, pulseCookieSource); err != nil {
|
||||||
|
state.Fatal("Error copying PulseAudio cookie:", err)
|
||||||
|
}
|
||||||
|
if err = acl.UpdatePerm(pulseCookieFinal, a.UID(), acl.Read); err != nil {
|
||||||
|
state.Fatal("Error publishing PulseAudio cookie:", err)
|
||||||
|
} else {
|
||||||
|
state.RegisterRevertPath(pulseCookieFinal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Printf("PulseAudio dir '%s' configured\n", pulse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/system"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
term = "TERM"
|
||||||
|
sudoAskPass = "SUDO_ASKPASS"
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
LaunchMethodSudo = iota
|
||||||
|
LaunchMethodMachineCtl
|
||||||
|
|
||||||
|
launchOptionLength
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// LaunchOptions is set in main's cli.go
|
||||||
|
LaunchOptions [launchOptionLength]bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) Run() {
|
||||||
|
// pass $TERM to launcher
|
||||||
|
if t, ok := os.LookupEnv(term); ok {
|
||||||
|
a.AppendEnv(term, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
commandBuilder := a.commandBuilderSudo
|
||||||
|
|
||||||
|
var toolPath string
|
||||||
|
|
||||||
|
// dependency checks
|
||||||
|
const sudoFallback = "Falling back to 'sudo', some desktop integration features may not work"
|
||||||
|
if LaunchOptions[LaunchMethodMachineCtl] && !LaunchOptions[LaunchMethodSudo] { // sudo argument takes priority
|
||||||
|
if !util.SdBooted() {
|
||||||
|
fmt.Println("This system was not booted through systemd")
|
||||||
|
fmt.Println(sudoFallback)
|
||||||
|
} else if machineCtlPath, ok := util.Which("machinectl"); !ok {
|
||||||
|
fmt.Println("Did not find 'machinectl' in PATH")
|
||||||
|
fmt.Println(sudoFallback)
|
||||||
|
} else {
|
||||||
|
toolPath = machineCtlPath
|
||||||
|
commandBuilder = a.commandBuilderMachineCtl
|
||||||
|
}
|
||||||
|
} else if sudoPath, ok := util.Which("sudo"); !ok {
|
||||||
|
state.Fatal("Did not find 'sudo' in PATH")
|
||||||
|
} else {
|
||||||
|
toolPath = sudoPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Printf("Selected launcher '%s'\n", toolPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(toolPath, commandBuilder()...)
|
||||||
|
cmd.Env = a.env
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Dir = system.V.RunDir
|
||||||
|
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Println("Executing:", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
state.Fatal("Error starting process:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state.RegisterEnablement(a.enablements)
|
||||||
|
|
||||||
|
if err := state.SaveProcess(a.Uid, cmd); err != nil {
|
||||||
|
// process already started, shouldn't be fatal
|
||||||
|
fmt.Println("Error registering process:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r int
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
var exitError *exec.ExitError
|
||||||
|
if !errors.As(err, &exitError) {
|
||||||
|
state.Fatal("Error running process:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Println("Process exited with exit code", r)
|
||||||
|
}
|
||||||
|
state.BeforeExit()
|
||||||
|
os.Exit(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) commandBuilderSudo() (args []string) {
|
||||||
|
args = make([]string, 0, 4+len(a.env)+len(a.command))
|
||||||
|
|
||||||
|
// -Hiu $USER
|
||||||
|
args = append(args, "-Hiu", a.Username)
|
||||||
|
|
||||||
|
// -A?
|
||||||
|
if _, ok := os.LookupEnv(sudoAskPass); ok {
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Printf("%s set, adding askpass flag\n", sudoAskPass)
|
||||||
|
}
|
||||||
|
args = append(args, "-A")
|
||||||
|
}
|
||||||
|
|
||||||
|
// environ
|
||||||
|
args = append(args, a.env...)
|
||||||
|
|
||||||
|
// -- $@
|
||||||
|
args = append(args, "--")
|
||||||
|
args = append(args, a.command...)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) commandBuilderMachineCtl() (args []string) {
|
||||||
|
args = make([]string, 0, 9+len(a.env))
|
||||||
|
|
||||||
|
// shell --uid=$USER
|
||||||
|
args = append(args, "shell", "--uid="+a.Username)
|
||||||
|
|
||||||
|
// --quiet
|
||||||
|
if !system.V.Verbose {
|
||||||
|
args = append(args, "--quiet")
|
||||||
|
}
|
||||||
|
|
||||||
|
// environ
|
||||||
|
envQ := make([]string, len(a.env)+1)
|
||||||
|
for i, e := range a.env {
|
||||||
|
envQ[i] = "-E" + e
|
||||||
|
}
|
||||||
|
envQ[len(a.env)] = "-E" + a.launcherPayloadEnv()
|
||||||
|
args = append(args, envQ...)
|
||||||
|
|
||||||
|
// -- .host
|
||||||
|
args = append(args, "--", ".host")
|
||||||
|
|
||||||
|
// /bin/sh -c
|
||||||
|
if sh, ok := util.Which("sh"); !ok {
|
||||||
|
state.Fatal("Did not find 'sh' in PATH")
|
||||||
|
} else {
|
||||||
|
args = append(args, sh, "-c")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.command) == 0 { // execute shell if command is not provided
|
||||||
|
a.command = []string{"$SHELL"}
|
||||||
|
}
|
||||||
|
|
||||||
|
innerCommand := strings.Builder{}
|
||||||
|
|
||||||
|
innerCommand.WriteString("dbus-update-activation-environment --systemd")
|
||||||
|
for _, e := range a.env {
|
||||||
|
innerCommand.WriteString(" " + strings.SplitN(e, "=", 2)[0])
|
||||||
|
}
|
||||||
|
innerCommand.WriteString("; ")
|
||||||
|
|
||||||
|
if executable, err := os.Executable(); err != nil {
|
||||||
|
state.Fatal("Error reading executable path:", err)
|
||||||
|
} else {
|
||||||
|
if a.enablements.Has(state.EnableDBus) {
|
||||||
|
innerCommand.WriteString(dbusSessionBusAddress + "=" + "'" + dbusAddress + "' ")
|
||||||
|
}
|
||||||
|
innerCommand.WriteString("exec " + executable + " -V")
|
||||||
|
}
|
||||||
|
args = append(args, innerCommand.String())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -4,13 +4,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.ophivana.moe/cat/fortify/internal/state"
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
"git.ophivana.moe/cat/fortify/internal/system"
|
"git.ophivana.moe/cat/fortify/internal/system"
|
||||||
"git.ophivana.moe/cat/fortify/internal/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
|
@ -18,78 +16,19 @@ type App struct {
|
||||||
env []string
|
env []string
|
||||||
command []string
|
command []string
|
||||||
|
|
||||||
|
enablements state.Enablements
|
||||||
*user.User
|
*user.User
|
||||||
|
|
||||||
|
// absolutely *no* method of this type is thread-safe
|
||||||
|
// so don't treat it as if it is
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Run() {
|
func (a *App) setEnablement(e state.Enablement) {
|
||||||
f := a.launchBySudo
|
if a.enablements.Has(e) {
|
||||||
m, b := false, false
|
panic("enablement " + e.String() + " set twice")
|
||||||
switch {
|
|
||||||
case system.MethodFlags[0]: // sudo
|
|
||||||
case system.MethodFlags[1]: // bare
|
|
||||||
m, b = true, true
|
|
||||||
default: // machinectl
|
|
||||||
m, b = true, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var toolPath string
|
a.enablements |= e.Mask()
|
||||||
|
|
||||||
// dependency checks
|
|
||||||
const sudoFallback = "Falling back to 'sudo', some desktop integration features may not work"
|
|
||||||
if m {
|
|
||||||
if !util.SdBooted() {
|
|
||||||
fmt.Println("This system was not booted through systemd")
|
|
||||||
fmt.Println(sudoFallback)
|
|
||||||
} else if tp, ok := util.Which("machinectl"); !ok {
|
|
||||||
fmt.Println("Did not find 'machinectl' in PATH")
|
|
||||||
fmt.Println(sudoFallback)
|
|
||||||
} else {
|
|
||||||
toolPath = tp
|
|
||||||
f = func() []string { return a.launchByMachineCtl(b) }
|
|
||||||
}
|
|
||||||
} else if tp, ok := util.Which("sudo"); !ok {
|
|
||||||
state.Fatal("Did not find 'sudo' in PATH")
|
|
||||||
} else {
|
|
||||||
toolPath = tp
|
|
||||||
}
|
|
||||||
|
|
||||||
if system.V.Verbose {
|
|
||||||
fmt.Printf("Selected launcher '%s' bare=%t\n", toolPath, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(toolPath, f()...)
|
|
||||||
cmd.Env = a.env
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Dir = system.V.RunDir
|
|
||||||
|
|
||||||
if system.V.Verbose {
|
|
||||||
fmt.Println("Executing:", cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
state.Fatal("Error starting process:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := state.SaveProcess(a.Uid, cmd); err != nil {
|
|
||||||
// process already started, shouldn't be fatal
|
|
||||||
fmt.Println("Error registering process:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var r int
|
|
||||||
if err := cmd.Wait(); err != nil {
|
|
||||||
var exitError *exec.ExitError
|
|
||||||
if !errors.As(err, &exitError) {
|
|
||||||
state.Fatal("Error running process:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if system.V.Verbose {
|
|
||||||
fmt.Println("Process exited with exit code", r)
|
|
||||||
}
|
|
||||||
state.BeforeExit()
|
|
||||||
os.Exit(r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(userName string, args []string) *App {
|
func New(userName string, args []string) *App {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/acl"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// https://manpages.debian.org/experimental/libwayland-doc/wl_display_connect.3.en.html
|
||||||
|
waylandDisplay = "WAYLAND_DISPLAY"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) ShareWayland() {
|
||||||
|
a.setEnablement(state.EnableWayland)
|
||||||
|
|
||||||
|
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
|
||||||
|
if w, ok := os.LookupEnv(waylandDisplay); !ok {
|
||||||
|
state.Fatal("Wayland: WAYLAND_DISPLAY not set")
|
||||||
|
} else {
|
||||||
|
// add environment variable for new process
|
||||||
|
wp := path.Join(system.V.Runtime, w)
|
||||||
|
a.AppendEnv(waylandDisplay, wp)
|
||||||
|
if err := acl.UpdatePerm(wp, a.UID(), acl.Read, acl.Write, acl.Execute); err != nil {
|
||||||
|
state.Fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err)
|
||||||
|
} else {
|
||||||
|
state.RegisterRevertPath(wp)
|
||||||
|
}
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Printf("Wayland socket '%s' configured\n", w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/system"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/xcb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const display = "DISPLAY"
|
||||||
|
|
||||||
|
func (a *App) ShareX() {
|
||||||
|
a.setEnablement(state.EnableX)
|
||||||
|
|
||||||
|
// discovery X11 and grant user permission via the `ChangeHosts` command
|
||||||
|
if d, ok := os.LookupEnv(display); !ok {
|
||||||
|
state.Fatal("X11: DISPLAY not set")
|
||||||
|
} else {
|
||||||
|
// add environment variable for new process
|
||||||
|
a.AppendEnv(display, d)
|
||||||
|
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Printf("X11: Adding XHost entry SI:localuser:%s to display '%s'\n", a.Username, d)
|
||||||
|
}
|
||||||
|
if err := xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+a.Username); err != nil {
|
||||||
|
state.Fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err)
|
||||||
|
} else {
|
||||||
|
state.XcbActionComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
type (
|
||||||
|
Enablement uint8
|
||||||
|
Enablements uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EnableWayland Enablement = iota
|
||||||
|
EnableX
|
||||||
|
EnableDBus
|
||||||
|
EnablePulse
|
||||||
|
|
||||||
|
enableLength
|
||||||
|
)
|
||||||
|
|
||||||
|
var enablementString = [enableLength]string{
|
||||||
|
"Wayland",
|
||||||
|
"X11",
|
||||||
|
"D-Bus",
|
||||||
|
"PulseAudio",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Enablement) String() string {
|
||||||
|
return enablementString[e]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Enablement) Mask() Enablements {
|
||||||
|
return 1 << e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es Enablements) Has(e Enablement) bool {
|
||||||
|
return es&e.Mask() != 0
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ func BeforeExit() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if d, err := readLaunchers(); err != nil {
|
if d, err := readLaunchers(u.Uid); err != nil {
|
||||||
fmt.Println("Error reading active launchers:", err)
|
fmt.Println("Error reading active launchers:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
} else if len(d) > 0 {
|
} else if len(d) > 0 {
|
||||||
|
@ -65,4 +65,23 @@ func BeforeExit() {
|
||||||
fmt.Printf("Stripped ACL entry for user '%s' from '%s'\n", u.Username, candidate)
|
fmt.Printf("Stripped ACL entry for user '%s' from '%s'\n", u.Username, candidate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dbusProxy != nil {
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Println("D-Bus proxy registered, cleaning up")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dbusProxy.Close(); err != nil {
|
||||||
|
if errors.Is(err, os.ErrClosed) {
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Println("D-Bus proxy already closed")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("Error closing D-Bus proxy:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for Proxy.Wait to return
|
||||||
|
<-*dbusDone
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
stateActionEarly bool
|
||||||
|
stateActionEarlyC bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&stateActionEarly, "state", false, "print state information of active launchers")
|
||||||
|
flag.BoolVar(&stateActionEarlyC, "state-current", false, "print state information of active launchers for the specified user")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Early() {
|
||||||
|
var w *tabwriter.Writer
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case stateActionEarly:
|
||||||
|
if runDir, err := os.ReadDir(system.V.RunDir); err != nil {
|
||||||
|
fmt.Println("Error reading runtime directory:", err)
|
||||||
|
} else {
|
||||||
|
for _, e := range runDir {
|
||||||
|
if !e.IsDir() {
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Println("Skipped non-directory entry", e.Name())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = strconv.Atoi(e.Name()); err != nil {
|
||||||
|
if system.V.Verbose {
|
||||||
|
fmt.Println("Skipped non-uid entry", e.Name())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
printLauncherState(e.Name(), &w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case stateActionEarlyC:
|
||||||
|
printLauncherState(u.Uid, &w)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if w != nil {
|
||||||
|
if err := w.Flush(); err != nil {
|
||||||
|
fmt.Println("warn: error formatting output:", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("No information available.")
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printLauncherState(uid string, w **tabwriter.Writer) {
|
||||||
|
launchers, err := readLaunchers(uid)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error reading launchers:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *w == nil {
|
||||||
|
*w = tabwriter.NewWriter(os.Stdout, 0, 1, 4, ' ', 0)
|
||||||
|
|
||||||
|
if !system.V.Verbose {
|
||||||
|
_, _ = fmt.Fprintln(*w, "\tUID\tPID\tEnablements\tLauncher\tCommand")
|
||||||
|
} else {
|
||||||
|
_, _ = fmt.Fprintln(*w, "\tUID\tPID\tArgv")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, state := range launchers {
|
||||||
|
enablementsDescription := strings.Builder{}
|
||||||
|
for i := Enablement(0); i < enableLength; i++ {
|
||||||
|
if state.Capability.Has(i) {
|
||||||
|
enablementsDescription.WriteString(", " + i.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if enablementsDescription.Len() == 0 {
|
||||||
|
enablementsDescription.WriteString("none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !system.V.Verbose {
|
||||||
|
_, _ = fmt.Fprintf(*w, "\t%s\t%d\t%s\t%s\t%s\n",
|
||||||
|
uid, state.PID, strings.TrimPrefix(enablementsDescription.String(), ", "), state.Launcher,
|
||||||
|
state.Command)
|
||||||
|
} else {
|
||||||
|
_, _ = fmt.Fprintf(*w, "\t%s\t%d\t%s\n",
|
||||||
|
uid, state.PID, state.Argv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,35 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
|
import "git.ophivana.moe/cat/fortify/dbus"
|
||||||
|
|
||||||
|
var (
|
||||||
|
cleanupCandidate []string
|
||||||
|
enablements *Enablements
|
||||||
|
xcbActionComplete bool
|
||||||
|
|
||||||
|
dbusProxy *dbus.Proxy
|
||||||
|
dbusDone *chan struct{}
|
||||||
|
)
|
||||||
|
|
||||||
func RegisterRevertPath(p string) {
|
func RegisterRevertPath(p string) {
|
||||||
cleanupCandidate = append(cleanupCandidate, p)
|
cleanupCandidate = append(cleanupCandidate, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RegisterEnablement(e Enablements) {
|
||||||
|
if enablements != nil {
|
||||||
|
panic("enablement state set twice")
|
||||||
|
}
|
||||||
|
enablements = &e
|
||||||
|
}
|
||||||
|
|
||||||
func XcbActionComplete() {
|
func XcbActionComplete() {
|
||||||
if xcbActionComplete {
|
if xcbActionComplete {
|
||||||
Fatal("xcb inserted twice")
|
Fatal("xcb inserted twice")
|
||||||
}
|
}
|
||||||
xcbActionComplete = true
|
xcbActionComplete = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RegisterDBus(p *dbus.Proxy, done *chan struct{}) {
|
||||||
|
dbusProxy = p
|
||||||
|
dbusDone = done
|
||||||
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@ package state
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -18,10 +16,7 @@ import (
|
||||||
// this and launcher should eventually be replaced by a server process
|
// this and launcher should eventually be replaced by a server process
|
||||||
|
|
||||||
var (
|
var (
|
||||||
stateActionEarly bool
|
|
||||||
statePath string
|
statePath string
|
||||||
cleanupCandidate []string
|
|
||||||
xcbActionComplete bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type launcherState struct {
|
type launcherState struct {
|
||||||
|
@ -29,29 +24,7 @@ type launcherState struct {
|
||||||
Launcher string
|
Launcher string
|
||||||
Argv []string
|
Argv []string
|
||||||
Command []string
|
Command []string
|
||||||
}
|
Capability Enablements
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.BoolVar(&stateActionEarly, "state", false, "query state value of current active launchers")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Early() {
|
|
||||||
if !stateActionEarly {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
launchers, err := readLaunchers()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error reading launchers:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("\tPID\tLauncher")
|
|
||||||
for _, state := range launchers {
|
|
||||||
fmt.Printf("\t%d\t%s\nCommand: %s\nArgv: %s\n", state.PID, state.Launcher, state.Command, state.Argv)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveProcess called after process start, before wait
|
// SaveProcess called after process start, before wait
|
||||||
|
@ -62,6 +35,7 @@ func SaveProcess(uid string, cmd *exec.Cmd) error {
|
||||||
Launcher: cmd.Path,
|
Launcher: cmd.Path,
|
||||||
Argv: cmd.Args,
|
Argv: cmd.Args,
|
||||||
Command: command,
|
Command: command,
|
||||||
|
Capability: *enablements,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Mkdir(path.Join(system.V.RunDir, uid), 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
if err := os.Mkdir(path.Join(system.V.RunDir, uid), 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||||
|
@ -81,10 +55,10 @@ func SaveProcess(uid string, cmd *exec.Cmd) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readLaunchers() ([]*launcherState, error) {
|
func readLaunchers(uid string) ([]*launcherState, error) {
|
||||||
var f *os.File
|
var f *os.File
|
||||||
var r []*launcherState
|
var r []*launcherState
|
||||||
launcherPrefix := path.Join(system.V.RunDir, u.Uid)
|
launcherPrefix := path.Join(system.V.RunDir, uid)
|
||||||
|
|
||||||
if pl, err := os.ReadDir(launcherPrefix); err != nil {
|
if pl, err := os.ReadDir(launcherPrefix); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -11,7 +11,4 @@ type Values struct {
|
||||||
Verbose bool
|
Verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var V *Values
|
||||||
V *Values
|
|
||||||
MethodFlags [2]bool
|
|
||||||
)
|
|
||||||
|
|
128
main.go
128
main.go
|
@ -1,26 +1,27 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/dbus"
|
||||||
"git.ophivana.moe/cat/fortify/internal/acl"
|
"git.ophivana.moe/cat/fortify/internal/acl"
|
||||||
"git.ophivana.moe/cat/fortify/internal/app"
|
"git.ophivana.moe/cat/fortify/internal/app"
|
||||||
"git.ophivana.moe/cat/fortify/internal/state"
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
"git.ophivana.moe/cat/fortify/internal/system"
|
"git.ophivana.moe/cat/fortify/internal/system"
|
||||||
"git.ophivana.moe/cat/fortify/internal/util"
|
|
||||||
"git.ophivana.moe/cat/fortify/internal/xcb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version = "impure"
|
Version = "impure"
|
||||||
|
|
||||||
a *app.App
|
a *app.App
|
||||||
|
c *dbus.Config
|
||||||
)
|
)
|
||||||
|
|
||||||
func tryVersion() {
|
func tryVersion() {
|
||||||
|
@ -30,14 +31,6 @@ func tryVersion() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
term = "TERM"
|
|
||||||
display = "DISPLAY"
|
|
||||||
|
|
||||||
// https://manpages.debian.org/experimental/libwayland-doc/wl_display_connect.3.en.html
|
|
||||||
waylandDisplay = "WAYLAND_DISPLAY"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -52,6 +45,21 @@ func main() {
|
||||||
a = app.New(userName, flag.Args())
|
a = app.New(userName, flag.Args())
|
||||||
state.Set(*a.User, a.Command(), a.UID())
|
state.Set(*a.User, a.Command(), a.UID())
|
||||||
|
|
||||||
|
// parse D-Bus config file if applicable
|
||||||
|
if mustDBus {
|
||||||
|
if dbusConfig == "builtin" {
|
||||||
|
c = dbus.NewConfig(dbusID, true, mpris)
|
||||||
|
} else {
|
||||||
|
if f, err := os.Open(dbusConfig); err != nil {
|
||||||
|
state.Fatal("Error opening D-Bus proxy config file:", err)
|
||||||
|
} else {
|
||||||
|
if err = json.NewDecoder(f).Decode(&c); err != nil {
|
||||||
|
state.Fatal("Error parsing D-Bus proxy config file:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ensure RunDir (e.g. `/run/user/%d/fortify`)
|
// ensure RunDir (e.g. `/run/user/%d/fortify`)
|
||||||
if err := os.Mkdir(system.V.RunDir, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
if err := os.Mkdir(system.V.RunDir, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||||
state.Fatal("Error creating runtime directory:", err)
|
state.Fatal("Error creating runtime directory:", err)
|
||||||
|
@ -105,102 +113,20 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
|
if mustWayland {
|
||||||
if w, ok := os.LookupEnv(waylandDisplay); !ok {
|
a.ShareWayland()
|
||||||
if system.V.Verbose {
|
|
||||||
fmt.Println("Wayland: WAYLAND_DISPLAY not set, skipping")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// add environment variable for new process
|
|
||||||
wp := path.Join(system.V.Runtime, w)
|
|
||||||
a.AppendEnv(waylandDisplay, wp)
|
|
||||||
if err := acl.UpdatePerm(wp, a.UID(), acl.Read, acl.Write, acl.Execute); err != nil {
|
|
||||||
state.Fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err)
|
|
||||||
} else {
|
|
||||||
state.RegisterRevertPath(wp)
|
|
||||||
}
|
|
||||||
if system.V.Verbose {
|
|
||||||
fmt.Printf("Wayland socket '%s' configured\n", w)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// discovery X11 and grant user permission via the `ChangeHosts` command
|
if mustX {
|
||||||
if d, ok := os.LookupEnv(display); !ok {
|
a.ShareX()
|
||||||
if system.V.Verbose {
|
|
||||||
fmt.Println("X11: DISPLAY not set, skipping")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// add environment variable for new process
|
|
||||||
a.AppendEnv(display, d)
|
|
||||||
|
|
||||||
if system.V.Verbose {
|
|
||||||
fmt.Printf("X11: Adding XHost entry SI:localuser:%s to display '%s'\n", a.Username, d)
|
|
||||||
}
|
|
||||||
if err := xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+a.Username); err != nil {
|
|
||||||
state.Fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err)
|
|
||||||
} else {
|
|
||||||
state.XcbActionComplete()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure PulseAudio directory ACL (e.g. `/run/user/%d/pulse`)
|
if mustDBus {
|
||||||
pulse := path.Join(system.V.Runtime, "pulse")
|
a.ShareDBus(c)
|
||||||
pulseS := path.Join(pulse, "native")
|
|
||||||
if s, err := os.Stat(pulse); err != nil {
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
state.Fatal("Error accessing PulseAudio directory:", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if mustPulse {
|
if mustPulse {
|
||||||
state.Fatal("PulseAudio is unavailable")
|
a.SharePulse()
|
||||||
}
|
|
||||||
if system.V.Verbose {
|
|
||||||
fmt.Printf("PulseAudio dir '%s' not found, skipping\n", pulse)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// add environment variable for new process
|
|
||||||
a.AppendEnv(util.PulseServer, "unix:"+pulseS)
|
|
||||||
if err = acl.UpdatePerm(pulse, a.UID(), acl.Execute); err != nil {
|
|
||||||
state.Fatal("Error preparing PulseAudio:", err)
|
|
||||||
} else {
|
|
||||||
state.RegisterRevertPath(pulse)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`)
|
|
||||||
if s, err = os.Stat(pulseS); err != nil {
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
state.Fatal("PulseAudio directory found but socket does not exist")
|
|
||||||
}
|
|
||||||
state.Fatal("Error accessing PulseAudio socket:", err)
|
|
||||||
} else {
|
|
||||||
if m := s.Mode(); m&0o006 != 0o006 {
|
|
||||||
state.Fatal(fmt.Sprintf("Unexpected permissions on '%s':", pulseS), m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish current user's pulse-cookie for target user
|
|
||||||
pulseCookieSource := util.DiscoverPulseCookie()
|
|
||||||
pulseCookieFinal := path.Join(system.V.Share, "pulse-cookie")
|
|
||||||
a.AppendEnv(util.PulseCookie, pulseCookieFinal)
|
|
||||||
if system.V.Verbose {
|
|
||||||
fmt.Printf("Publishing PulseAudio cookie '%s' to '%s'\n", pulseCookieSource, pulseCookieFinal)
|
|
||||||
}
|
|
||||||
if err = util.CopyFile(pulseCookieFinal, pulseCookieSource); err != nil {
|
|
||||||
state.Fatal("Error copying PulseAudio cookie:", err)
|
|
||||||
}
|
|
||||||
if err = acl.UpdatePerm(pulseCookieFinal, a.UID(), acl.Read); err != nil {
|
|
||||||
state.Fatal("Error publishing PulseAudio cookie:", err)
|
|
||||||
} else {
|
|
||||||
state.RegisterRevertPath(pulseCookieFinal)
|
|
||||||
}
|
|
||||||
|
|
||||||
if system.V.Verbose {
|
|
||||||
fmt.Printf("PulseAudio dir '%s' configured\n", pulse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pass $TERM to launcher
|
|
||||||
if t, ok := os.LookupEnv(term); ok {
|
|
||||||
a.AppendEnv(term, t)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a.Run()
|
a.Run()
|
||||||
|
|
74
nixos.nix
74
nixos.nix
|
@ -63,6 +63,60 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dbus = {
|
||||||
|
config = mkOption {
|
||||||
|
type = nullOr anything;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
D-Bus custom configuration.
|
||||||
|
Setting this to null will enable built-in defaults.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
id = mkOption {
|
||||||
|
type = nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
D-Bus application id.
|
||||||
|
Setting this to null will disable own path in defaults.
|
||||||
|
Has no effect if custom configuration is set.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
mpris = mkOption {
|
||||||
|
type = bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to enable MPRIS in D-Bus defaults.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
capability = {
|
||||||
|
wayland = mkOption {
|
||||||
|
type = bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether to share the Wayland socket.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
x11 = mkOption {
|
||||||
|
type = bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to share the X11 socket and allow connection.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
dbus = mkOption {
|
||||||
|
type = bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether to proxy D-Bus.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
pulse = mkOption {
|
pulse = mkOption {
|
||||||
type = bool;
|
type = bool;
|
||||||
default = true;
|
default = true;
|
||||||
|
@ -70,6 +124,7 @@ in
|
||||||
Whether to share the PulseAudio socket and cookie.
|
Whether to share the PulseAudio socket and cookie.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
share = mkOption {
|
share = mkOption {
|
||||||
type = nullOr package;
|
type = nullOr package;
|
||||||
|
@ -164,8 +219,23 @@ in
|
||||||
user: launchers:
|
user: launchers:
|
||||||
mapAttrsToList (
|
mapAttrsToList (
|
||||||
name: launcher:
|
name: launcher:
|
||||||
|
with launcher.capability;
|
||||||
let
|
let
|
||||||
command = if launcher.command == null then name else launcher.command;
|
command = if launcher.command == null then name else launcher.command;
|
||||||
|
dbusConfig =
|
||||||
|
if launcher.dbus.config != null then
|
||||||
|
pkgs.writeText "${name}-dbus.json" (builtins.toJSON launcher.dbus.config)
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
capArgs =
|
||||||
|
(if wayland then " -wayland" else "")
|
||||||
|
+ (if x11 then " -X" else "")
|
||||||
|
+ (if dbus then " -dbus" else "")
|
||||||
|
+ (if pulse then " -pulse" else "")
|
||||||
|
+ (if launcher.dbus.mpris then " -mpris" else "")
|
||||||
|
+ (if launcher.dbus.id != null then " -dbus-id ${dbus.id}" else "")
|
||||||
|
+ (if dbusConfig != null then " -dbus-config ${dbusConfig}" else "")
|
||||||
|
+ (if launcher.method == "fortify-sudo" then " -sudo" else "");
|
||||||
in
|
in
|
||||||
pkgs.writeShellScriptBin name (
|
pkgs.writeShellScriptBin name (
|
||||||
if launcher.method == "sudo" then
|
if launcher.method == "sudo" then
|
||||||
|
@ -174,9 +244,7 @@ in
|
||||||
''
|
''
|
||||||
else
|
else
|
||||||
''
|
''
|
||||||
exec fortify${if launcher.pulse then " -pulse" else ""} -u ${user}${
|
exec fortify${capArgs} -u ${user} ${cfg.shell} -c "exec ${command} $@"
|
||||||
if launcher.method == "fortify-sudo" then " -sudo" else ""
|
|
||||||
} ${cfg.shell} -c "exec ${command} $@"
|
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
) launchers;
|
) launchers;
|
||||||
|
|
13
package.nix
13
package.nix
|
@ -1,12 +1,15 @@
|
||||||
{
|
{
|
||||||
|
lib,
|
||||||
|
buildGoModule,
|
||||||
|
makeBinaryWrapper,
|
||||||
|
xdg-dbus-proxy,
|
||||||
acl,
|
acl,
|
||||||
xorg,
|
xorg,
|
||||||
buildGoModule,
|
|
||||||
}:
|
}:
|
||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "fortify";
|
pname = "fortify";
|
||||||
version = "1.0.4";
|
version = "1.1.0";
|
||||||
|
|
||||||
src = ./.;
|
src = ./.;
|
||||||
vendorHash = null;
|
vendorHash = null;
|
||||||
|
@ -22,4 +25,10 @@ buildGoModule rec {
|
||||||
acl
|
acl
|
||||||
xorg.libxcb
|
xorg.libxcb
|
||||||
];
|
];
|
||||||
|
|
||||||
|
nativeBuildInputs = [ makeBinaryWrapper ];
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
wrapProgram $out/bin/${pname} --prefix PATH : ${lib.makeBinPath [ xdg-dbus-proxy ]}
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue