mirror of
https://github.com/9001/copyparty.git
synced 2025-08-16 16:42:13 -06:00
add v2 config syntax (#20)
This commit is contained in:
parent
98cce66aa4
commit
84937d1ce0
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue