diff --git a/bin/hooks/README.md b/bin/hooks/README.md index d4481523..7dd7f739 100644 --- a/bin/hooks/README.md +++ b/bin/hooks/README.md @@ -28,6 +28,9 @@ these are `--xiu` hooks; unlike `xbu` and `xau` (which get executed on every sin * [reject-extension.py](reject-extension.py) rejects uploads if they match a list of file extensions * [reloc-by-ext.py](reloc-by-ext.py) redirects an upload to another destination based on the file extension * good example of the `reloc` [hook effect](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#hook-effects) +* [reject-and-explain.py](reject-and-explain.py) shows a custom error-message when it rejects an upload +* [reject-ramdisk.py](reject-ramdisk.py) rejects the upload if the destination is a ramdisk + * this hook uses the `I` flag which makes it 140x faster, but if the plugin has a bug it may crash copyparty # on message @@ -35,3 +38,7 @@ these are `--xiu` hooks; unlike `xbu` and `xau` (which get executed on every sin * [qbittorrent-magnet.py](qbittorrent-magnet.py) starts downloading a torrent if you post a magnet url * [usb-eject.py](usb-eject.py) adds web-UI buttons to safe-remove usb flashdrives shared through copyparty * [msg-log.py](msg-log.py) is a guestbook; logs messages to a doc in the same folder + + +# general concept demos +* [import-me.py](import-me.py) shows how the `I` flag makes the hook 140x faster (but you need to be Very Careful when writing the plugin) diff --git a/bin/hooks/import-me.py b/bin/hooks/import-me.py index 547f4172..38e800b0 100644 --- a/bin/hooks/import-me.py +++ b/bin/hooks/import-me.py @@ -38,7 +38,7 @@ IMPORTANT NOTE: """ -def main(ka: dict[str, Any]) -> dict[str, str]: +def main(ka: dict[str, Any]) -> dict[str, Any]: # "ka" is a dictionary with info from copyparty... # but because we are running inside copyparty, we don't need such courtesies; @@ -47,7 +47,8 @@ def main(ka: dict[str, Any]) -> dict[str, str]: cf = inspect.currentframe().f_back.f_back.f_back t = "hello from hook; I am able to peek into copyparty's memory like so:\n function name: %s\n variables:\n %s\n" t2 = "\n ".join([("%r: %r" % (k, v))[:99] for k, v in cf.f_locals.items()][:9]) - print(t % (cf.f_code, t2)) + logger = ka["log"] + logger(t % (cf.f_code, t2)) # must return a dictionary with: # "rc": the retcode; 0 is ok diff --git a/bin/hooks/reject-ramdisk.py b/bin/hooks/reject-ramdisk.py new file mode 100644 index 00000000..75678fd8 --- /dev/null +++ b/bin/hooks/reject-ramdisk.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 + +import os +import threading +from argparse import Namespace + +from jinja2.nodes import Name +from copyparty.fsutil import Fstab +from typing import Any, Optional + + +_ = r""" +reject an upload if the target folder is on a ramdisk; useful when you +have a volume where some folders inside are ramdisks but others aren't + +example usage as global config: + --xbu I,bin/hooks/reject-ramdisk.py + +example usage as a volflag (per-volume config): + -v srv/inc:inc:r:rw,ed:c,xbu=I,bin/hooks/reject-ramdisk.py + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + (share filesystem-path srv/inc as volume /inc, + readable by everyone, read-write for user 'ed', + running this plugin on all uploads with the params listed below) + +example usage as a volflag in a copyparty config file: + [/inc] + srv/inc + accs: + r: * + rw: ed + flags: + xbu: I,bin/hooks/reject-ramdisk.py + +parameters explained, + I = import; do not fork / subprocess + +IMPORTANT NOTE: + because this hook is imported inside copyparty, you need to + be EXCEPTIONALLY CAREFUL to avoid side-effects, for example + DO NOT os.chdir() or anything like that, and also make sure + that the name of this file is unique (cannot be the same as + an existing python module/library) +""" + + +mutex = threading.Lock() +fstab: Optional[Fstab] = None + + +def main(ka: dict[str, Any]) -> dict[str, Any]: + global fstab + with mutex: + log = ka["log"] # this is a copyparty NamedLogger function + if not fstab: + log(" creating fstab", 6) + args = Namespace() + args.mtab_age = 1 # cache the filesystem info for 1 sec + fstab = Fstab(log, args, False) + + ap = ka["ap"] # abspath the upload is going to + fs, mp = fstab.get(ap) # figure out what the filesystem is + ramdisk = fs in ("tmspfs", "overlay") # looks like a ramdisk? + + # log(" fs=%r" % (fs,)) + + if ramdisk: + t = "Upload REJECTED because destination is a ramdisk" + return {"rc": 1, "rejectmsg": t} + + return {"rc": 0} diff --git a/copyparty/util.py b/copyparty/util.py index f75c7b34..8ce3bdc0 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -3629,7 +3629,7 @@ def retchk( def _parsehook( log: Optional["NamedLogger"], cmd: str -) -> tuple[str, bool, bool, bool, float, dict[str, Any], list[str]]: +) -> tuple[str, bool, bool, bool, bool, float, dict[str, Any], list[str]]: areq = "" chk = False fork = False @@ -3906,6 +3906,7 @@ def _runhook( "txt": txt, } if imp: + ja["log"] = log mod = loadpy(acmd[0], False) return mod.main(ja) arg = json.dumps(ja) @@ -4003,7 +4004,7 @@ def runhook( else: ret[k] = v except Exception as ex: - (log or print)("hook: {}".format(ex)) + (log or print)("hook: %r, %s" % (ex, ex)) if ",c," in "," + cmd: return {} break diff --git a/docs/devnotes.md b/docs/devnotes.md index b1a9110f..0a2bc999 100644 --- a/docs/devnotes.md +++ b/docs/devnotes.md @@ -15,6 +15,7 @@ * [general](#general) * [event hooks](#event-hooks) - on writing your own [hooks](../README.md#event-hooks) * [hook effects](#hook-effects) - hooks can cause intentional side-effects + * [hook import](#hook-import) - the `I` flag runs the hook inside copyparty * [assumptions](#assumptions) * [mdns](#mdns) * [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features @@ -310,7 +311,7 @@ a subset of effect types are available for a subset of hook types, to trigger indexing of files `/foo/1.txt` and `/foo/bar/2.txt`, a hook can `print(json.dumps({"idx":{"vp":["/foo/1.txt","/foo/bar/2.txt"]}}))` (and replace "idx" with "del" to delete instead) * note: paths starting with `/` are absolute URLs, but you can also do `../3.txt` relative to the destination folder of each uploaded file -### hook import +## hook import the `I` flag runs the hook inside copyparty, which can be very useful and dangerous: