New arg parser and profile support

This commit is contained in:
Martin 2021-06-19 16:33:47 +02:00
parent 9708146d81
commit d96e27f3f3
Signed by: mawalu
GPG Key ID: BF556F989760A7C8
5 changed files with 145 additions and 57 deletions

48
lib/args.nim Normal file
View File

@ -0,0 +1,48 @@
import parseopt
import options
import modes
import os
type Args* = object
name*: Option[string]
cmd*: Option[string]
profile*: Option[string]
proc getCmd*(args: Args): string =
return args.cmd.get(getEnv("SHELL", "/bin/bash"))
proc getProfile*(args: Args, mode: Modes): string =
if args.profile.isSome:
return args.profile.unsafeGet
return case mode
of Modes.Shell: "shell"
of Modes.Box: "gui"
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] =
var p = initOptParser()
var args = Args()
while true:
p.next()
case p.kind
of cmdEnd: break
of cmdShortOption, cmdLongOption:
if p.val == "" or args.parseOpt(p.key, p.val) == false:
echo "Invalid argument ", p.val
return
of cmdArgument:
args.name = some(p.key.string)
return some(args)

View File

@ -1,11 +1,43 @@
import sequtils
import options import options
import bwrap
import utils
import json
type Link* = object type Link* = object
src*: string src*: string
dst*: string dst*: string
type Config* = object type Config* = object
extends*: Option[seq[string]] extends*: Option[string]
mount*: Option[seq[string]] mount*: Option[seq[string]]
romount*: Option[seq[string]] romount*: Option[seq[string]]
symlinks*: Option[seq[Link]] symlinks*: Option[seq[Link]]
proc applyConfig*(call: var BwrapCall, config: Config) =
for mount in config.mount.get(@[]):
call.addMount("--bind", checkRelativePath(mount))
for mount in config.romount.get(@[]):
call.addMount("--ro-bind", checkRelativePath(mount))
for symlink in config.symlinks.get(@[]):
call.addArg("--symlink", symlink.src, symlink.dst)
proc loadConfig*(path: string): Config =
return readFile(path)
.parseJson()
.to(Config)
proc extendConfig*(config: var Config): Config {.discardable.} =
if config.extends.isNone:
return
var eConf = loadConfig(getProfilePath(config.extends.unsafeGet))
eConf.extendConfig()
config.mount = some(config.mount.get(@[]).concat(eConf.mount.get(@[])))
config.romount = some(config.romount.get(@[]).concat(eConf.romount.get(@[])))
config.symlinks = some(config.symlinks.get(@[]).concat(eConf.symlinks.get(@[])))
return config

View File

@ -1,59 +1,49 @@
import os import os
import json import args
import utils
import modes import modes
import bwrap import bwrap
import config import config
import options import options
proc homePath(p: string): string = proc sandboxExec*(mode: Modes, args: Args) =
joinPath(getHomeDir(), p) var call = BwrapCall()
var userConfig = none(Config)
const CONFIG_LOCATION = homePath(joinPath(".sandboxes", "config.json")) let hostname = args.name.get("sandbox")
let profilePath = getProfilePath(args, mode)
proc checkRelativePath(p: string): string = if args.name.isSome:
if p[0] == '/': let name = args.name.unsafeGet
return p let sandboxPath = getSandboxPath(name)
homePath(p) let sandboxFiles = sandboxPath.joinPath("files")
let configPath = sandboxPath.joinPath("config.json")
proc applyConfig(call: var BwrapCall, config: Config) = if fileExists(configPath):
for mount in config.mount.get(@[]): userConfig = some(loadConfig(configPath))
call.addMount("--bind", checkRelativePath(mount))
for mount in config.romount.get(@[]):
call.addMount("--ro-bind", checkRelativePath(mount))
for symlink in config.symlinks.get(@[]):
call.addArg("--symlink", symlink.src, symlink.dst)
proc loadConfig(path: string): Config =
return readFile(path).parseJson().to(Config)
proc sandboxExec*(name: string, command: string, mode: Modes) =
let sandboxPath = homePath(joinPath(".sandboxes", name))
let sandboxFiles = joinPath(sandboxPath, "files")
let sandboxInfo = joinPath(sandboxPath, "info")
createDir(sandboxFiles) createDir(sandboxFiles)
var call = BwrapCall() call.addArg("--bind", sandboxFiles, getHomeDir())
var profile = loadConfig(profilePath)
profile.extendConfig()
call call
.addArg("--bind", sandboxFiles, getHomeDir()) .addMount("--dev-bind", "/dev/null")
.addMount("--dev-bind", "/dev")
.addArg("--tmpfs", "/tmp") .addArg("--tmpfs", "/tmp")
.addArg("--proc", "/proc") .addArg("--proc", "/proc")
.addArg("--unshare-all") .addArg("--unshare-all")
.addArg("--share-net") .addArg("--share-net")
.addArg("--die-with-parent") .addArg("--die-with-parent")
.addArg("--hostname", name) .applyConfig(profile)
.applyConfig(loadConfig(CONFIG_LOCATION))
if mode == Modes.Shell: if mode == Modes.Shell:
call call
.addMount("--bind", getCurrentDir()) .addMount("--bind", getCurrentDir())
.addArg("--chdir", getCurrentDir()) .addArg("--chdir", getCurrentDir())
.addArg("--hostname", hostname)
let configPath = sandboxPath.joinPath("config.json") if userConfig.isSome:
if fileExists(configPath): call.applyConfig(userConfig.unsafeGet)
call.applyConfig(loadConfig(configPath))
call.addArg(command).exec() call.addArg(args.getCmd).exec()

26
lib/utils.nim Normal file
View File

@ -0,0 +1,26 @@
import os
import args
import modes
const APP_NAME = "bwsandbox"
proc getDataDir*(): string =
getEnv("XDG_DATA_DIR", getHomeDir().joinPath(".local/share"))
proc checkRelativePath*(p: string): string =
if p[0] == '/':
return p
getHomeDir().joinPath(p)
proc getProfilePath*(profile: string): string =
getConfigDir()
.joinPath(APP_NAME)
.joinPath(profile)
proc getProfilePath*(args: Args, mode: Modes): string =
getProfilePath(args.getProfile(mode))
proc getSandboxPath*(name: string): string =
getDataDir()
.joinPath(APP_NAME)
.joinPath(name)

View File

@ -1,27 +1,19 @@
import lib/sandbox import lib/sandbox
import lib/modes import lib/modes
import lib/args
import strformat import strformat
import strutils import strutils
import options
import os import os
proc main() = proc main(): int =
let mode = parseEnum[Modes](paramStr(0)) let mode = parseEnum[Modes](paramStr(0), Modes.Shell)
let args = commandLineParams() let args = parseArgs()
let argc = paramCount()
if argc == 0: if args.isNone:
echo &"Usage: {mode} <sandbox> [command]" echo &"Usage: {mode} --command=cmd --profile=profile <sandbox_name>"
quit(1) return 1
let name = args[0]
var command: string
if argc > 1:
command = args[1]
else: else:
command = getEnv("SHELL", "/bin/sh") sandboxExec(mode, args.unsafeGet)
sandboxExec(name, command, mode) quit(main())
main()