add v2 config syntax (#20)

This commit is contained in:
ed 2023-02-07 19:54:08 +00:00
parent 98cce66aa4
commit 84937d1ce0
8 changed files with 378 additions and 193 deletions

View file

@ -306,6 +306,7 @@ upgrade notes
per-folder, per-user permissions - if your setup is getting complex, consider making a [config file](./docs/example.conf) instead of using arguments per-folder, per-user permissions - if your setup is getting complex, consider making a [config file](./docs/example.conf) instead of using arguments
* much easier to manage, and you can modify the config at runtime with `systemctl reload copyparty` or more conveniently using the `[reload cfg]` button in the control-panel (if logged in as admin) * much easier to manage, and you can modify the config at runtime with `systemctl reload copyparty` or more conveniently using the `[reload cfg]` button in the control-panel (if logged in as admin)
* changes to the `[global]` config section requires a restart to take effect
a quick summary can be seen using `--help-accounts` a quick summary can be seen using `--help-accounts`
@ -690,6 +691,7 @@ using arguments or config files, or a mix of both:
* config files (`-c some.conf`) can set additional commandline arguments; see [./docs/example.conf](docs/example.conf) and [./docs/example2.conf](docs/example2.conf) * config files (`-c some.conf`) can set additional commandline arguments; see [./docs/example.conf](docs/example.conf) and [./docs/example2.conf](docs/example2.conf)
* `kill -s USR1` (same as `systemctl reload copyparty`) to reload accounts and volumes from config files without restarting * `kill -s USR1` (same as `systemctl reload copyparty`) to reload accounts and volumes from config files without restarting
* or click the `[reload cfg]` button in the control-panel when logged in as admin * or click the `[reload cfg]` button in the control-panel when logged in as admin
* changes to the `[global]` config section requires a restart to take effect
## zeroconf ## zeroconf
@ -800,7 +802,7 @@ some **BIG WARNINGS** specific to SMB/CIFS, in decreasing importance:
and some minor issues, and some minor issues,
* clients only see the first ~400 files in big folders; [impacket#1433](https://github.com/SecureAuthCorp/impacket/issues/1433) * clients only see the first ~400 files in big folders; [impacket#1433](https://github.com/SecureAuthCorp/impacket/issues/1433)
* hot-reload of server config (`/?reload=cfg`) only works for volumes, not account passwords * hot-reload of server config (`/?reload=cfg`) does not include the `[global]` section (commandline args)
* listens on the first IPv4 `-i` interface only (default = :: = 0.0.0.0 = all) * listens on the first IPv4 `-i` interface only (default = :: = 0.0.0.0 = all)
* login doesn't work on winxp, but anonymous access is ok -- remove all accounts from copyparty config for that to work * login doesn't work on winxp, but anonymous access is ok -- remove all accounts from copyparty config for that to work
* win10 onwards does not allow connecting anonymously / without accounts * win10 onwards does not allow connecting anonymously / without accounts

View file

@ -25,9 +25,9 @@ from textwrap import dedent
from .__init__ import ANYWIN, CORES, PY2, VT100, WINDOWS, E, EnvParams, unicode from .__init__ import ANYWIN, CORES, PY2, VT100, WINDOWS, E, EnvParams, unicode
from .__version__ import CODENAME, S_BUILD_DT, S_VERSION from .__version__ import CODENAME, S_BUILD_DT, S_VERSION
from .authsrv import expand_config_file, re_vol from .authsrv import expand_config_file, re_vol, upgrade_cfg_fmt, split_cfg_ln
from .svchub import SvcHub from .svchub import SvcHub
from .cfg import flagcats from .cfg import flagcats, onedash
from .util import ( from .util import (
IMPLICATIONS, IMPLICATIONS,
JINJA_VER, JINJA_VER,
@ -356,28 +356,28 @@ def configure_ssl_ciphers(al: argparse.Namespace) -> None:
def args_from_cfg(cfg_path: str) -> list[str]: def args_from_cfg(cfg_path: str) -> list[str]:
lines: list[str] = [] lines: list[str] = []
expand_config_file(lines, cfg_path, "") expand_config_file(lines, cfg_path, "")
lines = upgrade_cfg_fmt(None, argparse.Namespace(vc=False), lines, "")
ret: list[str] = [] ret: list[str] = []
skip = False skip = True
for ln in lines: for ln in lines:
if not ln: sn = ln.split(" #")[0].strip()
if sn.startswith("["):
skip = True
if sn.startswith("[global]"):
skip = False skip = False
continue continue
if skip or not sn.split("#")[0].strip():
if ln.startswith("#"):
continue continue
for k, v in split_cfg_ln(sn).items():
if not ln.startswith("-"): k = k.lstrip("-")
skip = True if not k:
continue continue
prefix = "-" if k in onedash else "--"
if skip: if v is True:
continue ret.append(prefix + k)
else:
try: ret.append(prefix + k + "=" + v)
ret.extend(ln.split(" ", 1))
except:
ret.append(ln)
return ret return ret

View file

@ -37,7 +37,7 @@ if True: # pylint: disable=using-constant-test
from typing import Any, Generator, Optional, Union from typing import Any, Generator, Optional, Union
from .util import RootLogger from .util import RootLogger, NamedLogger
if TYPE_CHECKING: if TYPE_CHECKING:
pass pass
@ -710,7 +710,7 @@ class AuthSrv(object):
daxs[dst] = AXS() daxs[dst] = AXS()
mflags[dst] = {} mflags[dst] = {}
def _e(self, desc: str) -> None: def _e(self, desc: Optional[str] = None) -> None:
if not self.args.vc or not self.line_ctr: if not self.args.vc or not self.line_ctr:
return return
@ -718,10 +718,11 @@ class AuthSrv(object):
self.log("") self.log("")
return return
desc = desc or ""
desc = desc.replace("[", "[\033[0m").replace("]", "\033[90m]") desc = desc.replace("[", "[\033[0m").replace("]", "\033[90m]")
self.log(" >>> {}{}".format(self.indent, desc), "90") self.log(" >>> {}{}".format(self.indent, desc), "90")
def _l(self, ln: str, c: int) -> None: def _l(self, ln: str, c: int, desc: str) -> None:
if not self.args.vc or not self.line_ctr: if not self.args.vc or not self.line_ctr:
return return
@ -729,11 +730,11 @@ class AuthSrv(object):
c += 30 c += 30
t = "\033[97m{:4} \033[{}m{}{}" t = "\033[97m{:4} \033[{}m{}{}"
self.log(t.format(self.line_ctr, c, self.indent, ln)) if desc:
t += " \033[0;90m# {}\033[0m"
desc = desc.replace("[", "[\033[0m").replace("]", "\033[90m]")
def _el(self, ln: str, c: int, desc: str) -> None: self.log(t.format(self.line_ctr, c, self.indent, ln, desc))
self._e(desc)
self._l(ln, c)
def _parse_config_file( def _parse_config_file(
self, self,
@ -744,9 +745,6 @@ class AuthSrv(object):
mflags: dict[str, dict[str, Any]], mflags: dict[str, dict[str, Any]],
mount: dict[str, str], mount: dict[str, str],
) -> None: ) -> None:
vol_src = None
vol_dst = None
new_blk = True
self.desc = [] self.desc = []
self.line_ctr = 0 self.line_ctr = 0
@ -755,83 +753,131 @@ class AuthSrv(object):
lns = ["{:4}: {}".format(n, s) for n, s in enumerate(cfg_lines, 1)] lns = ["{:4}: {}".format(n, s) for n, s in enumerate(cfg_lines, 1)]
self.log("expanded config file (unprocessed):\n" + "\n".join(lns)) self.log("expanded config file (unprocessed):\n" + "\n".join(lns))
cfg_lines = upgrade_cfg_fmt(self.log, self.args, cfg_lines, fp)
cat = ""
catg = "[global]"
cata = "[accounts]"
catx = "accs:"
catf = "flags:"
ap: Optional[str] = None
vp: Optional[str] = None
for ln in cfg_lines: for ln in cfg_lines:
self.line_ctr += 1 self.line_ctr += 1
if not ln and vol_src is not None: ln = ln.split(" #")[0].strip()
self.indent = "" if not ln.split("#")[0].strip():
self._e("└─end of settings for volume at URL [/{}]".format(vol_dst))
if vol_dst is None:
t = "no URL provided for filesystem path [{}]"
raise Exception(t.format(vol_src))
vol_src = None
vol_dst = None
if not ln:
new_blk = True
continue continue
if ln.startswith("#"): subsection = ln in (catx, catf)
continue if ln.startswith("[") or subsection:
self._e()
if ap is None and vp is not None:
t = "the first line after [/{}] must be a filesystem path to share on that volume"
raise Exception(t.format(vp))
if vol_src is None: cat = ln
if ln.startswith("u "): if not subsection:
u, p = ln[2:].split(":", 1) ap = vp = None
self._e("") self.indent = ""
self._el(ln, 5, "account [{}], password [{}]:".format(u, p))
acct[u] = p
elif ln.startswith("-"):
self._e("")
try:
ck, cv = ln.split(" ", 1)
t = "argument [{}] with value [{}]"
self._el(ln, 6, t.format(ck, cv))
except:
self._el(ln, 6, "argument [{}]".format(ln))
elif new_blk:
self._e("")
self._e("┌─share filesystem path [{}]:".format(ln))
self.indent = ""
self._l(ln, 3)
vol_src = ln
else: else:
raise Exception("unexpected line: {}".format(ln)) self.indent = " "
new_blk = False if ln == catg:
t = "begin commandline-arguments (anything from --help; dashes are optional)"
self._l(ln, 6, t)
elif ln == cata:
self._l(ln, 5, "begin user-accounts section")
elif ln.startswith("[/"):
vp = ln[1:-1].strip("/")
self._l(ln, 2, "define volume at URL [/{}]".format(vp))
elif subsection:
if ln == catx:
self._l(ln, 5, "volume access config:")
else:
t = "volume-specific config (anything from --help-flags)"
self._l(ln, 6, t)
else:
raise Exception("invalid section header")
self.indent = " " if subsection else " "
continue continue
if vol_src and vol_dst is None: if cat == catg:
vol_dst = ln self._l(ln, 6, "")
if not vol_dst.startswith("/"): zt = split_cfg_ln(ln)
raise Exception('invalid mountpoint "{}"'.format(vol_dst)) for zs, za in zt.items():
zs = zs.lstrip("-")
if vol_src.startswith("~"): if za is True:
vol_src = os.path.expanduser(vol_src) self._e("└─argument [{}]".format(zs))
else:
# cfg files override arguments and previous files self._e("└─argument [{}] with value [{}]".format(zs, za))
vol_src = absreal(vol_src)
vol_dst = vol_dst.strip("/")
self._e("[{}]".format(vol_src))
self._e("")
self._el(ln, 2, "at URL [/{}]:".format(vol_dst))
self._map_volume(vol_src, vol_dst, mount, daxs, mflags)
continue continue
try: if cat == cata:
lvl, uname = ln.split(" ", 1) try:
except: u, p = [zs.strip() for zs in ln.split(":", 1)]
lvl = ln self._l(ln, 5, "account [{}], password [{}]".format(u, p))
uname = "*" acct[u] = p
except:
t = 'lines inside the [accounts] section must be "username: password"'
raise Exception(t)
continue
if lvl == "a": if vp is not None and ap is None:
t = "WARNING (config-file): permission flag 'a' is deprecated; please use 'rw' instead" ap = ln
self.log(t, 1) if ap.startswith("~"):
ap = os.path.expanduser(ap)
assert vol_dst is not None ap = absreal(ap)
self._e("") self._l(ln, 2, "bound to filesystem-path [{}]".format(ap))
self._read_vol_str(lvl, uname, daxs[vol_dst], mflags[vol_dst]) self._map_volume(ap, vp, mount, daxs, mflags)
self._l(ln, 2) continue
self._e("") if cat == catx:
err = ""
try:
self._l(ln, 5, "volume access config:")
sk, sv = ln.split(":")
if re.sub("[rwmdgG]", "", sk) or not sk:
err = "invalid accs permissions list; "
raise Exception(err)
if " " in re.sub(", *", "", sv).strip():
err = "list of users is not comma-separated; "
raise Exception(err)
self._read_vol_str(sk, sv.replace(" ", ""), daxs[vp], mflags[vp])
continue
except:
err += "accs entries must be 'rwmdgG: user1, user2, ...'"
raise Exception(err)
if cat == catf:
err = ""
try:
self._l(ln, 6, "volume-specific config:")
zd = split_cfg_ln(ln)
fstr = ""
for sk, sv in zd.items():
bad = re.sub(r"[a-z0-9_]", "", sk)
if bad:
err = "bad characters [{}] in volflag name [{}]; "
err = err.format(bad, sk)
raise Exception(err)
if sv is True:
fstr += "," + sk
else:
fstr += ",{}={}".format(sk, sv)
self._read_vol_str("c", fstr[1:], daxs[vp], mflags[vp])
fstr = ""
if fstr:
self._read_vol_str("c", fstr[1:], daxs[vp], mflags[vp])
continue
except:
err += "flags entries (volflags) must be one of the following:\n 'flag1, flag2, ...'\n 'key: value'\n 'flag1, flag2, key: value'"
raise Exception(err)
raise Exception("unprocessable line in config")
self._e()
self.line_ctr = 0 self.line_ctr = 0
def _read_vol_str( def _read_vol_str(
@ -871,7 +917,7 @@ class AuthSrv(object):
("G", axs.upget), ("G", axs.upget),
]: # b bb bbb ]: # b bb bbb
if ch in lvl: if ch in lvl:
t = "add permission [{}] for user [{}] -- {}" t = "└─add permission [{}] for user [{}] -- {}"
desc = permdescs.get(ch, "?") desc = permdescs.get(ch, "?")
self._e(t.format(ch, un, desc)) self._e(t.format(ch, un, desc))
al.add(un) al.add(un)
@ -886,9 +932,9 @@ class AuthSrv(object):
desc = flagdescs.get(name, "?").replace("\n", " ") desc = flagdescs.get(name, "?").replace("\n", " ")
if name not in ["mtp", "xbu", "xau", "xbr", "xar", "xbd", "xad", "xm"]: if name not in ["mtp", "xbu", "xau", "xbr", "xar", "xbd", "xad", "xm"]:
if value is True: if value is True:
t = "add volflag [{}] = {} ({})" t = "└─add volflag [{}] = {} ({})"
else: else:
t = "add volflag [{}] = [{}] ({})" t = "└─add volflag [{}] = [{}] ({})"
self._e(t.format(name, value, desc)) self._e(t.format(name, value, desc))
flags[name] = value flags[name] = value
return return
@ -1580,12 +1626,12 @@ class AuthSrv(object):
args.pop(pop, None) args.pop(pop, None)
if args: if args:
ret.append("# add commandline args") ret.append("[global]")
for k, v in args.items(): for k, v in args.items():
if k in askip: if k in askip:
continue continue
if k in csv: if k in csv:
v = ",".join([str(za) for za in v]) v = ", ".join([str(za) for za in v])
try: try:
v2 = getattr(self.dargs, k) v2 = getattr(self.dargs, k)
if v == v2: if v == v2:
@ -1593,27 +1639,27 @@ class AuthSrv(object):
except: except:
continue continue
dk = ("-" if k in onedash else "--") + k.replace("_", "-") dk = " " + k.replace("_", "-")
if k in lst: if k in lst:
for ve in v: for ve in v:
ret.append("{} {}".format(dk, ve)) ret.append("{}: {}".format(dk, ve))
else: else:
if v is True: if v is True:
ret.append(dk) ret.append(dk)
elif v not in (False, None, ""): elif v not in (False, None, ""):
ret.append("{} {}".format(dk, v)) ret.append("{}: {}".format(dk, v))
ret.append("") ret.append("")
if self.acct: if self.acct:
ret.append("# add accounts") ret.append("[accounts]")
for u, p in self.acct.items(): for u, p in self.acct.items():
ret.append("u {}:{}".format(u, p)) ret.append(" {}: {}".format(u, p))
ret.append("") ret.append("")
for vol in self.vfs.all_vols.values(): for vol in self.vfs.all_vols.values():
ret.append("# add volume [/{}]".format(vol.vpath)) ret.append("[/{}]".format(vol.vpath))
ret.append(vol.realpath) ret.append(" " + vol.realpath)
ret.append("/" + vol.vpath) ret.append(" accs:")
perms = { perms = {
"r": "uread", "r": "uread",
"w": "uwrite", "w": "uwrite",
@ -1638,14 +1684,12 @@ class AuthSrv(object):
continue continue
if uname in getattr(vol.axs, pkey): if uname in getattr(vol.axs, pkey):
pstr += pchar pstr += pchar
if uname == "*":
uname = ""
try: try:
vperms[pstr].append(uname) vperms[pstr].append(uname)
except: except:
vperms[pstr] = [uname] vperms[pstr] = [uname]
for pstr, uname in vperms.items(): for pstr, uname in vperms.items():
ret.append("{} {}".format(pstr, " ".join(uname)).rstrip(" ")) ret.append(" {}: {}".format(pstr, ", ".join(uname)))
trues = [] trues = []
vals = [] vals = []
for k, v in sorted(vol.flags.items()): for k, v in sorted(vol.flags.items()):
@ -1658,24 +1702,49 @@ class AuthSrv(object):
if k in lst: if k in lst:
for ve in v: for ve in v:
vals.append("c {}={}".format(k, ve)) vals.append("{}: {}".format(k, ve))
elif v is True: elif v is True:
trues.append(k) trues.append(k)
elif v is not False: elif v is not False:
vals.append("c {}={}".format(k, v)) vals.append("{}: {}".format(k, v))
pops = [] pops = []
for k1, k2 in IMPLICATIONS: for k1, k2 in IMPLICATIONS:
if k1 in trues: if k1 in trues:
pops.append(k2) pops.append(k2)
trues = [x for x in trues if x not in pops] trues = [x for x in trues if x not in pops]
if trues: if trues:
ret.append("c " + ",".join(trues)) vals.append(", ".join(trues))
ret.extend(vals) if vals:
ret.append(" flags:")
for zs in vals:
ret.append(" " + zs)
ret.append("") ret.append("")
self.log("generated config:\n\n" + "\n".join(ret)) self.log("generated config:\n\n" + "\n".join(ret))
def split_cfg_ln(ln: str) -> dict[str, Any]:
# "a, b, c: 3" => {a:true, b:true, c:3}
ret = {}
while True:
ln = ln.strip()
if not ln:
break
ofs_sep = ln.find(",") + 1
ofs_var = ln.find(":") + 1
if not ofs_sep and not ofs_var:
ret[ln] = True
break
if ofs_sep and (ofs_sep < ofs_var or not ofs_var):
k, ln = ln.split(",", 1)
ret[k.strip()] = True
else:
k, ln = ln.split(":", 1)
ret[k.strip()] = ln.strip()
break
return ret
def expand_config_file(ret: list[str], fp: str, ipath: str) -> None: def expand_config_file(ret: list[str], fp: str, ipath: str) -> None:
"""expand all % file includes""" """expand all % file includes"""
fp = absreal(fp) fp = absreal(fp)
@ -1694,13 +1763,126 @@ def expand_config_file(ret: list[str], fp: str, ipath: str) -> None:
return return
with open(fp, "rb") as f: with open(fp, "rb") as f:
for ln in [x.decode("utf-8").strip() for x in f]: for oln in [x.decode("utf-8").rstrip() for x in f]:
ln = oln.split(" #")[0].strip()
if ln.startswith("% "): if ln.startswith("% "):
pad = " " * len(oln.split("%")[0])
fp2 = ln[1:].strip() fp2 = ln[1:].strip()
fp2 = os.path.join(os.path.dirname(fp), fp2) fp2 = os.path.join(os.path.dirname(fp), fp2)
ofs = len(ret)
expand_config_file(ret, fp2, ipath) expand_config_file(ret, fp2, ipath)
for n in range(ofs, len(ret)):
ret[n] = pad + ret[n]
continue continue
ret.append(ln) ret.append(oln)
ret.append("#\033[36m closed{}\033[0m".format(ipath)) ret.append("#\033[36m closed{}\033[0m".format(ipath))
def upgrade_cfg_fmt(
log: Optional["NamedLogger"], args: argparse.Namespace, orig: list[str], cfg_fp: str
) -> list[str]:
"""convert from v1 to v2 format"""
zst = [x.split("#")[0].strip() for x in orig]
zst = [x for x in zst if x]
if (
"[global]" in zst
or "[accounts]" in zst
or "accs:" in zst
or "flags:" in zst
or [x for x in zst if x.startswith("[/")]
or len(zst) == len([x for x in zst if x.startswith("%")])
):
return orig
zst = [x for x in orig if "#\033[36m opening cfg file" not in x]
incl = len(zst) != len(orig) - 1
t = "upgrading config file [{}] from v1 to v2"
if not args.vc:
t += ". Run with argument '--vc' to see the converted config if you want to upgrade"
if incl:
t += ". Please don't include v1 configs from v2 files or vice versa! Upgrade all of them at the same time."
if log:
log(t.format(cfg_fp), 3)
ret = []
vp = ""
ap = ""
cat = ""
catg = "[global]"
cata = "[accounts]"
catx = " accs:"
catf = " flags:"
for ln in orig:
sn = ln.strip()
if not sn:
cat = vp = ap = ""
if not sn.split("#")[0]:
ret.append(ln)
elif sn.startswith("-") and cat in ("", catg):
if cat != catg:
cat = catg
ret.append(cat)
sn = sn.lstrip("-")
zst = sn.split(" ", 1)
if len(zst) > 1:
sn = "{}: {}".format(zst[0], zst[1].strip())
ret.append(" " + sn)
elif sn.startswith("u ") and cat in ("", catg, cata):
if cat != cata:
cat = cata
ret.append(cat)
s1, s2 = sn[1:].split(":", 1)
ret.append(" {}: {}".format(s1.strip(), s2.strip()))
elif not ap:
ap = sn
elif not vp:
vp = "/" + sn.strip("/")
cat = "[{}]".format(vp)
ret.append(cat)
ret.append(" " + ap)
elif sn.startswith("c "):
if cat != catf:
cat = catf
ret.append(cat)
sn = sn[1:].strip()
if "=" in sn:
zst = sn.split("=", 1)
sn = zst[0].replace(",", ", ")
sn += ": " + zst[1]
else:
sn = sn.replace(",", ", ")
ret.append(" " + sn)
elif sn[:1] in "rwmdgG":
if cat != catx:
cat = catx
ret.append(cat)
zst = sn.split(" ")
zst = [x for x in zst if x]
if len(zst) == 1:
zst.append("*")
ret.append(" {}: {}".format(zst[0], ", ".join(zst[1:])))
else:
t = "did not understand line {} in the config"
t1 = t
n = 0
for ln in orig:
n += 1
t += "\n{:4} {}".format(n, ln)
if log:
log(t, 1)
else:
print("\033[31m" + t)
raise Exception(t1)
if args.vc and log:
t = "new config syntax (copy/paste this to upgrade your config):\n"
t += "\n# ======================[ begin upgraded config ]======================\n\n"
for ln in ret:
t += ln + "\n"
t += "\n# ======================[ end of upgraded config ]======================\n"
log(t)
return ret

View file

@ -1,5 +1,6 @@
# this file gets included twice from ../some.conf, # this file gets included twice from ../some.conf,
# setting user permissions for a volume # setting user permissions for a volume
rw usr1 accs:
r usr2 rw: usr1
% sibling.conf r: usr2
% sibling.conf

View file

@ -1,3 +1,3 @@
# and this config file gets included from ./another.conf, # and this config file gets included from ./another.conf,
# adding a final permission for each of the two volumes in ../some.conf # adding a final permission for each of the two volumes in ../some.conf
m usr1 usr2 m: usr1, usr2

View file

@ -1,26 +1,29 @@
# not actually YAML but lets pretend:
# -*- mode: yaml -*-
# vim: ft=yaml:
# lets make two volumes with the same accounts/permissions for both; # lets make two volumes with the same accounts/permissions for both;
# first declare the accounts just once: # first declare the accounts just once:
u usr1:passw0rd [accounts]
u usr2:letmein usr1: passw0rd
usr2: letmein
# and listen on 127.0.0.1 only, port 2434 [global]
-i 127.0.0.1 i: 127.0.0.1 # listen on 127.0.0.1 only,
-p 2434 p: 2434 # port 2434
e2ds # enable file indexing+scanning
# enable file indexing+scanning and multimedia indexing+scanning e2ts # and multimedia indexing+scanning
-e2ds # (inline comments are OK if there is 2 spaces before the #)
-e2ts
# share /usr/share/games from the server filesystem # share /usr/share/games from the server filesystem
/usr/share/games [/vidya]
/vidya /usr/share/games
# include config file with volume permissions % foo/another.conf # include config file with volume permissions
% foo/another.conf
# and share your ~/Music folder too # and share your ~/Music folder too
~/Music [/bangers]
/bangers ~/Music
% foo/another.conf % foo/another.conf
# which should result in each of the volumes getting the following permissions: # which should result in each of the volumes getting the following permissions:
# usr1 read/write/move # usr1 read/write/move

View file

@ -1,72 +1,68 @@
# not actually YAML but lets pretend:
# -*- mode: yaml -*-
# vim: ft=yaml:
# append some arguments to the commandline; # append some arguments to the commandline;
# the first space in a line counts as a separator, # accepts anything listed in --help (leading dashes are optional)
# any additional spaces are part of the value # and inline comments are OK if there is 2 spaces before the '#'
-e2dsa [global]
-e2ts p: 8086, 3939 # listen on ports 8086 and 3939
-i 127.0.0.1 e2dsa # enable file indexing and filesystem scanning
e2ts # and enable multimedia indexing
z, qr # and zeroconf and qrcode (you can comma-separate arguments)
# create users: # create users:
# u username:password [accounts]
u ed:123 ed: 123 # username: password
u k:k k: k
# leave a blank line between volumes # create volumes:
# (and also between users and volumes) [/] # create a volume at "/" (the webroot), which will
. # share the contents of "." (the current directory)
accs:
r: * # everyone gets read-access, but
rw: ed # the user "ed" gets read-write
# create a volume: # let's specify different permissions for the "priv" subfolder
# share "." (the current directory) # by creating another volume at that location:
# as "/" (the webroot) for the following users: [/priv]
# "r" grants read-access for anyone ./priv
# "rw ed" grants read-write to ed accs:
. r: k # the user "k" can see the contents,
/ rw: ed # while "ed" gets read-write
r
rw ed
# custom permissions for the "priv" folder:
# user "k" can only see/read the contents
# user "ed" gets read-write access
./priv
/priv
r k
rw ed
# this does the same thing,
# and will cause an error on startup since /priv is already taken:
./priv
/priv
r ed k
w ed
# share /home/ed/Music/ as /music and let anyone read it # share /home/ed/Music/ as /music and let anyone read it
# (this will replace any folder called "music" in the webroot) # (this will replace any folder called "music" in the webroot)
/home/ed/Music [/music]
/music /home/ed/Music
r accs:
r: *
# and a folder where anyone can upload, but nobody can see the contents
[/dump]
/home/ed/inc
accs:
w: *
flags:
e2d # the e2d volflag enables the uploads database
nodupe # the nodupe volflag rejects duplicate uploads
# (see --help-flags for all available volflags to use)
# and a folder where anyone can upload # and a folder where anyone can upload
# but nobody can see the contents # and anyone can access their own uploads, but nothing else
# and set the e2d flag to enable the uploads database [/sharex]
# and set the nodupe flag to reject duplicate uploads /home/ed/inc/sharex
/home/ed/inc accs:
/dump wG: * # wG = write-upget = see your own uploads only
w rwmd: ed, k # read-write-modify-delete for users "ed" and "k"
c e2d flags:
c nodupe e2d, d2t, fk: 4
# volflag "e2d" enables the uploads database,
# and a folder where anyone can upload # "d2t" disables multimedia parsers (in case the uploads are malicious),
# and anyone can access their own uploads but nothing else # "dthumb" disables thumbnails (same reason),
# (permissions "wG" = write + upget); # "fk" enables filekeys (necessary for upget permission) (4 chars long)
# volflag "e2d" enables the uploads database, # -- note that its fine to combine all the volflags on
# "d2t" disables multimedia parsers (in case the uploads are malicious), # one line because only the last volflag has an argument
# "dthumb" disables thumbnails (same reason),
# "fk" enables filekeys (necessary for upget permission) (4 chars long)
# -- note that its fine to combine all the volflags on
# one line because only the last volflag has an argument
/home/ed/inc/sharex
/sharex
wG
c e2d,d2t,fk=4
# this entire config file can be replaced with these arguments: # this entire config file can be replaced with these arguments:
# -u ed:123 -u k:k -v .::r:a,ed -v priv:priv:r,k:rw,ed -v /home/ed/Music:music:r -v /home/ed/inc:dump:w:c,e2d,nodupe -v /home/ed/inc/sharex:sharex:wG:c,e2d,d2t,fk=4 # -u ed:123 -u k:k -v .::r:a,ed -v priv:priv:r,k:rw,ed -v /home/ed/Music:music:r -v /home/ed/inc:dump:w:c,e2d,nodupe -v /home/ed/inc/sharex:sharex:wG:c,e2d,d2t,fk=4

View file

@ -11,6 +11,7 @@ copyparty/broker_mp.py,
copyparty/broker_mpw.py, copyparty/broker_mpw.py,
copyparty/broker_thr.py, copyparty/broker_thr.py,
copyparty/broker_util.py, copyparty/broker_util.py,
copyparty/cfg.py,
copyparty/dxml.py, copyparty/dxml.py,
copyparty/fsutil.py, copyparty/fsutil.py,
copyparty/ftpd.py, copyparty/ftpd.py,