Compare commits

3 Commits

Author SHA1 Message Date
9a3e36fcb1 Update argparser to work with launcher mode 2021-08-29 19:40:24 +02:00
9cad6fc050 Start working on launcher mode 2021-08-14 13:49:26 +02:00
354ba05faa add dbus and dri 2021-08-14 13:48:55 +02:00
7 changed files with 160 additions and 87 deletions

View File

@@ -1,14 +1,13 @@
import parseopt
import options import options
import os import os
type Args* = object type Args* = object
name*: Option[string] name*: Option[string]
cmd*: Option[string] cmd*: Option[seq[string]]
profile*: Option[string] profile*: Option[string]
proc getCmd*(args: Args): string = proc getCmd*(args: Args): seq[string] =
return args.cmd.get(getEnv("SHELL", "/bin/bash")) return args.cmd.get(@[getEnv("SHELL", "/bin/bash")])
proc getProfile*(args: Args): string = proc getProfile*(args: Args): string =
if args.profile.isSome: if args.profile.isSome:
@@ -16,30 +15,27 @@ proc getProfile*(args: Args): string =
return "default" return "default"
proc parseOpt(args: var Args, key: string, value: string): bool =
case key
of "command", "c":
args.cmd = some(value)
of "profile", "p":
args.profile = some(value)
else:
return false
return true
proc parseArgs*(): Option[Args] = proc parseArgs*(): Option[Args] =
var p = initOptParser()
var args = Args() var args = Args()
while true: var command = newSeq[string]()
p.next() var i = 1
case p.kind
of cmdEnd: break while i <= paramCount():
of cmdShortOption, cmdLongOption: var arg = paramStr(i)
if p.val == "" or args.parseOpt(p.key, p.val) == false:
echo "Invalid argument ", p.val if arg == "--name":
return args.name = some(paramStr(i + 1))
of cmdArgument: i += 2
args.name = some(p.key.string) elif arg == "--profile":
args.profile = some(paramStr(i + 1))
i += 2
else:
echo arg
command.add(arg)
i += 1
if command.len > 0:
args.cmd = some(command)
return some(args) return some(args)

View File

@@ -16,6 +16,13 @@ type Config* = object
mountcwd*: Option[bool] mountcwd*: Option[bool]
privileged*: Option[bool] privileged*: Option[bool]
sethostname*: Option[bool] sethostname*: Option[bool]
allowdri*: Option[bool]
dbus*: Option[bool]
dbussee*: Option[seq[string]]
dbustalk*: Option[seq[string]]
dbusown*: Option[seq[string]]
dbuscall*: Option[seq[string]]
dbusbroadcast*: Option[seq[string]]
proc applyConfig*(call: var BwrapCall, config: Config) = proc applyConfig*(call: var BwrapCall, config: Config) =
for mount in config.mount.get(@[]): for mount in config.mount.get(@[]):
@@ -39,10 +46,19 @@ proc extendConfig*(config: var Config): Config {.discardable.} =
var eConf = loadConfig(getProfilePath(config.extends.unsafeGet)) var eConf = loadConfig(getProfilePath(config.extends.unsafeGet))
eConf.extendConfig() eConf.extendConfig()
# todo: replace using macro / templates
config.mount = some(config.mount.get(@[]).concat(eConf.mount.get(@[]))) config.mount = some(config.mount.get(@[]).concat(eConf.mount.get(@[])))
config.romount = some(config.romount.get(@[]).concat(eConf.romount.get(@[]))) config.romount = some(config.romount.get(@[]).concat(eConf.romount.get(@[])))
config.symlinks = some(config.symlinks.get(@[]).concat(eConf.symlinks.get(@[]))) config.symlinks = some(config.symlinks.get(@[]).concat(eConf.symlinks.get(@[])))
config.mountcwd = some(config.mountcwd.get(eConf.mountcwd.get(false))) config.mountcwd = some(config.mountcwd.get(eConf.mountcwd.get(false)))
config.sethostname = some(config.sethostname.get(eConf.sethostname.get(false))) config.sethostname = some(config.sethostname.get(eConf.sethostname.get(false)))
config.allowdri = some(config.allowdri.get(eConf.allowdri.get(false)))
config.dbus = some(config.dbus.get(eConf.dbus.get(false)))
config.dbussee = some(config.dbussee.get(@[]).concat(eConf.dbussee.get(@[])))
config.dbustalk = some(config.dbustalk.get(@[]).concat(eConf.dbustalk.get(@[])))
config.dbusown = some(config.dbusown.get(@[]).concat(eConf.dbusown.get(@[])))
config.dbuscall = some(config.dbuscall.get(@[]).concat(eConf.dbuscall.get(@[])))
config.dbusbroadcast = some(config.dbusbroadcast.get(@[]).concat(eConf.dbusbroadcast.get(@[])))
return config return config

View File

@@ -1,43 +1,54 @@
import strformat import strformat
import options
import config
import osproc import osproc
import random
import os
type DbusProxy* = object type DbusProxy* = object
process*: Process
socket*: string
args: seq[string] args: seq[string]
proc addSee*(proxy: var DbusProxy, name: string): var DbusProxy {.discardable.} = proc exec*(proxy: DbusProxy): Process =
proxy.args.add(&"--see={name}")
proxy
proc addTalk*(proxy: var DbusProxy, name: string): var DbusProxy {.discardable.} =
proxy.args.add(&"--talk={name}")
proxy
proc addOwn*(proxy: var DbusProxy, name: string): var DbusProxy {.discardable.} =
proxy.args.add(&"--own={name}")
proxy
proc addCall*(proxy: var DbusProxy, name: string): var DbusProxy {.discardable.} =
proxy.args.add(&"--call={name}")
proxy
proc addBroadcast*(proxy: var DbusProxy, name: string): var DbusProxy {.discardable.} =
proxy.args.add(&"--broadcast={name}")
proxy
proc paths*(proxy: var DbusProxy, systembus: string, filterbus: string): var DbusProxy {.discardable.} =
proxy.args.add(&"unix:path={systembus}")
proxy.args.add(filterbus)
proxy
proc log*(proxy: var DbusProxy): var DbusProxy {.discardable.} =
proxy.args.add("--log")
proxy
proc filter*(proxy: var DbusProxy): var DbusProxy {.discardable.} =
proxy.args.add("--filter")
proxy
proc exec*(proxy: DbusProxy): Process {.discardable.} =
# todo: start dbus proxy in bwrap # todo: start dbus proxy in bwrap
# todo: pass arguments as fd # todo: pass arguments as fd
startProcess("xdg-dbus-proxy", args = proxy.args, options = {poEchoCmd, poParentStreams, poUsePath}) startProcess("xdg-dbus-proxy", args = proxy.args,
options = {poEchoCmd, poParentStreams, poUsePath})
proc startDBusProxy*(config: Config, hostname: string): DbusProxy =
let busPath = getEnv("DBUS_SESSION_BUS_ADDRESS")
let runtimeDir = getEnv("XDG_RUNTIME_DIR")
if busPath == "" or runtimeDir == "":
raise newException(IOError, "DBUS_SESSION_BUS_ADDRESS and XDG_RUNTIME_DIR are required")
let id = rand(1000)
let filterName = &"dbus-proxy-{hostname}-{id}"
var proxy = DbusProxy()
proxy.socket = &"{runtimeDir}/{filterName}"
proxy.args.add(busPath)
proxy.args.add(proxy.socket)
for name in config.dbussee.get(@[]):
proxy.args.add(&"--see={name}")
for name in config.dbustalk.get(@[]):
proxy.args.add(&"--talk={name}")
for name in config.dbuscall.get(@[]):
proxy.args.add(&"--call={name}")
for name in config.dbusown.get(@[]):
proxy.args.add(&"--own={name}")
for name in config.dbusbroadcast.get(@[]):
proxy.args.add(&"--broadcast={name}")
proxy.args.add("--filter")
proxy.args.add("--log")
proxy.process = proxy.exec()
proxy

View File

@@ -1,11 +1,12 @@
import os import strutils
import options
import config
import utils
import bwrap
import args import args
import json import json
import dbus import dbus
import utils import os
import bwrap
import config
import options
proc sandboxExec*(args: Args) = proc sandboxExec*(args: Args) =
var call = BwrapCall() var call = BwrapCall()
@@ -34,28 +35,10 @@ proc sandboxExec*(args: Args) =
var config = loadConfig(configPath.unsafeGet) var config = loadConfig(configPath.unsafeGet)
config.extendConfig() config.extendConfig()
var proxy = DbusProxy()
proxy
.paths("/run/user/1000/bus", "/run/user/1000/.bus-sandboxed/test2")
.addCall("org.freedesktop.Notifications.*=@/org/freedesktop/Notifications")
.addCall("org.freedesktop.portal.*=*")
.addBroadcast("org.freedesktop.portal.*=@/org/freedesktop/portal/*")
.addOwn("org.mpris.MediaPlayer2.spotify")
.filter()
.log()
.exec()
call call
.addArg("--dev", "/dev") .addArg("--dev", "/dev")
# https://github.com/flatpak/flatpak/blob/1bdbb80ac57df437e46fce2cdd63e4ff7704718b/common/flatpak-run.c#L1496
.addMount("--dev-bind", "/dev/dri")
.addMount("--dev-bind", "/dev/nvidiactl")
.addMount("--dev-bind", "/dev/nvidia-modeset")
.addMount("--dev-bind", "/dev/nvidia0")
.addMount("--dev-bind", "/dev/random") .addMount("--dev-bind", "/dev/random")
.addMount("--dev-bind", "/dev/urandom") .addMount("--dev-bind", "/dev/urandom")
.addArg("--ro-bind", "/run/user/1000/.bus-sandboxed/test2", "/run/user/1000/bus")
.addArg("--tmpfs", "/tmp") .addArg("--tmpfs", "/tmp")
.addArg("--tmpfs", "/dev/shm") .addArg("--tmpfs", "/dev/shm")
.addArg("--proc", "/proc") .addArg("--proc", "/proc")
@@ -65,6 +48,18 @@ proc sandboxExec*(args: Args) =
.addArg("--setenv", "BWSANDBOX", "1") .addArg("--setenv", "BWSANDBOX", "1")
.applyConfig(config) .applyConfig(config)
if config.dbus.get(false):
# todo: handle process and cleanup later
let proxy = startDBusProxy(config, hostname)
call.addArg("--ro-bind", proxy.socket,
getEnv("DBUS_SESSION_BUS_ADDRESS").split('=')[1])
# todo: use fd signaling instead of this
sleep(100)
if config.allowdri.get(false):
enableDri(call)
if config.mountcwd.get(false): if config.mountcwd.get(false):
call call
.addMount("--bind", getCurrentDir()) .addMount("--bind", getCurrentDir())

View File

@@ -1,5 +1,8 @@
import os import strformat
import posix
import bwrap
import args import args
import os
const APP_NAME = "bwsandbox" const APP_NAME = "bwsandbox"
@@ -22,4 +25,27 @@ proc getProfilePath*(args: Args): string =
proc getSandboxPath*(name: string): string = proc getSandboxPath*(name: string): string =
getDataDir() getDataDir()
.joinPath(APP_NAME) .joinPath(APP_NAME)
.joinPath(name) .joinPath(name)
proc deviceExists(path: string): bool =
var res: Stat
return stat(path, res) >= 0 and S_ISCHR(res.st_mode)
# https://github.com/flatpak/flatpak/blob/1bdbb80ac57df437e46fce2cdd63e4ff7704718b/common/flatpak-run.c#L1496
proc enableDri*(call: var BwrapCall) =
const mounts = [
"/dev/dri", # general
"/dev/mali", "/dev/mali0", "/dev/umplock", # mali
"/dev/nvidiactl", "/dev/nvidia-modeset", # nvidia
"/dev/nvidia-uvm", "/dev/nvidia-uvm-tools" # nvidia OpenCl/CUDA
]
for mount in mounts:
if deviceExists(mount):
call.addMount("--dev-bind", mount)
for i in 0..20:
let device = &"/dev/nvidia{i}"
if deviceExists(device):
call.addMount("--dev-bind", device)

View File

@@ -1,14 +1,17 @@
import lib/sandbox import lib/sandbox
import lib/args import lib/args
import options import options
import random
proc main(): int = proc main(): int =
let args = parseArgs() let args = parseArgs()
echo args
if args.isNone: if args.isNone:
echo "Usage: bwshell --command=cmd --profile=profile <sandbox_name>" echo "Usage: bwshell --name=sandbox_name --profile=profile <sandbox_cmd>"
return 1 return 1
else: else:
randomize()
sandboxExec(args.unsafeGet) sandboxExec(args.unsafeGet)
quit(main()) quit(main())

26
scripts/applications.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
if [ $# -ne 1 ]; then
echo "Usage: $0 <target_dir>"
exit 1
fi
check_dir() {
local dir=$1
local file
for application in "$dir/"*; do
file="$(basename "$application")"
sed "s/Exec=/Exec=bwshell --name '$file' --profile gui /gi" "$application" > "$target/$file"
done
}
dirs=("/usr/share/applications" "$HOME/.local/share/applications")
target="$1"
mkdir -p "$target"
for dir in "${dirs[@]}"; do
check_dir "$dir"
done