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 bwrap
import utils
import json
type Link* = object
src*: string
dst*: string
type Config* = object
extends*: Option[seq[string]]
extends*: Option[string]
mount*: Option[seq[string]]
romount*: Option[seq[string]]
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 json
import args
import utils
import modes
import bwrap
import config
import options
proc homePath(p: string): string =
joinPath(getHomeDir(), p)
const CONFIG_LOCATION = homePath(joinPath(".sandboxes", "config.json"))
proc checkRelativePath(p: string): string =
if p[0] == '/':
return p
homePath(p)
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 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)
proc sandboxExec*(mode: Modes, args: Args) =
var call = BwrapCall()
var userConfig = none(Config)
let hostname = args.name.get("sandbox")
let profilePath = getProfilePath(args, mode)
if args.name.isSome:
let name = args.name.unsafeGet
let sandboxPath = getSandboxPath(name)
let sandboxFiles = sandboxPath.joinPath("files")
let configPath = sandboxPath.joinPath("config.json")
if fileExists(configPath):
userConfig = some(loadConfig(configPath))
createDir(sandboxFiles)
call.addArg("--bind", sandboxFiles, getHomeDir())
var profile = loadConfig(profilePath)
profile.extendConfig()
call
.addArg("--bind", sandboxFiles, getHomeDir())
.addMount("--dev-bind", "/dev")
.addMount("--dev-bind", "/dev/null")
.addArg("--tmpfs", "/tmp")
.addArg("--proc", "/proc")
.addArg("--unshare-all")
.addArg("--share-net")
.addArg("--die-with-parent")
.addArg("--hostname", name)
.applyConfig(loadConfig(CONFIG_LOCATION))
.applyConfig(profile)
if mode == Modes.Shell:
call
.addMount("--bind", getCurrentDir())
.addArg("--chdir", getCurrentDir())
.addArg("--hostname", hostname)
let configPath = sandboxPath.joinPath("config.json")
if fileExists(configPath):
call.applyConfig(loadConfig(configPath))
if userConfig.isSome:
call.applyConfig(userConfig.unsafeGet)
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/modes
import lib/args
import strformat
import strutils
import options
import os
proc main() =
let mode = parseEnum[Modes](paramStr(0))
let args = commandLineParams()
let argc = paramCount()
proc main(): int =
let mode = parseEnum[Modes](paramStr(0), Modes.Shell)
let args = parseArgs()
if argc == 0:
echo &"Usage: {mode} <sandbox> [command]"
quit(1)
let name = args[0]
var command: string
if argc > 1:
command = args[1]
if args.isNone:
echo &"Usage: {mode} --command=cmd --profile=profile <sandbox_name>"
return 1
else:
command = getEnv("SHELL", "/bin/sh")
sandboxExec(mode, args.unsafeGet)
sandboxExec(name, command, mode)
main()
quit(main())