mirror of
https://github.com/9001/copyparty.git
synced 2025-08-21 19:02:21 -06:00
Compare commits
No commits in common. "hovudstraum" and "v1.6.2" have entirely different histories.
hovudstrau
...
v1.6.2
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -8,42 +8,33 @@ assignees: '9001'
|
||||||
---
|
---
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
**please use english, or include an english translation.** aside from that,
|
|
||||||
all of the below are optional, consider them as inspiration, delete and rewrite at will, thx md
|
all of the below are optional, consider them as inspiration, delete and rewrite at will, thx md
|
||||||
|
|
||||||
|
|
||||||
### Describe the bug
|
**Describe the bug**
|
||||||
a description of what the bug is
|
a description of what the bug is
|
||||||
|
|
||||||
### To Reproduce
|
**To Reproduce**
|
||||||
List of steps to reproduce the issue, or, if it's hard to reproduce, then at least a detailed explanation of what you did to run into it
|
List of steps to reproduce the issue, or, if it's hard to reproduce, then at least a detailed explanation of what you did to run into it
|
||||||
|
|
||||||
### Expected behavior
|
**Expected behavior**
|
||||||
a description of what you expected to happen
|
a description of what you expected to happen
|
||||||
|
|
||||||
### Screenshots
|
**Screenshots**
|
||||||
if applicable, add screenshots to help explain your problem, such as the kickass crashpage :^)
|
if applicable, add screenshots to help explain your problem, such as the kickass crashpage :^)
|
||||||
|
|
||||||
### Server details (if you are using docker/podman)
|
**Server details**
|
||||||
remove the ones that are not relevant:
|
if the issue is possibly on the server-side, then mention some of the following:
|
||||||
* **server OS / version:**
|
* server OS / version:
|
||||||
* **how you're running copyparty:** (docker/podman/something-else)
|
* python version:
|
||||||
* **docker image:** (variant, version, and arch if you know)
|
* copyparty arguments:
|
||||||
* **copyparty arguments and/or config-file:**
|
* filesystem (`lsblk -f` on linux):
|
||||||
|
|
||||||
### Server details (if you're NOT using docker/podman)
|
**Client details**
|
||||||
remove the ones that are not relevant:
|
|
||||||
* **server OS / version:**
|
|
||||||
* **what copyparty did you grab:** (sfx/exe/pip/arch/...)
|
|
||||||
* **how you're running it:** (in a terminal, as a systemd-service, ...)
|
|
||||||
* run copyparty with `--version` and grab the last 3 lines (they start with `copyparty`, `CPython`, `sqlite`) and paste them below this line:
|
|
||||||
* **copyparty arguments and/or config-file:**
|
|
||||||
|
|
||||||
### Client details
|
|
||||||
if the issue is possibly on the client-side, then mention some of the following:
|
if the issue is possibly on the client-side, then mention some of the following:
|
||||||
* the device type and model:
|
* the device type and model:
|
||||||
* OS version:
|
* OS version:
|
||||||
* browser version:
|
* browser version:
|
||||||
|
|
||||||
### Additional context
|
**Additional context**
|
||||||
any other context about the problem here
|
any other context about the problem here
|
||||||
|
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -7,8 +7,6 @@ assignees: '9001'
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
NOTE:
|
|
||||||
**please use english, or include an english translation.** aside from that,
|
|
||||||
all of the below are optional, consider them as inspiration, delete and rewrite at will
|
all of the below are optional, consider them as inspiration, delete and rewrite at will
|
||||||
|
|
||||||
**is your feature request related to a problem? Please describe.**
|
**is your feature request related to a problem? Please describe.**
|
||||||
|
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
@ -1,2 +0,0 @@
|
||||||
To show that your contribution is compatible with the MIT License, please include the following text somewhere in this PR description:
|
|
||||||
This PR complies with the DCO; https://developercertificate.org/
|
|
19
.gitignore
vendored
19
.gitignore
vendored
|
@ -12,7 +12,6 @@ copyparty.egg-info/
|
||||||
/dist/
|
/dist/
|
||||||
/py2/
|
/py2/
|
||||||
/sfx*
|
/sfx*
|
||||||
/pyz/
|
|
||||||
/unt/
|
/unt/
|
||||||
/log/
|
/log/
|
||||||
|
|
||||||
|
@ -22,27 +21,11 @@ copyparty.egg-info/
|
||||||
# winmerge
|
# winmerge
|
||||||
*.bak
|
*.bak
|
||||||
|
|
||||||
# apple pls
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# derived
|
# derived
|
||||||
copyparty/res/COPYING.txt
|
copyparty/res/COPYING.txt
|
||||||
copyparty/web/deps/
|
copyparty/web/deps/
|
||||||
srv/
|
srv/
|
||||||
scripts/docker/i/
|
|
||||||
scripts/deps-docker/uncomment.py
|
|
||||||
contrib/package/arch/pkg/
|
|
||||||
contrib/package/arch/src/
|
|
||||||
|
|
||||||
# state/logs
|
# state/logs
|
||||||
up.*.txt
|
up.*.txt
|
||||||
.hist/
|
.hist/
|
||||||
scripts/docker/*.out
|
|
||||||
scripts/docker/*.err
|
|
||||||
/perf.*
|
|
||||||
|
|
||||||
# nix build output link
|
|
||||||
result
|
|
||||||
|
|
||||||
# IDEA config
|
|
||||||
.idea/
|
|
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
|
@ -9,17 +9,14 @@
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"justMyCode": false,
|
"justMyCode": false,
|
||||||
"env": {
|
|
||||||
"PYDEVD_DISABLE_FILE_VALIDATION": "1",
|
|
||||||
"PYTHONWARNINGS": "always", //error
|
|
||||||
},
|
|
||||||
"args": [
|
"args": [
|
||||||
//"-nw",
|
//"-nw",
|
||||||
"-ed",
|
"-ed",
|
||||||
"-emp",
|
"-emp",
|
||||||
"-e2dsa",
|
"-e2dsa",
|
||||||
"-e2ts",
|
"-e2ts",
|
||||||
"-mtp=.bpm=f,bin/mtag/audio-bpm.py",
|
"-mtp",
|
||||||
|
".bpm=f,bin/mtag/audio-bpm.py",
|
||||||
"-aed:wark",
|
"-aed:wark",
|
||||||
"-vsrv::r:rw,ed:c,dupe",
|
"-vsrv::r:rw,ed:c,dupe",
|
||||||
"-vdist:dist:r"
|
"-vdist:dist:r"
|
||||||
|
|
12
.vscode/launch.py
vendored
12
.vscode/launch.py
vendored
|
@ -30,18 +30,10 @@ except:
|
||||||
|
|
||||||
argv = [os.path.expanduser(x) if x.startswith("~") else x for x in argv]
|
argv = [os.path.expanduser(x) if x.startswith("~") else x for x in argv]
|
||||||
|
|
||||||
sfx = ""
|
|
||||||
if len(sys.argv) > 1 and os.path.isfile(sys.argv[1]):
|
|
||||||
sfx = sys.argv[1]
|
|
||||||
sys.argv = [sys.argv[0]] + sys.argv[2:]
|
|
||||||
|
|
||||||
argv += sys.argv[1:]
|
argv += sys.argv[1:]
|
||||||
|
|
||||||
if sfx:
|
if re.search(" -j ?[0-9]", " ".join(argv)):
|
||||||
argv = [sys.executable, sfx] + argv
|
argv = [sys.executable, "-m", "copyparty"] + argv
|
||||||
sp.check_call(argv)
|
|
||||||
elif re.search(" -j ?[0-9]", " ".join(argv)):
|
|
||||||
argv = [sys.executable, "-Wa", "-m", "copyparty"] + argv
|
|
||||||
sp.check_call(argv)
|
sp.check_call(argv)
|
||||||
else:
|
else:
|
||||||
sys.path.insert(0, os.getcwd())
|
sys.path.insert(0, os.getcwd())
|
||||||
|
|
44
.vscode/settings.json
vendored
44
.vscode/settings.json
vendored
|
@ -22,9 +22,6 @@
|
||||||
"terminal.ansiBrightCyan": "#9cf0ed",
|
"terminal.ansiBrightCyan": "#9cf0ed",
|
||||||
"terminal.ansiBrightWhite": "#ffffff",
|
"terminal.ansiBrightWhite": "#ffffff",
|
||||||
},
|
},
|
||||||
"python.terminal.activateEnvironment": false,
|
|
||||||
"python.analysis.enablePytestSupport": false,
|
|
||||||
"python.analysis.typeCheckingMode": "standard",
|
|
||||||
"python.testing.pytestEnabled": false,
|
"python.testing.pytestEnabled": false,
|
||||||
"python.testing.unittestEnabled": true,
|
"python.testing.unittestEnabled": true,
|
||||||
"python.testing.unittestArgs": [
|
"python.testing.unittestArgs": [
|
||||||
|
@ -34,8 +31,39 @@
|
||||||
"-p",
|
"-p",
|
||||||
"test_*.py"
|
"test_*.py"
|
||||||
],
|
],
|
||||||
// python3 -m isort --py=27 --profile=black ~/dev/copyparty/{copyparty,tests}/*.py && python3 -m black -t py27 ~/dev/copyparty/{copyparty,tests,bin}/*.py $(find ~/dev/copyparty/copyparty/stolen -iname '*.py')
|
"python.linting.pylintEnabled": true,
|
||||||
"editor.formatOnSave": false,
|
"python.linting.flake8Enabled": true,
|
||||||
|
"python.linting.banditEnabled": true,
|
||||||
|
"python.linting.mypyEnabled": true,
|
||||||
|
"python.linting.mypyArgs": [
|
||||||
|
"--ignore-missing-imports",
|
||||||
|
"--follow-imports=silent",
|
||||||
|
"--show-column-numbers",
|
||||||
|
"--strict"
|
||||||
|
],
|
||||||
|
"python.linting.flake8Args": [
|
||||||
|
"--max-line-length=120",
|
||||||
|
"--ignore=E722,F405,E203,W503,W293,E402,E501,E128",
|
||||||
|
],
|
||||||
|
"python.linting.banditArgs": [
|
||||||
|
"--ignore=B104"
|
||||||
|
],
|
||||||
|
"python.linting.pylintArgs": [
|
||||||
|
"--disable=missing-module-docstring",
|
||||||
|
"--disable=missing-class-docstring",
|
||||||
|
"--disable=missing-function-docstring",
|
||||||
|
"--disable=import-outside-toplevel",
|
||||||
|
"--disable=wrong-import-position",
|
||||||
|
"--disable=raise-missing-from",
|
||||||
|
"--disable=bare-except",
|
||||||
|
"--disable=broad-except",
|
||||||
|
"--disable=invalid-name",
|
||||||
|
"--disable=line-too-long",
|
||||||
|
"--disable=consider-using-f-string"
|
||||||
|
],
|
||||||
|
// python3 -m isort --py=27 --profile=black copyparty/
|
||||||
|
"python.formatting.provider": "black",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.autoIndent": "keep",
|
"editor.autoIndent": "keep",
|
||||||
|
@ -46,4 +74,10 @@
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.makefile": "makefile"
|
"*.makefile": "makefile"
|
||||||
},
|
},
|
||||||
|
"python.formatting.blackArgs": [
|
||||||
|
"-t",
|
||||||
|
"py27"
|
||||||
|
],
|
||||||
|
"python.linting.enabled": true,
|
||||||
|
"python.pythonPath": "/usr/bin/python3"
|
||||||
}
|
}
|
1
.vscode/tasks.json
vendored
1
.vscode/tasks.json
vendored
|
@ -11,7 +11,6 @@
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "${config:python.pythonPath}",
|
"command": "${config:python.pythonPath}",
|
||||||
"args": [
|
"args": [
|
||||||
"-Wa", //-We
|
|
||||||
".vscode/launch.py"
|
".vscode/launch.py"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +1,3 @@
|
||||||
* **found a bug?** [create an issue!](https://github.com/9001/copyparty/issues) or let me know in the [discord](https://discord.gg/25J8CdTT6G) :>
|
* do something cool
|
||||||
* **fixed a bug?** create a PR or post a patch! big thx in advance :>
|
|
||||||
* **have a cool idea?** let's discuss it! anywhere's fine, you choose.
|
|
||||||
|
|
||||||
but please:
|
really tho, send a PR or an issue or whatever, all appreciated, anything goes, just behave aight
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# do not use AI / LMM when writing code
|
|
||||||
|
|
||||||
copyparty is 100% organic, free-range, human-written software!
|
|
||||||
|
|
||||||
> ⚠ you are now entering a no-copilot zone
|
|
||||||
|
|
||||||
the *only* place where LMM/AI *may* be accepted is for [localization](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#translations) if you are fluent and have confirmed that the translation is accurate.
|
|
||||||
|
|
||||||
sorry for the harsh tone, but this is important to me 🙏
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# contribution ideas
|
|
||||||
|
|
||||||
|
|
||||||
## documentation
|
|
||||||
|
|
||||||
I think we can agree that the documentation leaves a LOT to be desired. I've realized I'm not exactly qualified for this 😅 but maybe the [soon-to-come setup GUI](https://github.com/9001/copyparty/issues/57) will make this more manageable. The best documentation is the one that never had to be written, right? :> so I suppose we can give this a wait-and-see approach for a bit longer.
|
|
||||||
|
|
||||||
|
|
||||||
## crazy ideas & features
|
|
||||||
|
|
||||||
assuming they won't cause too much problems or side-effects :>
|
|
||||||
|
|
||||||
i think someone was working on a way to list directories over DNS for example...
|
|
||||||
|
|
||||||
if you wanna have a go at coding it up yourself then maybe mention the idea on discord before you get too far, otherwise just go nuts 👍
|
|
||||||
|
|
||||||
|
|
||||||
## others
|
|
||||||
|
|
||||||
aside from documentation and ideas, some other things that would be cool to have some help with is:
|
|
||||||
|
|
||||||
* **translations** -- the copyparty web-UI has translations for english and norwegian at the top of [browser.js](https://github.com/9001/copyparty/blob/hovudstraum/copyparty/web/browser.js); if you'd like to add a translation for another language then that'd be welcome! and if that language has a grammar that doesn't fit into the way the strings are assembled, then we'll fix that as we go :>
|
|
||||||
|
|
||||||
* but please note that support for [RTL (Right-to-Left) languages](https://en.wikipedia.org/wiki/Right-to-left_script) is currently not planned, since the javascript is a bit too jank for that
|
|
||||||
|
|
||||||
* **UI ideas** -- at some point I was thinking of rewriting the UI in react/preact/something-not-vanilla-javascript, but I'll admit the comfiness of not having any build stage combined with raw performance has kinda convinced me otherwise :p but I'd be very open to ideas on how the UI could be improved, or be more intuitive.
|
|
||||||
|
|
||||||
* **docker improvements** -- I don't really know what I'm doing when it comes to containers, so I'm sure there's a *huge* room for improvement here, mainly regarding how you're supposed to use the container with kubernetes / docker-compose / any of the other popular ways to do things. At some point I swear I'll start learning about docker so I can pick up clach04's [docker-compose draft](https://github.com/9001/copyparty/issues/38) and learn how that stuff ticks, unless someone beats me to it!
|
|
||||||
|
|
||||||
* **packaging** for various linux distributions -- this could either be as simple as just plopping the sfx.py in the right place and calling that from systemd (the archlinux package [originally did this](https://github.com/9001/copyparty/pull/18)); maybe with a small config-file which would cause copyparty to load settings from `/etc/copyparty.d` (like the [archlinux package](https://github.com/9001/copyparty/tree/hovudstraum/contrib/package/arch) does with `copyparty.conf`), or it could be a proper installation of the copyparty python package into /usr/lib or similar (the archlinux package [eventually went for this approach](https://github.com/9001/copyparty/pull/26))
|
|
||||||
|
|
||||||
* [fpm](https://github.com/jordansissel/fpm) can probably help with the technical part of it, but someone needs to handle distro relations :-)
|
|
||||||
|
|
||||||
* **software integration** -- I'm sure there's a lot of usecases where copyparty could complement something else, or the other way around, so any ideas or any work in this regard would be dope. This doesn't necessarily have to be code inside copyparty itself;
|
|
||||||
|
|
||||||
* [hooks](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks) -- these are small programs which are called by copyparty when certain things happen (files are uploaded, someone hits a 404, etc.), and could be a fun way to add support for more usecases
|
|
||||||
|
|
||||||
* [parser plugins](https://github.com/9001/copyparty/tree/hovudstraum/bin/mtag) -- if you want to have copyparty analyze and index metadata for some oddball file-formats, then additional plugins would be neat :>
|
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 ed <oss@ocv.me>
|
Copyright (c) 2019 ed
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# [`u2c.py`](u2c.py)
|
# [`up2k.py`](up2k.py)
|
||||||
* command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
|
* command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
|
||||||
* file uploads, file-search, autoresume of aborted/broken uploads
|
* file uploads, file-search, autoresume of aborted/broken uploads
|
||||||
* sync local folder to server
|
* sync local folder to server
|
||||||
|
@ -15,18 +15,22 @@ produces a chronological list of all uploads by collecting info from up2k databa
|
||||||
# [`partyfuse.py`](partyfuse.py)
|
# [`partyfuse.py`](partyfuse.py)
|
||||||
* mount a copyparty server as a local filesystem (read-only)
|
* mount a copyparty server as a local filesystem (read-only)
|
||||||
* **supports Windows!** -- expect `194 MiB/s` sequential read
|
* **supports Windows!** -- expect `194 MiB/s` sequential read
|
||||||
* **supports Linux** -- expect `600 MiB/s` sequential read
|
* **supports Linux** -- expect `117 MiB/s` sequential read
|
||||||
* **supports macos** -- expect `85 MiB/s` sequential read
|
* **supports macos** -- expect `85 MiB/s` sequential read
|
||||||
|
|
||||||
|
filecache is default-on for windows and macos;
|
||||||
|
* macos readsize is 64kB, so speed ~32 MiB/s without the cache
|
||||||
|
* windows readsize varies by software; explorer=1M, pv=32k
|
||||||
|
|
||||||
note that copyparty should run with `-ed` to enable dotfiles (hidden otherwise)
|
note that copyparty should run with `-ed` to enable dotfiles (hidden otherwise)
|
||||||
|
|
||||||
and consider using [../docs/rclone.md](../docs/rclone.md) instead; usually a bit faster, especially on windows
|
also consider using [../docs/rclone.md](../docs/rclone.md) instead for 5x performance
|
||||||
|
|
||||||
|
|
||||||
## to run this on windows:
|
## to run this on windows:
|
||||||
* install [winfsp](https://github.com/billziss-gh/winfsp/releases/latest) and [python 3](https://www.python.org/downloads/)
|
* install [winfsp](https://github.com/billziss-gh/winfsp/releases/latest) and [python 3](https://www.python.org/downloads/)
|
||||||
* [x] add python 3.x to PATH (it asks during install)
|
* [x] add python 3.x to PATH (it asks during install)
|
||||||
* `python -m pip install --user fusepy` (or grab a copy of `fuse.py` from the `connect` page on your copyparty, and keep it in the same folder)
|
* `python -m pip install --user fusepy`
|
||||||
* `python ./partyfuse.py n: http://192.168.1.69:3923/`
|
* `python ./partyfuse.py n: http://192.168.1.69:3923/`
|
||||||
|
|
||||||
10% faster in [msys2](https://www.msys2.org/), 700% faster if debug prints are enabled:
|
10% faster in [msys2](https://www.msys2.org/), 700% faster if debug prints are enabled:
|
||||||
|
@ -78,6 +82,3 @@ cd /mnt/nas/music/.hist
|
||||||
# [`prisonparty.sh`](prisonparty.sh)
|
# [`prisonparty.sh`](prisonparty.sh)
|
||||||
* run copyparty in a chroot, preventing any accidental file access
|
* run copyparty in a chroot, preventing any accidental file access
|
||||||
* creates bindmounts for /bin, /lib, and so on, see `sysdirs=`
|
* creates bindmounts for /bin, /lib, and so on, see `sysdirs=`
|
||||||
|
|
||||||
# [`bubbleparty.sh`](bubbleparty.sh)
|
|
||||||
* run copyparty in an isolated process, preventing any accidental file access and more
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# usage: ./bubbleparty.sh ./copyparty-sfx.py ....
|
|
||||||
bwrap \
|
|
||||||
--unshare-all \
|
|
||||||
--ro-bind /usr /usr \
|
|
||||||
--ro-bind /bin /bin \
|
|
||||||
--ro-bind /lib /lib \
|
|
||||||
--ro-bind /etc/resolv.conf /etc/resolv.conf \
|
|
||||||
--dev-bind /dev /dev \
|
|
||||||
--dir /tmp \
|
|
||||||
--dir /var \
|
|
||||||
--bind $(pwd) $(pwd) \
|
|
||||||
--share-net \
|
|
||||||
--die-with-parent \
|
|
||||||
--file 11 /etc/passwd \
|
|
||||||
--file 12 /etc/group \
|
|
||||||
"$@" \
|
|
||||||
11< <(getent passwd $(id -u) 65534) \
|
|
||||||
12< <(getent group $(id -g) 65534)
|
|
|
@ -8,7 +8,7 @@ import sqlite3
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
DB_VER1 = 3
|
DB_VER1 = 3
|
||||||
DB_VER2 = 6
|
DB_VER2 = 5
|
||||||
|
|
||||||
BY_PATH = None
|
BY_PATH = None
|
||||||
NC = None
|
NC = None
|
||||||
|
@ -39,7 +39,7 @@ def ls(db):
|
||||||
print(f"{nfiles} files")
|
print(f"{nfiles} files")
|
||||||
print(f"{ntags} tags\n")
|
print(f"{ntags} tags\n")
|
||||||
|
|
||||||
print("number of occurrences for each tag,")
|
print("number of occurences for each tag,")
|
||||||
print(" 'x' = file has no tags")
|
print(" 'x' = file has no tags")
|
||||||
print(" 't:mtp' = the mtp flag (file not mtp processed yet)")
|
print(" 't:mtp' = the mtp flag (file not mtp processed yet)")
|
||||||
print()
|
print()
|
||||||
|
@ -207,7 +207,7 @@ def examples():
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global NC, BY_PATH # pylint: disable=global-statement
|
global NC, BY_PATH
|
||||||
os.system("")
|
os.system("")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
@ -282,8 +282,7 @@ def main():
|
||||||
if ver == "corrupt":
|
if ver == "corrupt":
|
||||||
die("{} database appears to be corrupt, sorry")
|
die("{} database appears to be corrupt, sorry")
|
||||||
|
|
||||||
iver = int(ver)
|
if ver < DB_VER1 or ver > DB_VER2:
|
||||||
if iver < DB_VER1 or iver > DB_VER2:
|
|
||||||
m = f"{n} db is version {ver}, this tool only supports versions between {DB_VER1} and {DB_VER2}, please upgrade it with copyparty first"
|
m = f"{n} db is version {ver}, this tool only supports versions between {DB_VER1} and {DB_VER2}, please upgrade it with copyparty first"
|
||||||
die(m)
|
die(m)
|
||||||
|
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
replace the standard 404 / 403 responses with plugins
|
|
||||||
|
|
||||||
|
|
||||||
# usage
|
|
||||||
|
|
||||||
load plugins either globally with `--on404 ~/dev/copyparty/bin/handlers/sorry.py` or for a specific volume with `:c,on404=~/handlers/sorry.py`
|
|
||||||
|
|
||||||
|
|
||||||
# api
|
|
||||||
|
|
||||||
each plugin must define a `main()` which takes 3 arguments;
|
|
||||||
|
|
||||||
* `cli` is an instance of [copyparty/httpcli.py](https://github.com/9001/copyparty/blob/hovudstraum/copyparty/httpcli.py) (the monstrosity itself)
|
|
||||||
* `vn` is the VFS which overlaps with the requested URL, and
|
|
||||||
* `rem` is the URL remainder below the VFS mountpoint
|
|
||||||
* so `vn.vpath + rem` == `cli.vpath` == original request
|
|
||||||
|
|
||||||
|
|
||||||
# examples
|
|
||||||
|
|
||||||
## on404
|
|
||||||
|
|
||||||
* [redirect.py](redirect.py) sends an HTTP 301 or 302, redirecting the client to another page/file
|
|
||||||
* [randpic.py](randpic.py) redirects `/foo/bar/randpic.jpg` to a random pic in `/foo/bar/`
|
|
||||||
* [sorry.py](answer.py) replies with a custom message instead of the usual 404
|
|
||||||
* [nooo.py](nooo.py) replies with an endless noooooooooooooo
|
|
||||||
* [never404.py](never404.py) 100% guarantee that 404 will never be a thing again as it automatically creates dummy files whenever necessary
|
|
||||||
* [caching-proxy.py](caching-proxy.py) transforms copyparty into a squid/varnish knockoff
|
|
||||||
|
|
||||||
## on403
|
|
||||||
|
|
||||||
* [ip-ok.py](ip-ok.py) disables security checks if client-ip is 1.2.3.4
|
|
||||||
|
|
||||||
|
|
||||||
# notes
|
|
||||||
|
|
||||||
* on403 only works for trivial stuff (basic http access) since I haven't been able to think of any good usecases for it (was just easy to add while doing on404)
|
|
|
@ -1,36 +0,0 @@
|
||||||
# assume each requested file exists on another webserver and
|
|
||||||
# download + mirror them as they're requested
|
|
||||||
# (basically pretend we're warnish)
|
|
||||||
|
|
||||||
import os
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from copyparty.httpcli import HttpCli
|
|
||||||
|
|
||||||
|
|
||||||
def main(cli: "HttpCli", vn, rem):
|
|
||||||
url = "https://mirrors.edge.kernel.org/alpine/" + rem
|
|
||||||
abspath = os.path.join(vn.realpath, rem)
|
|
||||||
|
|
||||||
# sneaky trick to preserve a requests-session between downloads
|
|
||||||
# so it doesn't have to spend ages reopening https connections;
|
|
||||||
# luckily we can stash it inside the copyparty client session,
|
|
||||||
# name just has to be definitely unused so "hacapo_req_s" it is
|
|
||||||
req_s = getattr(cli.conn, "hacapo_req_s", None) or requests.Session()
|
|
||||||
setattr(cli.conn, "hacapo_req_s", req_s)
|
|
||||||
|
|
||||||
try:
|
|
||||||
os.makedirs(os.path.dirname(abspath), exist_ok=True)
|
|
||||||
with req_s.get(url, stream=True, timeout=69) as r:
|
|
||||||
r.raise_for_status()
|
|
||||||
with open(abspath, "wb", 64 * 1024) as f:
|
|
||||||
for buf in r.iter_content(chunk_size=64 * 1024):
|
|
||||||
f.write(buf)
|
|
||||||
except:
|
|
||||||
os.unlink(abspath)
|
|
||||||
return "false"
|
|
||||||
|
|
||||||
return "retry"
|
|
|
@ -1,6 +0,0 @@
|
||||||
# disable permission checks and allow access if client-ip is 1.2.3.4
|
|
||||||
|
|
||||||
|
|
||||||
def main(cli, vn, rem):
|
|
||||||
if cli.ip == "1.2.3.4":
|
|
||||||
return "allow"
|
|
|
@ -1,11 +0,0 @@
|
||||||
# create a dummy file and let copyparty return it
|
|
||||||
|
|
||||||
|
|
||||||
def main(cli, vn, rem):
|
|
||||||
print("hello", cli.ip)
|
|
||||||
|
|
||||||
abspath = vn.canonical(rem)
|
|
||||||
with open(abspath, "wb") as f:
|
|
||||||
f.write(b"404? not on MY watch!")
|
|
||||||
|
|
||||||
return "retry"
|
|
|
@ -1,16 +0,0 @@
|
||||||
# reply with an endless "noooooooooooooooooooooooo"
|
|
||||||
|
|
||||||
|
|
||||||
def say_no():
|
|
||||||
yield b"n"
|
|
||||||
while True:
|
|
||||||
yield b"o" * 4096
|
|
||||||
|
|
||||||
|
|
||||||
def main(cli, vn, rem):
|
|
||||||
cli.send_headers(None, 404, "text/plain")
|
|
||||||
|
|
||||||
for chunk in say_no():
|
|
||||||
cli.s.sendall(chunk)
|
|
||||||
|
|
||||||
return "false"
|
|
|
@ -1,35 +0,0 @@
|
||||||
import os
|
|
||||||
import random
|
|
||||||
from urllib.parse import quote
|
|
||||||
|
|
||||||
|
|
||||||
# assuming /foo/bar/ is a valid URL but /foo/bar/randpic.png does not exist,
|
|
||||||
# hijack the 404 with a redirect to a random pic in that folder
|
|
||||||
#
|
|
||||||
# thx to lia & kipu for the idea
|
|
||||||
|
|
||||||
|
|
||||||
def main(cli, vn, rem):
|
|
||||||
req_fn = rem.split("/")[-1]
|
|
||||||
if not cli.can_read or not req_fn.startswith("randpic"):
|
|
||||||
return
|
|
||||||
|
|
||||||
req_abspath = vn.canonical(rem)
|
|
||||||
req_ap_dir = os.path.dirname(req_abspath)
|
|
||||||
files_in_dir = os.listdir(req_ap_dir)
|
|
||||||
|
|
||||||
if "." in req_fn:
|
|
||||||
file_ext = "." + req_fn.split(".")[-1]
|
|
||||||
files_in_dir = [x for x in files_in_dir if x.lower().endswith(file_ext)]
|
|
||||||
|
|
||||||
if not files_in_dir:
|
|
||||||
return
|
|
||||||
|
|
||||||
selected_file = random.choice(files_in_dir)
|
|
||||||
|
|
||||||
req_url = "/".join([vn.vpath, rem]).strip("/")
|
|
||||||
req_dir = req_url.rsplit("/", 1)[0]
|
|
||||||
new_url = "/".join([req_dir, quote(selected_file)]).strip("/")
|
|
||||||
|
|
||||||
cli.reply(b"redirecting...", 302, headers={"Location": "/" + new_url})
|
|
||||||
return "true"
|
|
|
@ -1,52 +0,0 @@
|
||||||
# if someone hits a 404, redirect them to another location
|
|
||||||
|
|
||||||
|
|
||||||
def send_http_302_temporary_redirect(cli, new_path):
|
|
||||||
"""
|
|
||||||
replies with an HTTP 302, which is a temporary redirect;
|
|
||||||
"new_path" can be any of the following:
|
|
||||||
- "http://a.com/" would redirect to another website,
|
|
||||||
- "/foo/bar" would redirect to /foo/bar on the same server;
|
|
||||||
note the leading '/' in the location which is important
|
|
||||||
"""
|
|
||||||
cli.reply(b"redirecting...", 302, headers={"Location": new_path})
|
|
||||||
|
|
||||||
|
|
||||||
def send_http_301_permanent_redirect(cli, new_path):
|
|
||||||
"""
|
|
||||||
replies with an HTTP 301, which is a permanent redirect;
|
|
||||||
otherwise identical to send_http_302_temporary_redirect
|
|
||||||
"""
|
|
||||||
cli.reply(b"redirecting...", 301, headers={"Location": new_path})
|
|
||||||
|
|
||||||
|
|
||||||
def send_errorpage_with_redirect_link(cli, new_path):
|
|
||||||
"""
|
|
||||||
replies with a website explaining that the page has moved;
|
|
||||||
"new_path" must be an absolute location on the same server
|
|
||||||
but without a leading '/', so for example "foo/bar"
|
|
||||||
would redirect to "/foo/bar"
|
|
||||||
"""
|
|
||||||
cli.redirect(new_path, click=False, msg="this page has moved")
|
|
||||||
|
|
||||||
|
|
||||||
def main(cli, vn, rem):
|
|
||||||
"""
|
|
||||||
this is the function that gets called by copyparty;
|
|
||||||
note that vn.vpath and cli.vpath does not have a leading '/'
|
|
||||||
so we're adding the slash in the debug messages below
|
|
||||||
"""
|
|
||||||
print(f"this client just hit a 404: {cli.ip}")
|
|
||||||
print(f"they were accessing this volume: /{vn.vpath}")
|
|
||||||
print(f"and the original request-path (straight from the URL) was /{cli.vpath}")
|
|
||||||
print(f"...which resolves to the following filesystem path: {vn.canonical(rem)}")
|
|
||||||
|
|
||||||
new_path = "/foo/bar/"
|
|
||||||
print(f"will now redirect the client to {new_path}")
|
|
||||||
|
|
||||||
# uncomment one of these:
|
|
||||||
send_http_302_temporary_redirect(cli, new_path)
|
|
||||||
# send_http_301_permanent_redirect(cli, new_path)
|
|
||||||
# send_errorpage_with_redirect_link(cli, new_path)
|
|
||||||
|
|
||||||
return "true"
|
|
|
@ -1,7 +0,0 @@
|
||||||
# sends a custom response instead of the usual 404
|
|
||||||
|
|
||||||
|
|
||||||
def main(cli, vn, rem):
|
|
||||||
msg = f"sorry {cli.ip} but {cli.vpath} doesn't exist"
|
|
||||||
|
|
||||||
return str(cli.reply(msg.encode("utf-8"), 404, "text/plain"))
|
|
|
@ -2,36 +2,18 @@ standalone programs which are executed by copyparty when an event happens (uploa
|
||||||
|
|
||||||
these programs either take zero arguments, or a filepath (the affected file), or a json message with filepath + additional info
|
these programs either take zero arguments, or a filepath (the affected file), or a json message with filepath + additional info
|
||||||
|
|
||||||
run copyparty with `--help-hooks` for usage details / hook type explanations (xm/xbu/xau/xiu/xbc/xac/xbr/xar/xbd/xad/xban)
|
|
||||||
|
|
||||||
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
|
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
|
||||||
|
|
||||||
|
|
||||||
# after upload
|
# after upload
|
||||||
* [notify.py](notify.py) shows a desktop notification ([example](https://user-images.githubusercontent.com/241032/215335767-9c91ed24-d36e-4b6b-9766-fb95d12d163f.png))
|
* [notify.py](notify.py) shows a desktop notification ([example](https://user-images.githubusercontent.com/241032/215335767-9c91ed24-d36e-4b6b-9766-fb95d12d163f.png))
|
||||||
* [notify2.py](notify2.py) uses the json API to show more context
|
|
||||||
* [image-noexif.py](image-noexif.py) removes image exif by overwriting / directly editing the uploaded file
|
|
||||||
* [discord-announce.py](discord-announce.py) announces new uploads on discord using webhooks ([example](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png))
|
* [discord-announce.py](discord-announce.py) announces new uploads on discord using webhooks ([example](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png))
|
||||||
* [reject-mimetype.py](reject-mimetype.py) rejects uploads unless the mimetype is acceptable
|
* [reject-mimetype.py](reject-mimetype.py) rejects uploads unless the mimetype is acceptable
|
||||||
* [into-the-cache-it-goes.py](into-the-cache-it-goes.py) avoids bugs in caching proxies by immediately downloading each file that is uploaded
|
|
||||||
* [podcast-normalizer.py](podcast-normalizer.py) creates a second file with dynamic-range-compression whenever an audio file is uploaded
|
|
||||||
* good example of the `idx` [hook effect](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#hook-effects) to tell copyparty about additional files to scan/index
|
|
||||||
|
|
||||||
|
|
||||||
# upload batches
|
|
||||||
these are `--xiu` hooks; unlike `xbu` and `xau` (which get executed on every single file), `xiu` hooks are given a list of recent uploads on STDIN after the server has gone idle for N seconds, reducing server load + providing more context
|
|
||||||
* [xiu.py](xiu.py) is a "minimal" example showing a list of filenames + total filesize
|
|
||||||
* [xiu-sha.py](xiu-sha.py) produces a sha512 checksum list in the volume root
|
|
||||||
|
|
||||||
|
|
||||||
# before upload
|
# before upload
|
||||||
* [reject-extension.py](reject-extension.py) rejects uploads if they match a list of file extensions
|
* [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)
|
|
||||||
|
|
||||||
|
|
||||||
# on message
|
# on message
|
||||||
* [wget.py](wget.py) lets you download files by POSTing URLs to copyparty
|
* [wget.py](wget.py) lets you download files by POSTing URLs to copyparty
|
||||||
* [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
|
|
||||||
|
|
|
@ -12,28 +12,13 @@ announces a new upload on discord
|
||||||
example usage as global config:
|
example usage as global config:
|
||||||
--xau f,t5,j,bin/hooks/discord-announce.py
|
--xau f,t5,j,bin/hooks/discord-announce.py
|
||||||
|
|
||||||
parameters explained,
|
|
||||||
xau = execute after upload
|
|
||||||
f = fork; don't delay other hooks while this is running
|
|
||||||
t5 = timeout if it's still running after 5 sec
|
|
||||||
j = this hook needs upload information as json (not just the filename)
|
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
example usage as a volflag (per-volume config):
|
||||||
-v srv/inc:inc:r:rw,ed:c,xau=f,t5,j,bin/hooks/discord-announce.py
|
-v srv/inc:inc:c,xau=f,t5,j,bin/hooks/discord-announce.py
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
(share filesystem-path srv/inc as volume /inc,
|
parameters explained,
|
||||||
readable by everyone, read-write for user 'ed',
|
f = fork; don't wait for it to finish
|
||||||
running this plugin on all uploads with the params explained above)
|
t5 = timeout if it's still running after 5 sec
|
||||||
|
j = provide upload information as json; not just the filename
|
||||||
example usage as a volflag in a copyparty config file:
|
|
||||||
[/inc]
|
|
||||||
srv/inc
|
|
||||||
accs:
|
|
||||||
r: *
|
|
||||||
rw: ed
|
|
||||||
flags:
|
|
||||||
xau: f,t5,j,bin/hooks/discord-announce.py
|
|
||||||
|
|
||||||
replace "xau" with "xbu" to announce Before upload starts instead of After completion
|
replace "xau" with "xbu" to announce Before upload starts instead of After completion
|
||||||
|
|
||||||
|
@ -45,7 +30,6 @@ then use this to design your message: https://discohook.org/
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
WEBHOOK = "https://discord.com/api/webhooks/1234/base64"
|
WEBHOOK = "https://discord.com/api/webhooks/1234/base64"
|
||||||
WEBHOOK = "https://discord.com/api/webhooks/1066830390280597718/M1TDD110hQA-meRLMRhdurych8iyG35LDoI1YhzbrjGP--BXNZodZFczNVwK4Ce7Yme5"
|
|
||||||
|
|
||||||
# read info from copyparty
|
# read info from copyparty
|
||||||
inf = json.loads(sys.argv[1])
|
inf = json.loads(sys.argv[1])
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import subprocess as sp
|
|
||||||
|
|
||||||
|
|
||||||
_ = r"""
|
|
||||||
remove exif tags from uploaded images; the eventhook edition of
|
|
||||||
https://github.com/9001/copyparty/blob/hovudstraum/bin/mtag/image-noexif.py
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
exiftool / perl-Image-ExifTool
|
|
||||||
|
|
||||||
being an upload hook, this will take effect after upload completion
|
|
||||||
but before copyparty has hashed/indexed the file, which means that
|
|
||||||
copyparty will never index the original file, so deduplication will
|
|
||||||
not work as expected... which is mostly OK but ehhh
|
|
||||||
|
|
||||||
note: modifies the file in-place, so don't set the `f` (fork) flag
|
|
||||||
|
|
||||||
example usages; either as global config (all volumes) or as volflag:
|
|
||||||
--xau bin/hooks/image-noexif.py
|
|
||||||
-v srv/inc:inc:r:rw,ed:c,xau=bin/hooks/image-noexif.py
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
explained:
|
|
||||||
share fs-path srv/inc at /inc (readable by all, read-write for user ed)
|
|
||||||
running this xau (execute-after-upload) plugin for all uploaded files
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# filetypes to process; ignores everything else
|
|
||||||
EXTS = ("jpg", "jpeg", "avif", "heif", "heic")
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from copyparty.util import fsenc
|
|
||||||
except:
|
|
||||||
|
|
||||||
def fsenc(p):
|
|
||||||
return p.encode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
fp = sys.argv[1]
|
|
||||||
ext = fp.lower().split(".")[-1]
|
|
||||||
if ext not in EXTS:
|
|
||||||
return
|
|
||||||
|
|
||||||
cwd, fn = os.path.split(fp)
|
|
||||||
os.chdir(cwd)
|
|
||||||
f1 = fsenc(fn)
|
|
||||||
cmd = [
|
|
||||||
b"exiftool",
|
|
||||||
b"-exif:all=",
|
|
||||||
b"-iptc:all=",
|
|
||||||
b"-xmp:all=",
|
|
||||||
b"-P",
|
|
||||||
b"-overwrite_original",
|
|
||||||
b"--",
|
|
||||||
f1,
|
|
||||||
]
|
|
||||||
sp.check_output(cmd)
|
|
||||||
print("image-noexif: stripped")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
main()
|
|
||||||
except:
|
|
||||||
pass
|
|
|
@ -1,140 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import shutil
|
|
||||||
import platform
|
|
||||||
import subprocess as sp
|
|
||||||
from urllib.parse import quote
|
|
||||||
|
|
||||||
|
|
||||||
_ = r"""
|
|
||||||
try to avoid race conditions in caching proxies
|
|
||||||
(primarily cloudflare, but probably others too)
|
|
||||||
by means of the most obvious solution possible:
|
|
||||||
|
|
||||||
just as each file has finished uploading, use
|
|
||||||
the server's external URL to download the file
|
|
||||||
so that it ends up in the cache, warm and snug
|
|
||||||
|
|
||||||
this intentionally delays the upload response
|
|
||||||
as it waits for the file to finish downloading
|
|
||||||
before copyparty is allowed to return the URL
|
|
||||||
|
|
||||||
NOTE: you must edit this script before use,
|
|
||||||
replacing https://example.com with your URL
|
|
||||||
|
|
||||||
NOTE: if the files are only accessible with a
|
|
||||||
password and/or filekey, you must also add
|
|
||||||
a cromulent password in the PASSWORD field
|
|
||||||
|
|
||||||
NOTE: needs either wget, curl, or "requests":
|
|
||||||
python3 -m pip install --user -U requests
|
|
||||||
|
|
||||||
|
|
||||||
example usage as global config:
|
|
||||||
--xau j,t10,bin/hooks/into-the-cache-it-goes.py
|
|
||||||
|
|
||||||
parameters explained,
|
|
||||||
xau = execute after upload
|
|
||||||
j = this hook needs upload information as json (not just the filename)
|
|
||||||
t10 = abort download and continue if it takes longer than 10sec
|
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
|
||||||
-v srv/inc:inc:r:rw,ed:c,xau=j,t10,bin/hooks/into-the-cache-it-goes.py
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
(share filesystem-path srv/inc as volume /inc,
|
|
||||||
readable by everyone, read-write for user 'ed',
|
|
||||||
running this plugin on all uploads with params explained above)
|
|
||||||
|
|
||||||
example usage as a volflag in a copyparty config file:
|
|
||||||
[/inc]
|
|
||||||
srv/inc
|
|
||||||
accs:
|
|
||||||
r: *
|
|
||||||
rw: ed
|
|
||||||
flags:
|
|
||||||
xau: j,t10,bin/hooks/into-the-cache-it-goes.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# replace this with your site's external URL
|
|
||||||
# (including the :portnumber if necessary)
|
|
||||||
SITE_URL = "https://example.com"
|
|
||||||
|
|
||||||
# if downloading is protected by passwords or filekeys,
|
|
||||||
# specify a valid password between the quotes below:
|
|
||||||
PASSWORD = ""
|
|
||||||
|
|
||||||
# if file is larger than this, skip download
|
|
||||||
MAX_MEGABYTES = 8
|
|
||||||
|
|
||||||
# =============== END OF CONFIG ===============
|
|
||||||
|
|
||||||
|
|
||||||
WINDOWS = platform.system() == "Windows"
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
fun = download_with_python
|
|
||||||
if shutil.which("curl"):
|
|
||||||
fun = download_with_curl
|
|
||||||
elif shutil.which("wget"):
|
|
||||||
fun = download_with_wget
|
|
||||||
|
|
||||||
inf = json.loads(sys.argv[1])
|
|
||||||
|
|
||||||
if inf["sz"] > 1024 * 1024 * MAX_MEGABYTES:
|
|
||||||
print("[into-the-cache] file is too large; will not download")
|
|
||||||
return
|
|
||||||
|
|
||||||
file_url = "/"
|
|
||||||
if inf["vp"]:
|
|
||||||
file_url += inf["vp"] + "/"
|
|
||||||
file_url += inf["ap"].replace("\\", "/").split("/")[-1]
|
|
||||||
file_url = SITE_URL.rstrip("/") + quote(file_url, safe=b"/")
|
|
||||||
|
|
||||||
print("[into-the-cache] %s(%s)" % (fun.__name__, file_url))
|
|
||||||
fun(file_url, PASSWORD.strip())
|
|
||||||
|
|
||||||
print("[into-the-cache] Download OK")
|
|
||||||
|
|
||||||
|
|
||||||
def download_with_curl(url, pw):
|
|
||||||
cmd = ["curl"]
|
|
||||||
|
|
||||||
if pw:
|
|
||||||
cmd += ["-HPW:%s" % (pw,)]
|
|
||||||
|
|
||||||
nah = sp.DEVNULL
|
|
||||||
sp.check_call(cmd + [url], stdout=nah, stderr=nah)
|
|
||||||
|
|
||||||
|
|
||||||
def download_with_wget(url, pw):
|
|
||||||
cmd = ["wget", "-O"]
|
|
||||||
|
|
||||||
cmd += ["nul" if WINDOWS else "/dev/null"]
|
|
||||||
|
|
||||||
if pw:
|
|
||||||
cmd += ["--header=PW:%s" % (pw,)]
|
|
||||||
|
|
||||||
nah = sp.DEVNULL
|
|
||||||
sp.check_call(cmd + [url], stdout=nah, stderr=nah)
|
|
||||||
|
|
||||||
|
|
||||||
def download_with_python(url, pw):
|
|
||||||
import requests
|
|
||||||
|
|
||||||
headers = {}
|
|
||||||
if pw:
|
|
||||||
headers["PW"] = pw
|
|
||||||
|
|
||||||
with requests.get(url, headers=headers, stream=True) as r:
|
|
||||||
r.raise_for_status()
|
|
||||||
for _ in r.iter_content(chunk_size=1024 * 256):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,136 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# coding: utf-8
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
from __future__ import print_function, unicode_literals
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
try:
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
except:
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
_ = r"""
|
|
||||||
use copyparty as a dumb messaging server / guestbook thing;
|
|
||||||
accepts guestbook entries from 📟 (message-to-server-log) in the web-ui
|
|
||||||
initially contributed by @clach04 in https://github.com/9001/copyparty/issues/35 (thanks!)
|
|
||||||
|
|
||||||
example usage as global config:
|
|
||||||
python copyparty-sfx.py --xm j,bin/hooks/msg-log.py
|
|
||||||
|
|
||||||
parameters explained,
|
|
||||||
xm = execute on message (📟)
|
|
||||||
j = this hook needs message information as json (not just the message-text)
|
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
|
||||||
python copyparty-sfx.py -v srv/log:log:r:c,xm=j,bin/hooks/msg-log.py
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
(share filesystem-path srv/log as volume /log, readable by everyone,
|
|
||||||
running this plugin on all messages with the params explained above)
|
|
||||||
|
|
||||||
example usage as a volflag in a copyparty config file:
|
|
||||||
[/log]
|
|
||||||
srv/log
|
|
||||||
accs:
|
|
||||||
r: *
|
|
||||||
flags:
|
|
||||||
xm: j,bin/hooks/msg-log.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# output filename
|
|
||||||
FILENAME = os.environ.get("COPYPARTY_MESSAGE_FILENAME", "") or "README.md"
|
|
||||||
|
|
||||||
# set True to write in descending order (newest message at top of file);
|
|
||||||
# note that this becomes very slow/expensive as the file gets bigger
|
|
||||||
DESCENDING = True
|
|
||||||
|
|
||||||
# the message template; the following parameters are provided by copyparty and can be referenced below:
|
|
||||||
# 'ap' = absolute filesystem path where the message was posted
|
|
||||||
# 'vp' = virtual path (URL 'path') where the message was posted
|
|
||||||
# 'mt' = 'at' = unix-timestamp when the message was posted
|
|
||||||
# 'datetime' = ISO-8601 time when the message was posted
|
|
||||||
# 'sz' = message size in bytes
|
|
||||||
# 'host' = the server hostname which the user was accessing (URL 'host')
|
|
||||||
# 'user' = username (if logged in), otherwise '*'
|
|
||||||
# 'txt' = the message text itself
|
|
||||||
# (uncomment the print(msg_info) to see if additional information has been introduced by copyparty since this was written)
|
|
||||||
TEMPLATE = """
|
|
||||||
🕒 %(datetime)s, 👤 %(user)s @ %(ip)s
|
|
||||||
%(txt)s
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def write_ascending(filepath, msg_text):
|
|
||||||
with open(filepath, "a", encoding="utf-8", errors="replace") as outfile:
|
|
||||||
outfile.write(msg_text)
|
|
||||||
|
|
||||||
|
|
||||||
def write_descending(filepath, msg_text):
|
|
||||||
lockpath = filepath + ".lock"
|
|
||||||
got_it = False
|
|
||||||
for _ in range(16):
|
|
||||||
try:
|
|
||||||
os.mkdir(lockpath)
|
|
||||||
got_it = True
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
time.sleep(0.1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not got_it:
|
|
||||||
return sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
oldpath = filepath + ".old"
|
|
||||||
os.rename(filepath, oldpath)
|
|
||||||
with open(oldpath, "r", encoding="utf-8", errors="replace") as infile, open(
|
|
||||||
filepath, "w", encoding="utf-8", errors="replace"
|
|
||||||
) as outfile:
|
|
||||||
outfile.write(msg_text)
|
|
||||||
while True:
|
|
||||||
buf = infile.read(4096)
|
|
||||||
if not buf:
|
|
||||||
break
|
|
||||||
outfile.write(buf)
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
os.unlink(oldpath)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
os.rmdir(lockpath)
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
|
||||||
if argv is None:
|
|
||||||
argv = sys.argv
|
|
||||||
|
|
||||||
msg_info = json.loads(sys.argv[1])
|
|
||||||
# print(msg_info)
|
|
||||||
|
|
||||||
try:
|
|
||||||
dt = datetime.fromtimestamp(msg_info["at"], timezone.utc)
|
|
||||||
except:
|
|
||||||
dt = datetime.utcfromtimestamp(msg_info["at"])
|
|
||||||
|
|
||||||
msg_info["datetime"] = dt.strftime("%Y-%m-%d, %H:%M:%S")
|
|
||||||
|
|
||||||
msg_text = TEMPLATE % msg_info
|
|
||||||
|
|
||||||
filepath = os.path.join(msg_info["ap"], FILENAME)
|
|
||||||
|
|
||||||
if DESCENDING and os.path.exists(filepath):
|
|
||||||
write_descending(filepath, msg_text)
|
|
||||||
else:
|
|
||||||
write_ascending(filepath, msg_text)
|
|
||||||
|
|
||||||
print(msg_text)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -9,7 +9,7 @@ from plyer import notification
|
||||||
_ = r"""
|
_ = r"""
|
||||||
show os notification on upload; works on windows, linux, macos, android
|
show os notification on upload; works on windows, linux, macos, android
|
||||||
|
|
||||||
dependencies:
|
depdencies:
|
||||||
windows: python3 -m pip install --user -U plyer
|
windows: python3 -m pip install --user -U plyer
|
||||||
linux: python3 -m pip install --user -U plyer
|
linux: python3 -m pip install --user -U plyer
|
||||||
macos: python3 -m pip install --user -U plyer pyobjus
|
macos: python3 -m pip install --user -U plyer pyobjus
|
||||||
|
@ -17,12 +17,8 @@ dependencies:
|
||||||
|
|
||||||
example usages; either as global config (all volumes) or as volflag:
|
example usages; either as global config (all volumes) or as volflag:
|
||||||
--xau f,bin/hooks/notify.py
|
--xau f,bin/hooks/notify.py
|
||||||
-v srv/inc:inc:r:rw,ed:c,xau=f,bin/hooks/notify.py
|
-v srv/inc:inc:c,xau=f,bin/hooks/notify.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)
|
|
||||||
|
|
||||||
parameters explained,
|
parameters explained,
|
||||||
xau = execute after upload
|
xau = execute after upload
|
||||||
|
@ -30,23 +26,9 @@ parameters explained,
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from copyparty.util import humansize
|
|
||||||
except:
|
|
||||||
|
|
||||||
def humansize(n):
|
|
||||||
return n
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
fp = sys.argv[1]
|
dp, fn = os.path.split(sys.argv[1])
|
||||||
dp, fn = os.path.split(fp)
|
msg = "🏷️ {}\n📁 {}".format(fn, dp)
|
||||||
try:
|
|
||||||
sz = humansize(os.path.getsize(fp))
|
|
||||||
except:
|
|
||||||
sz = "?"
|
|
||||||
|
|
||||||
msg = "{} ({})\n📁 {}".format(fn, sz, dp)
|
|
||||||
title = "File received"
|
title = "File received"
|
||||||
|
|
||||||
if "com.termux" in sys.executable:
|
if "com.termux" in sys.executable:
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import subprocess as sp
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
from plyer import notification
|
|
||||||
|
|
||||||
|
|
||||||
_ = r"""
|
|
||||||
same as notify.py but with additional info (uploader, ...)
|
|
||||||
and also supports --xm (notify on 📟 message)
|
|
||||||
|
|
||||||
example usages; either as global config (all volumes) or as volflag:
|
|
||||||
--xm f,j,bin/hooks/notify2.py
|
|
||||||
--xau f,j,bin/hooks/notify2.py
|
|
||||||
-v srv/inc:inc:r:rw,ed:c,xm=f,j,bin/hooks/notify2.py
|
|
||||||
-v srv/inc:inc:r:rw,ed:c,xau=f,j,bin/hooks/notify2.py
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
(share filesystem-path srv/inc as volume /inc,
|
|
||||||
readable by everyone, read-write for user 'ed',
|
|
||||||
running this plugin on all uploads / msgs with the params listed below)
|
|
||||||
|
|
||||||
parameters explained,
|
|
||||||
xau = execute after upload
|
|
||||||
f = fork so it doesn't block uploads
|
|
||||||
j = provide json instead of filepath list
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from copyparty.util import humansize
|
|
||||||
except:
|
|
||||||
|
|
||||||
def humansize(n):
|
|
||||||
return n
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
inf = json.loads(sys.argv[1])
|
|
||||||
fp = inf["ap"]
|
|
||||||
sz = humansize(inf["sz"])
|
|
||||||
dp, fn = os.path.split(fp)
|
|
||||||
dt = datetime.fromtimestamp(inf["mt"], timezone.utc)
|
|
||||||
mt = dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
|
|
||||||
msg = f"{fn} ({sz})\n📁 {dp}"
|
|
||||||
title = "File received"
|
|
||||||
icon = "emblem-documents-symbolic" if sys.platform == "linux" else ""
|
|
||||||
|
|
||||||
if inf.get("txt"):
|
|
||||||
msg = inf["txt"]
|
|
||||||
title = "Message received"
|
|
||||||
icon = "mail-unread-symbolic" if sys.platform == "linux" else ""
|
|
||||||
|
|
||||||
msg += f"\n👤 {inf['user']} ({inf['ip']})\n🕒 {mt}"
|
|
||||||
|
|
||||||
if "com.termux" in sys.executable:
|
|
||||||
sp.run(["termux-notification", "-t", title, "-c", msg])
|
|
||||||
return
|
|
||||||
|
|
||||||
notification.notify(
|
|
||||||
title=title,
|
|
||||||
message=msg,
|
|
||||||
app_icon=icon,
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,121 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import subprocess as sp
|
|
||||||
|
|
||||||
|
|
||||||
_ = r"""
|
|
||||||
sends all uploaded audio files through an aggressive
|
|
||||||
dynamic-range-compressor to even out the volume levels
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
ffmpeg
|
|
||||||
|
|
||||||
being an xau hook, this gets eXecuted After Upload completion
|
|
||||||
but before copyparty has started hashing/indexing the file, so
|
|
||||||
we'll create a second normalized copy in a subfolder and tell
|
|
||||||
copyparty to hash/index that additional file as well
|
|
||||||
|
|
||||||
example usage as global config:
|
|
||||||
-e2d -e2t --xau j,c1,bin/hooks/podcast-normalizer.py
|
|
||||||
|
|
||||||
parameters explained,
|
|
||||||
e2d/e2t = enable database and metadata indexing
|
|
||||||
xau = execute after upload
|
|
||||||
j = this hook needs upload information as json (not just the filename)
|
|
||||||
c1 = this hook returns json on stdout, so tell copyparty to read that
|
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
|
||||||
-v srv/inc/pods:inc/pods:r:rw,ed:c,xau=j,c1,bin/hooks/podcast-normalizer.py
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
(share fs-path srv/inc/pods at URL /inc/pods,
|
|
||||||
readable by all, read-write for user ed,
|
|
||||||
running this xau (exec-after-upload) plugin for all uploaded files)
|
|
||||||
|
|
||||||
example usage as a volflag in a copyparty config file:
|
|
||||||
[/inc/pods]
|
|
||||||
srv/inc/pods
|
|
||||||
accs:
|
|
||||||
r: *
|
|
||||||
rw: ed
|
|
||||||
flags:
|
|
||||||
e2d # enables file indexing
|
|
||||||
e2t # metadata tags too
|
|
||||||
xau: j,c1,bin/hooks/podcast-normalizer.py
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
### CONFIG
|
|
||||||
|
|
||||||
# filetypes to process; ignores everything else
|
|
||||||
EXTS = "mp3 flac ogg oga opus m4a aac wav wma"
|
|
||||||
|
|
||||||
# the name of the subdir to put the normalized files in
|
|
||||||
SUBDIR = "normalized"
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
|
|
||||||
|
|
||||||
# try to enable support for crazy filenames
|
|
||||||
try:
|
|
||||||
from copyparty.util import fsenc
|
|
||||||
except:
|
|
||||||
|
|
||||||
def fsenc(p):
|
|
||||||
return p.encode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# read info from copyparty
|
|
||||||
inf = json.loads(sys.argv[1])
|
|
||||||
vpath = inf["vp"]
|
|
||||||
abspath = inf["ap"]
|
|
||||||
|
|
||||||
# check if the file-extension is on the to-be-processed list
|
|
||||||
ext = abspath.lower().split(".")[-1]
|
|
||||||
if ext not in EXTS.split():
|
|
||||||
return
|
|
||||||
|
|
||||||
# jump into the folder where the file was uploaded
|
|
||||||
# and create the subfolder to place the normalized copy inside
|
|
||||||
dirpath, filename = os.path.split(abspath)
|
|
||||||
os.chdir(fsenc(dirpath))
|
|
||||||
os.makedirs(SUBDIR, exist_ok=True)
|
|
||||||
|
|
||||||
# the input and output filenames to give ffmpeg
|
|
||||||
fname_in = fsenc(f"./{filename}")
|
|
||||||
fname_out = fsenc(f"{SUBDIR}/{filename}.opus")
|
|
||||||
|
|
||||||
# fmt: off
|
|
||||||
# create and run the ffmpeg command
|
|
||||||
cmd = [
|
|
||||||
b"ffmpeg",
|
|
||||||
b"-nostdin",
|
|
||||||
b"-hide_banner",
|
|
||||||
b"-i", fname_in,
|
|
||||||
b"-af", b"dynaudnorm=f=100:g=9", # the normalizer config
|
|
||||||
b"-c:a", b"libopus",
|
|
||||||
b"-b:a", b"128k",
|
|
||||||
fname_out,
|
|
||||||
]
|
|
||||||
# fmt: on
|
|
||||||
sp.check_output(cmd)
|
|
||||||
|
|
||||||
# and finally, tell copyparty about the new file
|
|
||||||
# so it appears in the database and rss-feed:
|
|
||||||
vpath = f"{SUBDIR}/{filename}.opus"
|
|
||||||
print(json.dumps({"idx": {"vp": [vpath]}}))
|
|
||||||
|
|
||||||
# (it's fine to give it a relative path like that; it gets
|
|
||||||
# resolved relative to the folder the file was uploaded into)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
main()
|
|
||||||
except Exception as ex:
|
|
||||||
print("podcast-normalizer failed; %r" % (ex,))
|
|
|
@ -1,128 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# coding: utf-8
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import shutil
|
|
||||||
import subprocess as sp
|
|
||||||
|
|
||||||
|
|
||||||
_ = r"""
|
|
||||||
start downloading a torrent by POSTing a magnet URL to copyparty,
|
|
||||||
for example using 📟 (message-to-server-log) in the web-ui
|
|
||||||
|
|
||||||
by default it will download the torrent to the folder you were in
|
|
||||||
when you pasted the magnet into the message-to-server-log field
|
|
||||||
|
|
||||||
you can optionally specify another location by adding a whitespace
|
|
||||||
after the magnet URL followed by the name of the subfolder to DL into,
|
|
||||||
or for example "anime/airing" would download to /srv/media/anime/airing
|
|
||||||
because the keyword "anime" is in the DESTS config below
|
|
||||||
|
|
||||||
needs python3
|
|
||||||
|
|
||||||
example usage as global config (not a good idea):
|
|
||||||
python copyparty-sfx.py --xm aw,f,j,t60,bin/hooks/qbittorrent-magnet.py
|
|
||||||
|
|
||||||
parameters explained,
|
|
||||||
xm = execute on message (📟)
|
|
||||||
aw = only users with write-access can use this
|
|
||||||
f = fork; don't delay other hooks while this is running
|
|
||||||
j = provide message information as json (not just the text)
|
|
||||||
t60 = abort if qbittorrent has to think about it for more than 1 min
|
|
||||||
|
|
||||||
example usage as a volflag (per-volume config, much better):
|
|
||||||
-v srv/qb:qb:A,ed:c,xm=aw,f,j,t60,bin/hooks/qbittorrent-magnet.py
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
(share filesystem-path srv/qb as volume /qb with Admin for user 'ed',
|
|
||||||
running this plugin on all messages with the params explained above)
|
|
||||||
|
|
||||||
example usage as a volflag in a copyparty config file:
|
|
||||||
[/qb]
|
|
||||||
srv/qb
|
|
||||||
accs:
|
|
||||||
A: ed
|
|
||||||
flags:
|
|
||||||
xm: aw,f,j,t60,bin/hooks/qbittorrent-magnet.py
|
|
||||||
|
|
||||||
the volflag examples only kicks in if you send the torrent magnet
|
|
||||||
while you're in the /qb folder (or any folder below there)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# list of usernames to allow
|
|
||||||
ALLOWLIST = [ "ed", "morpheus" ]
|
|
||||||
|
|
||||||
|
|
||||||
# list of destination aliases to translate into full filesystem
|
|
||||||
# paths; takes effect if the first folder component in the
|
|
||||||
# custom download location matches anything in this dict
|
|
||||||
DESTS = {
|
|
||||||
"iso": "/srv/pub/linux-isos",
|
|
||||||
"anime": "/srv/media/anime",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
inf = json.loads(sys.argv[1])
|
|
||||||
url = inf["txt"]
|
|
||||||
if not url.lower().startswith("magnet:?"):
|
|
||||||
# not a magnet, abort
|
|
||||||
return
|
|
||||||
|
|
||||||
if inf["user"] not in ALLOWLIST:
|
|
||||||
print("🧲 denied for user", inf["user"])
|
|
||||||
return
|
|
||||||
|
|
||||||
# might as well run the command inside the filesystem folder
|
|
||||||
# which matches the URL that the magnet message was sent to
|
|
||||||
os.chdir(inf["ap"])
|
|
||||||
|
|
||||||
# is there is a custom download location in the url?
|
|
||||||
dst = ""
|
|
||||||
if " " in url:
|
|
||||||
url, dst = url.split(" ", 1)
|
|
||||||
|
|
||||||
# is the location in the predefined list of locations?
|
|
||||||
parts = dst.replace("\\", "/").split("/")
|
|
||||||
if parts[0] in DESTS:
|
|
||||||
dst = os.path.join(DESTS[parts[0]], *(parts[1:]))
|
|
||||||
|
|
||||||
else:
|
|
||||||
# nope, so download to the current folder instead;
|
|
||||||
# comment the dst line below to instead use the default
|
|
||||||
# download location from your qbittorrent settings
|
|
||||||
dst = inf["ap"]
|
|
||||||
pass
|
|
||||||
|
|
||||||
# archlinux has a -nox suffix for qbittorrent if headless
|
|
||||||
# so check if we should be using that
|
|
||||||
if shutil.which("qbittorrent-nox"):
|
|
||||||
torrent_bin = "qbittorrent-nox"
|
|
||||||
else:
|
|
||||||
torrent_bin = "qbittorrent"
|
|
||||||
|
|
||||||
# the command to add a new torrent, adjust if necessary
|
|
||||||
cmd = [torrent_bin, url]
|
|
||||||
if dst:
|
|
||||||
cmd += ["--save-path=%s" % (dst,)]
|
|
||||||
|
|
||||||
# if copyparty and qbittorrent are running as different users
|
|
||||||
# you may have to do something like the following
|
|
||||||
# (assuming qbittorrent* is nopasswd-allowed in sudoers):
|
|
||||||
#
|
|
||||||
# cmd = ["sudo", "-u", "qbitter"] + cmd
|
|
||||||
|
|
||||||
print("🧲", cmd)
|
|
||||||
|
|
||||||
try:
|
|
||||||
sp.check_call(cmd)
|
|
||||||
except:
|
|
||||||
print("🧲 FAILED TO ADD", url)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
|
@ -10,12 +10,7 @@ example usage as global config:
|
||||||
--xbu c,bin/hooks/reject-extension.py
|
--xbu c,bin/hooks/reject-extension.py
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
example usage as a volflag (per-volume config):
|
||||||
-v srv/inc:inc:r:rw,ed:c,xbu=c,bin/hooks/reject-extension.py
|
-v srv/inc:inc:c,xbu=c,bin/hooks/reject-extension.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)
|
|
||||||
|
|
||||||
parameters explained,
|
parameters explained,
|
||||||
xbu = execute before upload
|
xbu = execute before upload
|
||||||
|
|
|
@ -17,12 +17,7 @@ example usage as global config:
|
||||||
--xau c,bin/hooks/reject-mimetype.py
|
--xau c,bin/hooks/reject-mimetype.py
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
example usage as a volflag (per-volume config):
|
||||||
-v srv/inc:inc:r:rw,ed:c,xau=c,bin/hooks/reject-mimetype.py
|
-v srv/inc:inc:c,xau=c,bin/hooks/reject-mimetype.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)
|
|
||||||
|
|
||||||
parameters explained,
|
parameters explained,
|
||||||
xau = execute after upload
|
xau = execute after upload
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
_ = r"""
|
|
||||||
relocate/redirect incoming uploads according to file extension or name
|
|
||||||
|
|
||||||
example usage as global config:
|
|
||||||
--xbu j,c1,bin/hooks/reloc-by-ext.py
|
|
||||||
|
|
||||||
parameters explained,
|
|
||||||
xbu = execute before upload
|
|
||||||
j = this hook needs upload information as json (not just the filename)
|
|
||||||
c1 = this hook returns json on stdout, so tell copyparty to read that
|
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
|
||||||
-v srv/inc:inc:r:rw,ed:c,xbu=j,c1,bin/hooks/reloc-by-ext.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 explained above)
|
|
||||||
|
|
||||||
example usage as a volflag in a copyparty config file:
|
|
||||||
[/inc]
|
|
||||||
srv/inc
|
|
||||||
accs:
|
|
||||||
r: *
|
|
||||||
rw: ed
|
|
||||||
flags:
|
|
||||||
xbu: j,c1,bin/hooks/reloc-by-ext.py
|
|
||||||
|
|
||||||
note: this could also work as an xau hook (after-upload), but
|
|
||||||
because it doesn't need to read the file contents its better
|
|
||||||
as xbu (before-upload) since that's safer / less buggy,
|
|
||||||
and only xbu works with up2k (dragdrop into browser)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
PICS = "avif bmp gif heic heif jpeg jpg jxl png psd qoi tga tif tiff webp"
|
|
||||||
VIDS = "3gp asf avi flv mkv mov mp4 mpeg mpeg2 mpegts mpg mpg2 nut ogm ogv rm ts vob webm wmv"
|
|
||||||
MUSIC = "aac aif aiff alac amr ape dfpwm flac m4a mp3 ogg opus ra tak tta wav wma wv"
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
inf = json.loads(sys.argv[1])
|
|
||||||
vdir, fn = os.path.split(inf["vp"])
|
|
||||||
|
|
||||||
try:
|
|
||||||
fn, ext = fn.rsplit(".", 1)
|
|
||||||
except:
|
|
||||||
# no file extension; pretend it's "bin"
|
|
||||||
ext = "bin"
|
|
||||||
|
|
||||||
ext = ext.lower()
|
|
||||||
|
|
||||||
# this function must end by printing the action to perform;
|
|
||||||
# that's handled by the print(json.dumps(... at the bottom
|
|
||||||
#
|
|
||||||
# the action can contain the following keys:
|
|
||||||
# "vp" is the folder URL to move the upload to,
|
|
||||||
# "ap" is the filesystem-path to move it to (but "vp" is safer),
|
|
||||||
# "fn" overrides the final filename to use
|
|
||||||
|
|
||||||
##
|
|
||||||
## some example actions to take; pick one by
|
|
||||||
## selecting it inside the print at the end:
|
|
||||||
##
|
|
||||||
|
|
||||||
# move all uploads to one specific folder
|
|
||||||
into_junk = {"vp": "/junk"}
|
|
||||||
|
|
||||||
# create a subfolder named after the filetype and move it into there
|
|
||||||
into_subfolder = {"vp": ext}
|
|
||||||
|
|
||||||
# move it into a toplevel folder named after the filetype
|
|
||||||
into_toplevel = {"vp": "/" + ext}
|
|
||||||
|
|
||||||
# move it into a filetype-named folder next to the target folder
|
|
||||||
into_sibling = {"vp": "../" + ext}
|
|
||||||
|
|
||||||
# move images into "/just/pics", vids into "/just/vids",
|
|
||||||
# music into "/just/tunes", and anything else as-is
|
|
||||||
if ext in PICS.split():
|
|
||||||
by_category = {"vp": "/just/pics"}
|
|
||||||
elif ext in VIDS.split():
|
|
||||||
by_category = {"vp": "/just/vids"}
|
|
||||||
elif ext in MUSIC.split():
|
|
||||||
by_category = {"vp": "/just/tunes"}
|
|
||||||
else:
|
|
||||||
by_category = {} # no action
|
|
||||||
|
|
||||||
# now choose the default effect to apply; can be any of these:
|
|
||||||
# into_junk into_subfolder into_toplevel into_sibling by_category
|
|
||||||
effect = into_sibling
|
|
||||||
|
|
||||||
##
|
|
||||||
## but we can keep going, adding more speicifc rules
|
|
||||||
## which can take precedence, replacing the fallback
|
|
||||||
## effect we just specified:
|
|
||||||
##
|
|
||||||
|
|
||||||
fn = fn.lower() # lowercase filename to make this easier
|
|
||||||
|
|
||||||
if "screenshot" in fn:
|
|
||||||
effect = {"vp": "/ss"}
|
|
||||||
if "mpv_" in fn:
|
|
||||||
effect = {"vp": "/anishots"}
|
|
||||||
elif "debian" in fn or "biebian" in fn:
|
|
||||||
effect = {"vp": "/linux-ISOs"}
|
|
||||||
elif re.search(r"ep(isode |\.)?[0-9]", fn):
|
|
||||||
effect = {"vp": "/podcasts"}
|
|
||||||
|
|
||||||
# regex lets you grab a part of the matching
|
|
||||||
# text and use that in the upload path:
|
|
||||||
m = re.search(r"\b(op|ed)([^a-z]|$)", fn)
|
|
||||||
if m:
|
|
||||||
# the regex matched; use "anime-op" or "anime-ed"
|
|
||||||
effect = {"vp": "/anime-" + m[1]}
|
|
||||||
|
|
||||||
# aaand DO IT
|
|
||||||
print(json.dumps({"reloc": effect}))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,62 +0,0 @@
|
||||||
// see usb-eject.py for usage
|
|
||||||
|
|
||||||
function usbclick() {
|
|
||||||
var o = QS('#treeul a[dst="/usb/"]') || QS('#treepar a[dst="/usb/"]');
|
|
||||||
if (o)
|
|
||||||
o.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function eject_cb() {
|
|
||||||
var t = ('' + this.responseText).trim();
|
|
||||||
if (t.indexOf('can be safely unplugged') < 0 && t.indexOf('Device can be removed') < 0)
|
|
||||||
return toast.err(30, 'usb eject failed:\n\n' + t);
|
|
||||||
|
|
||||||
toast.ok(5, esc(t.replace(/ - /g, '\n\n')).trim());
|
|
||||||
usbclick(); setTimeout(usbclick, 10);
|
|
||||||
};
|
|
||||||
|
|
||||||
function add_eject_2(a) {
|
|
||||||
var aw = a.getAttribute('href').split(/\//g);
|
|
||||||
if (aw.length != 4 || aw[3])
|
|
||||||
return;
|
|
||||||
|
|
||||||
var v = aw[2],
|
|
||||||
k = 'umount_' + v;
|
|
||||||
|
|
||||||
for (var b = 0; b < 9; b++) {
|
|
||||||
var o = ebi(k);
|
|
||||||
if (!o)
|
|
||||||
break;
|
|
||||||
o.parentNode.removeChild(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
a.appendChild(mknod('span', k, '⏏'), a);
|
|
||||||
o = ebi(k);
|
|
||||||
o.style.cssText = 'position:absolute; right:1em; margin-top:-.2em; font-size:1.3em';
|
|
||||||
o.onclick = function (e) {
|
|
||||||
ev(e);
|
|
||||||
var xhr = new XHR();
|
|
||||||
xhr.open('POST', get_evpath(), true);
|
|
||||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
|
|
||||||
xhr.send('msg=' + uricom_enc(':usb-eject:' + v + ':'));
|
|
||||||
xhr.onload = xhr.onerror = eject_cb;
|
|
||||||
toast.inf(10, "ejecting " + v + "...");
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function add_eject() {
|
|
||||||
var o = QSA('#treeul a[href^="/usb/"]') || QSA('#treepar a[href^="/usb/"]');
|
|
||||||
for (var a = o.length - 1; a > 0; a--)
|
|
||||||
add_eject_2(o[a]);
|
|
||||||
};
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
var f0 = treectl.rendertree;
|
|
||||||
treectl.rendertree = function (res, ts, top0, dst, rst) {
|
|
||||||
var ret = f0(res, ts, top0, dst, rst);
|
|
||||||
add_eject();
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
setTimeout(add_eject, 50);
|
|
|
@ -1,62 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import stat
|
|
||||||
import subprocess as sp
|
|
||||||
import sys
|
|
||||||
from urllib.parse import unquote_to_bytes as unquote
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
if you've found yourself using copyparty to serve flashdrives on a LAN
|
|
||||||
and your only wish is that the web-UI had a button to unmount / safely
|
|
||||||
remove those flashdrives, then boy howdy are you in the right place :D
|
|
||||||
|
|
||||||
put usb-eject.js in the webroot (or somewhere else http-accessible)
|
|
||||||
then run copyparty with these args:
|
|
||||||
|
|
||||||
-v /run/media/egon:/usb:A:c,hist=/tmp/junk
|
|
||||||
--xm=c1,bin/hooks/usb-eject.py
|
|
||||||
--js-browser=/usb-eject.js
|
|
||||||
|
|
||||||
which does the following respectively,
|
|
||||||
|
|
||||||
* share all of /run/media/egon as /usb with admin for everyone
|
|
||||||
and put the histpath somewhere it won't cause trouble
|
|
||||||
* run the usb-eject hook with stdout redirect to the web-ui
|
|
||||||
* add the complementary usb-eject.js to the browser
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
MOUNT_BASE = b"/run/media/egon/"
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
try:
|
|
||||||
label = sys.argv[1].split(":usb-eject:")[1].split(":")[0]
|
|
||||||
mp = MOUNT_BASE + unquote(label)
|
|
||||||
# print("ejecting [%s]... " % (mp,), end="")
|
|
||||||
mp = os.path.abspath(os.path.realpath(mp))
|
|
||||||
st = os.lstat(mp)
|
|
||||||
if not stat.S_ISDIR(st.st_mode) or not mp.startswith(MOUNT_BASE):
|
|
||||||
raise Exception("not a regular directory")
|
|
||||||
|
|
||||||
# if you're running copyparty as root (thx for the faith)
|
|
||||||
# you'll need something like this to make dbus talkative
|
|
||||||
cmd = b"sudo -u egon DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus gio mount -e"
|
|
||||||
|
|
||||||
# but if copyparty and the ui-session is running
|
|
||||||
# as the same user (good) then this is plenty
|
|
||||||
cmd = b"gio mount -e"
|
|
||||||
|
|
||||||
cmd = cmd.split(b" ") + [mp]
|
|
||||||
ret = sp.check_output(cmd).decode("utf-8", "replace")
|
|
||||||
print(ret.strip() or (label + " can be safely unplugged"))
|
|
||||||
|
|
||||||
except Exception as ex:
|
|
||||||
print("unmount failed: %r" % (ex,))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -9,38 +9,19 @@ import subprocess as sp
|
||||||
_ = r"""
|
_ = r"""
|
||||||
use copyparty as a file downloader by POSTing URLs as
|
use copyparty as a file downloader by POSTing URLs as
|
||||||
application/x-www-form-urlencoded (for example using the
|
application/x-www-form-urlencoded (for example using the
|
||||||
📟 message-to-server-log in the web-ui)
|
message/pager function on the website)
|
||||||
|
|
||||||
example usage as global config:
|
example usage as global config:
|
||||||
--xm aw,f,j,t3600,bin/hooks/wget.py
|
--xm f,j,t3600,bin/hooks/wget.py
|
||||||
|
|
||||||
parameters explained,
|
|
||||||
xm = execute on message-to-server-log
|
|
||||||
aw = only users with write-access can use this
|
|
||||||
f = fork; don't delay other hooks while this is running
|
|
||||||
j = provide message information as json (not just the text)
|
|
||||||
c3 = mute all output
|
|
||||||
t3600 = timeout and abort download after 1 hour
|
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
example usage as a volflag (per-volume config):
|
||||||
-v srv/inc:inc:r:rw,ed:c,xm=aw,f,j,t3600,bin/hooks/wget.py
|
-v srv/inc:inc:c,xm=f,j,t3600,bin/hooks/wget.py
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
(share filesystem-path srv/inc as volume /inc,
|
parameters explained,
|
||||||
readable by everyone, read-write for user 'ed',
|
f = fork so it doesn't block uploads
|
||||||
running this plugin on all messages with the params explained above)
|
j = provide message information as json; not just the text
|
||||||
|
c3 = mute all output
|
||||||
example usage as a volflag in a copyparty config file:
|
t3600 = timeout and kill download after 1 hour
|
||||||
[/inc]
|
|
||||||
srv/inc
|
|
||||||
accs:
|
|
||||||
r: *
|
|
||||||
rw: ed
|
|
||||||
flags:
|
|
||||||
xm: aw,f,j,t3600,bin/hooks/wget.py
|
|
||||||
|
|
||||||
the volflag examples only kicks in if you send the message
|
|
||||||
while you're in the /inc folder (or any folder below there)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,10 +31,6 @@ def main():
|
||||||
if "://" not in url:
|
if "://" not in url:
|
||||||
url = "https://" + url
|
url = "https://" + url
|
||||||
|
|
||||||
proto = url.split("://")[0].lower()
|
|
||||||
if proto not in ("http", "https", "ftp", "ftps"):
|
|
||||||
raise Exception("bad proto {}".format(proto))
|
|
||||||
|
|
||||||
os.chdir(inf["ap"])
|
os.chdir(inf["ap"])
|
||||||
|
|
||||||
name = url.split("?")[0].split("/")[-1]
|
name = url.split("?")[0].split("/")[-1]
|
||||||
|
@ -66,7 +43,7 @@ def main():
|
||||||
try:
|
try:
|
||||||
sp.check_call(cmd)
|
sp.check_call(cmd)
|
||||||
except:
|
except:
|
||||||
t = "-- FAILED TO DOWNLOAD " + name
|
t = "-- FAILED TO DONWLOAD " + name
|
||||||
print(f"{t}\n", end="")
|
print(f"{t}\n", end="")
|
||||||
open(t, "wb").close()
|
open(t, "wb").close()
|
||||||
|
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
|
|
||||||
|
|
||||||
_ = r"""
|
|
||||||
this hook will produce a single sha512 file which
|
|
||||||
covers all recent uploads (plus metadata comments)
|
|
||||||
|
|
||||||
use this with --xiu, which makes copyparty buffer
|
|
||||||
uploads until server is idle, providing file infos
|
|
||||||
on stdin (filepaths or json)
|
|
||||||
|
|
||||||
example usage as global config:
|
|
||||||
--xiu i5,j,bin/hooks/xiu-sha.py
|
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
|
||||||
-v srv/inc:inc:r:rw,ed:c,xiu=i5,j,bin/hooks/xiu-sha.py
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
(share filesystem-path srv/inc as volume /inc,
|
|
||||||
readable by everyone, read-write for user 'ed',
|
|
||||||
running this plugin on batches of uploads with the params listed below)
|
|
||||||
|
|
||||||
parameters explained,
|
|
||||||
xiu = execute after uploads...
|
|
||||||
i5 = ...after volume has been idle for 5sec
|
|
||||||
j = provide json instead of filepath list
|
|
||||||
|
|
||||||
note the "f" (fork) flag is not set, so this xiu
|
|
||||||
will block other xiu hooks while it's running
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from copyparty.util import fsenc
|
|
||||||
except:
|
|
||||||
|
|
||||||
def fsenc(p):
|
|
||||||
return p
|
|
||||||
|
|
||||||
|
|
||||||
UTC = timezone.utc
|
|
||||||
|
|
||||||
|
|
||||||
def humantime(ts):
|
|
||||||
return datetime.fromtimestamp(ts, UTC).strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
|
|
||||||
|
|
||||||
def find_files_root(inf):
|
|
||||||
di = 9000
|
|
||||||
for f1, f2 in zip(inf, inf[1:]):
|
|
||||||
p1 = f1["ap"].replace("\\", "/").rsplit("/", 1)[0]
|
|
||||||
p2 = f2["ap"].replace("\\", "/").rsplit("/", 1)[0]
|
|
||||||
di = min(len(p1), len(p2), di)
|
|
||||||
di = next((i for i in range(di) if p1[i] != p2[i]), di)
|
|
||||||
|
|
||||||
return di + 1
|
|
||||||
|
|
||||||
|
|
||||||
def find_vol_root(inf):
|
|
||||||
return len(inf[0]["ap"][: -len(inf[0]["vp"])])
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
zb = sys.stdin.buffer.read()
|
|
||||||
zs = zb.decode("utf-8", "replace")
|
|
||||||
inf = json.loads(zs)
|
|
||||||
|
|
||||||
# root directory (where to put the sha512 file);
|
|
||||||
# di = find_files_root(inf) # next to the file closest to volume root
|
|
||||||
di = find_vol_root(inf) # top of the entire volume
|
|
||||||
|
|
||||||
ret = []
|
|
||||||
total_sz = 0
|
|
||||||
for md in inf:
|
|
||||||
ap = md["ap"]
|
|
||||||
rp = ap[di:]
|
|
||||||
total_sz += md["sz"]
|
|
||||||
fsize = "{:,}".format(md["sz"])
|
|
||||||
mtime = humantime(md["mt"])
|
|
||||||
up_ts = humantime(md["at"])
|
|
||||||
|
|
||||||
h = hashlib.sha512()
|
|
||||||
with open(fsenc(md["ap"]), "rb", 512 * 1024) as f:
|
|
||||||
while True:
|
|
||||||
buf = f.read(512 * 1024)
|
|
||||||
if not buf:
|
|
||||||
break
|
|
||||||
|
|
||||||
h.update(buf)
|
|
||||||
|
|
||||||
cksum = h.hexdigest()
|
|
||||||
meta = " | ".join([md["wark"], up_ts, mtime, fsize, md["ip"]])
|
|
||||||
ret.append("# {}\n{} *{}".format(meta, cksum, rp))
|
|
||||||
|
|
||||||
ret.append("# {} files, {} bytes total".format(len(inf), total_sz))
|
|
||||||
ret.append("")
|
|
||||||
ftime = datetime.now(UTC).strftime("%Y-%m%d-%H%M%S.%f")
|
|
||||||
fp = "{}xfer-{}.sha512".format(inf[0]["ap"][:di], ftime)
|
|
||||||
with open(fsenc(fp), "wb") as f:
|
|
||||||
f.write("\n".join(ret).encode("utf-8", "replace"))
|
|
||||||
|
|
||||||
print("wrote checksums to {}".format(fp))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,50 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
_ = r"""
|
|
||||||
this hook prints absolute filepaths + total size
|
|
||||||
|
|
||||||
use this with --xiu, which makes copyparty buffer
|
|
||||||
uploads until server is idle, providing file infos
|
|
||||||
on stdin (filepaths or json)
|
|
||||||
|
|
||||||
example usage as global config:
|
|
||||||
--xiu i1,j,bin/hooks/xiu.py
|
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
|
||||||
-v srv/inc:inc:r:rw,ed:c,xiu=i1,j,bin/hooks/xiu.py
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
(share filesystem-path srv/inc as volume /inc,
|
|
||||||
readable by everyone, read-write for user 'ed',
|
|
||||||
running this plugin on batches of uploads with the params listed below)
|
|
||||||
|
|
||||||
parameters explained,
|
|
||||||
xiu = execute after uploads...
|
|
||||||
i1 = ...after volume has been idle for 1sec
|
|
||||||
j = provide json instead of filepath list
|
|
||||||
|
|
||||||
note the "f" (fork) flag is not set, so this xiu
|
|
||||||
will block other xiu hooks while it's running
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
zb = sys.stdin.buffer.read()
|
|
||||||
zs = zb.decode("utf-8", "replace")
|
|
||||||
inf = json.loads(zs)
|
|
||||||
|
|
||||||
total_sz = 0
|
|
||||||
for upload in inf:
|
|
||||||
sz = upload["sz"]
|
|
||||||
total_sz += sz
|
|
||||||
print("{:9} {}".format(sz, upload["ap"]))
|
|
||||||
|
|
||||||
print("{} files, {} bytes total".format(len(inf), total_sz))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -24,18 +24,6 @@ these do not have any problematic dependencies at all:
|
||||||
* also available as an [event hook](../hooks/wget.py)
|
* also available as an [event hook](../hooks/wget.py)
|
||||||
|
|
||||||
|
|
||||||
## dangerous plugins
|
|
||||||
|
|
||||||
plugins in this section should only be used with appropriate precautions:
|
|
||||||
|
|
||||||
* [very-bad-idea.py](./very-bad-idea.py) combined with [meadup.js](https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/meadup.js) converts copyparty into a janky yet extremely flexible chromecast clone
|
|
||||||
* also adds a virtual keyboard by @steinuil to the basic-upload tab for comfy couch crowd control
|
|
||||||
* anything uploaded through the [android app](https://github.com/9001/party-up) (files or links) are executed on the server, meaning anyone can infect your PC with malware... so protect this with a password and keep it on a LAN!
|
|
||||||
* [kamelåså](https://github.com/steinuil/kameloso) is a much better (and MUCH safer) alternative to this plugin
|
|
||||||
* powered by [chicken-curry-banana-pineapple-peanut pizza](https://a.ocv.me/pub/g/i/2025/01/298437ce-8351-4c8c-861c-fa131d217999.jpg?cache) so you know it's good
|
|
||||||
* and, unlike this plugin, kamelåså even has windows support (nice)
|
|
||||||
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
|
|
||||||
run [`install-deps.sh`](install-deps.sh) to build/install most dependencies required by these programs (supports windows/linux/macos)
|
run [`install-deps.sh`](install-deps.sh) to build/install most dependencies required by these programs (supports windows/linux/macos)
|
||||||
|
@ -43,7 +31,7 @@ run [`install-deps.sh`](install-deps.sh) to build/install most dependencies requ
|
||||||
*alternatively* (or preferably) use packages from your distro instead, then you'll need at least these:
|
*alternatively* (or preferably) use packages from your distro instead, then you'll need at least these:
|
||||||
|
|
||||||
* from distro: `numpy vamp-plugin-sdk beatroot-vamp mixxx-keyfinder ffmpeg`
|
* from distro: `numpy vamp-plugin-sdk beatroot-vamp mixxx-keyfinder ffmpeg`
|
||||||
* from pip: `keyfinder vamp`
|
* from pypy: `keyfinder vamp`
|
||||||
|
|
||||||
|
|
||||||
# usage from copyparty
|
# usage from copyparty
|
||||||
|
|
|
@ -16,10 +16,6 @@ dep: ffmpeg
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
# save beat timestamps to ".beats/filename.txt"
|
|
||||||
SAVE = False
|
|
||||||
|
|
||||||
|
|
||||||
def det(tf):
|
def det(tf):
|
||||||
# fmt: off
|
# fmt: off
|
||||||
sp.check_call([
|
sp.check_call([
|
||||||
|
@ -27,11 +23,12 @@ def det(tf):
|
||||||
b"-nostdin",
|
b"-nostdin",
|
||||||
b"-hide_banner",
|
b"-hide_banner",
|
||||||
b"-v", b"fatal",
|
b"-v", b"fatal",
|
||||||
|
b"-ss", b"13",
|
||||||
b"-y", b"-i", fsenc(sys.argv[1]),
|
b"-y", b"-i", fsenc(sys.argv[1]),
|
||||||
b"-map", b"0:a:0",
|
b"-map", b"0:a:0",
|
||||||
b"-ac", b"1",
|
b"-ac", b"1",
|
||||||
b"-ar", b"22050",
|
b"-ar", b"22050",
|
||||||
b"-t", b"360",
|
b"-t", b"300",
|
||||||
b"-f", b"f32le",
|
b"-f", b"f32le",
|
||||||
fsenc(tf)
|
fsenc(tf)
|
||||||
])
|
])
|
||||||
|
@ -50,29 +47,10 @@ def det(tf):
|
||||||
print(c["list"][0]["label"].split(" ")[0])
|
print(c["list"][0]["label"].split(" ")[0])
|
||||||
return
|
return
|
||||||
|
|
||||||
# throws if detection failed:
|
# throws if detection failed:
|
||||||
beats = [float(x["timestamp"]) for x in cl]
|
bpm = float(cl[-1]["timestamp"] - cl[1]["timestamp"])
|
||||||
bds = [b - a for a, b in zip(beats, beats[1:])]
|
bpm = round(60 * ((len(cl) - 1) / bpm), 2)
|
||||||
bds.sort()
|
print(f"{bpm:.2f}")
|
||||||
n0 = int(len(bds) * 0.2)
|
|
||||||
n1 = int(len(bds) * 0.75) + 1
|
|
||||||
bds = bds[n0:n1]
|
|
||||||
bpm = sum(bds)
|
|
||||||
bpm = round(60 * (len(bds) / bpm), 2)
|
|
||||||
print(f"{bpm:.2f}")
|
|
||||||
|
|
||||||
if SAVE:
|
|
||||||
fdir, fname = os.path.split(sys.argv[1])
|
|
||||||
bdir = os.path.join(fdir, ".beats")
|
|
||||||
try:
|
|
||||||
os.mkdir(fsenc(bdir))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
fp = os.path.join(bdir, fname) + ".txt"
|
|
||||||
with open(fsenc(fp), "wb") as f:
|
|
||||||
txt = "\n".join([f"{x:.2f}" for x in beats])
|
|
||||||
f.write(txt.encode("utf-8"))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
@ -2,15 +2,11 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
import zlib
|
||||||
import struct
|
import struct
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
try:
|
|
||||||
from zlib_ng import zlib_ng as zlib
|
|
||||||
except:
|
|
||||||
import zlib
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from copyparty.util import fsenc
|
from copyparty.util import fsenc
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -7,7 +7,7 @@ example copyparty config to use this:
|
||||||
--urlform save,get -vsrv/hello:hello:w:c,e2ts,mtp=guestbook=t10,ad,p,bin/mtag/guestbook-read.py:mte=+guestbook
|
--urlform save,get -vsrv/hello:hello:w:c,e2ts,mtp=guestbook=t10,ad,p,bin/mtag/guestbook-read.py:mte=+guestbook
|
||||||
|
|
||||||
explained:
|
explained:
|
||||||
for realpath srv/hello (served at /hello), write-only for everyone,
|
for realpath srv/hello (served at /hello), write-only for eveyrone,
|
||||||
enable file analysis on upload (e2ts),
|
enable file analysis on upload (e2ts),
|
||||||
use mtp plugin "bin/mtag/guestbook-read.py" to provide metadata tag "guestbook",
|
use mtp plugin "bin/mtag/guestbook-read.py" to provide metadata tag "guestbook",
|
||||||
do this on all uploads regardless of extension,
|
do this on all uploads regardless of extension,
|
||||||
|
|
|
@ -11,7 +11,7 @@ example copyparty config to use this:
|
||||||
--urlform save,get -vsrv/hello:hello:w:c,e2ts,mtp=xgb=ebin,t10,ad,p,bin/mtag/guestbook.py:mte=+xgb
|
--urlform save,get -vsrv/hello:hello:w:c,e2ts,mtp=xgb=ebin,t10,ad,p,bin/mtag/guestbook.py:mte=+xgb
|
||||||
|
|
||||||
explained:
|
explained:
|
||||||
for realpath srv/hello (served at /hello),write-only for everyone,
|
for realpath srv/hello (served at /hello),write-only for eveyrone,
|
||||||
enable file analysis on upload (e2ts),
|
enable file analysis on upload (e2ts),
|
||||||
use mtp plugin "bin/mtag/guestbook.py" to provide metadata tag "xgb",
|
use mtp plugin "bin/mtag/guestbook.py" to provide metadata tag "xgb",
|
||||||
do this on all uploads with the file extension "bin",
|
do this on all uploads with the file extension "bin",
|
||||||
|
|
|
@ -61,7 +61,7 @@ def main():
|
||||||
|
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
f1 = fsenc(fn)
|
f1 = fsenc(fn)
|
||||||
f2 = fsenc(os.path.join(b"noexif", fn))
|
f2 = os.path.join(b"noexif", f1)
|
||||||
cmd = [
|
cmd = [
|
||||||
b"exiftool",
|
b"exiftool",
|
||||||
b"-exif:all=",
|
b"-exif:all=",
|
||||||
|
|
|
@ -7,7 +7,6 @@ set -e
|
||||||
# linux/alpine: requires gcc g++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-dev py3-{wheel,pip} py3-numpy{,-dev}
|
# linux/alpine: requires gcc g++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-dev py3-{wheel,pip} py3-numpy{,-dev}
|
||||||
# linux/debian: requires libav{codec,device,filter,format,resample,util}-dev {libfftw3,python3,libsndfile1}-dev python3-{numpy,pip} vamp-{plugin-sdk,examples} patchelf cmake
|
# linux/debian: requires libav{codec,device,filter,format,resample,util}-dev {libfftw3,python3,libsndfile1}-dev python3-{numpy,pip} vamp-{plugin-sdk,examples} patchelf cmake
|
||||||
# linux/fedora: requires gcc gcc-c++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-devel python3-numpy vamp-plugin-sdk qm-vamp-plugins
|
# linux/fedora: requires gcc gcc-c++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-devel python3-numpy vamp-plugin-sdk qm-vamp-plugins
|
||||||
# linux/arch: requires gcc make cmake patchelf python3 ffmpeg fftw libsndfile python-{numpy,wheel,pip,setuptools}
|
|
||||||
# win64: requires msys2-mingw64 environment
|
# win64: requires msys2-mingw64 environment
|
||||||
# macos: requires macports
|
# macos: requires macports
|
||||||
#
|
#
|
||||||
|
@ -22,8 +21,6 @@ set -e
|
||||||
# modifies the keyfinder python lib to load the .so in ~/pe
|
# modifies the keyfinder python lib to load the .so in ~/pe
|
||||||
|
|
||||||
|
|
||||||
export FORCE_COLOR=1
|
|
||||||
|
|
||||||
linux=1
|
linux=1
|
||||||
|
|
||||||
win=
|
win=
|
||||||
|
@ -60,7 +57,6 @@ hash -r
|
||||||
command -v python3 && pybin=python3 || pybin=python
|
command -v python3 && pybin=python3 || pybin=python
|
||||||
}
|
}
|
||||||
|
|
||||||
$pybin -c 'import numpy' ||
|
|
||||||
$pybin -m pip install --user numpy
|
$pybin -m pip install --user numpy
|
||||||
|
|
||||||
|
|
||||||
|
@ -188,15 +184,12 @@ install_keyfinder() {
|
||||||
echo "so not found at $sop"
|
echo "so not found at $sop"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
x=${-//[^x]/}; set -x; cat /etc/alpine-release
|
|
||||||
# rm -rf /Users/ed/Library/Python/3.9/lib/python/site-packages/*keyfinder*
|
# rm -rf /Users/ed/Library/Python/3.9/lib/python/site-packages/*keyfinder*
|
||||||
CFLAGS="-I$h/pe/keyfinder/include -I/opt/local/include -I/usr/include/ffmpeg" \
|
CFLAGS="-I$h/pe/keyfinder/include -I/opt/local/include -I/usr/include/ffmpeg" \
|
||||||
CXXFLAGS="-I$h/pe/keyfinder/include -I/opt/local/include -I/usr/include/ffmpeg" \
|
|
||||||
LDFLAGS="-L$h/pe/keyfinder/lib -L$h/pe/keyfinder/lib64 -L/opt/local/lib" \
|
LDFLAGS="-L$h/pe/keyfinder/lib -L$h/pe/keyfinder/lib64 -L/opt/local/lib" \
|
||||||
PKG_CONFIG_PATH="/c/msys64/mingw64/lib/pkgconfig:$h/pe/keyfinder/lib/pkgconfig" \
|
PKG_CONFIG_PATH=/c/msys64/mingw64/lib/pkgconfig \
|
||||||
$pybin -m pip install --user keyfinder
|
$pybin -m pip install --user keyfinder
|
||||||
[ "$x" ] || set +x
|
|
||||||
|
|
||||||
pypath="$($pybin -c 'import keyfinder; print(keyfinder.__file__)')"
|
pypath="$($pybin -c 'import keyfinder; print(keyfinder.__file__)')"
|
||||||
for pyso in "${pypath%/*}"/*.so; do
|
for pyso in "${pypath%/*}"/*.so; do
|
||||||
|
@ -228,31 +221,27 @@ install_vamp() {
|
||||||
# use msys2 in mingw-w64 mode
|
# use msys2 in mingw-w64 mode
|
||||||
# pacman -S --needed mingw-w64-x86_64-{ffmpeg,python,python-pip,vamp-plugin-sdk}
|
# pacman -S --needed mingw-w64-x86_64-{ffmpeg,python,python-pip,vamp-plugin-sdk}
|
||||||
|
|
||||||
$pybin -m pip install --user vamp || {
|
$pybin -m pip install --user vamp
|
||||||
printf '\n\033[7malright, trying something else...\033[0m\n'
|
|
||||||
$pybin -m pip install --user --no-build-isolation vamp
|
|
||||||
}
|
|
||||||
|
|
||||||
cd "$td"
|
cd "$td"
|
||||||
echo '#include <vamp-sdk/Plugin.h>' | g++ -x c++ -c -o /dev/null - || [ -e ~/pe/vamp-sdk ] || {
|
echo '#include <vamp-sdk/Plugin.h>' | gcc -x c -c -o /dev/null - || [ -e ~/pe/vamp-sdk ] || {
|
||||||
printf '\033[33mcould not find the vamp-sdk, building from source\033[0m\n'
|
printf '\033[33mcould not find the vamp-sdk, building from source\033[0m\n'
|
||||||
(dl_files yolo https://ocv.me/mirror/vamp-plugin-sdk-2.10.0.tar.gz)
|
(dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/2588/vamp-plugin-sdk-2.9.0.tar.gz)
|
||||||
sha512sum -c <(
|
sha512sum -c <(
|
||||||
echo "153b7f2fa01b77c65ad393ca0689742d66421017fd5931d216caa0fcf6909355fff74706fabbc062a3a04588a619c9b515a1dae00f21a57afd97902a355c48ed -"
|
echo "7ef7f837d19a08048b059e0da408373a7964ced452b290fae40b85d6d70ca9000bcfb3302cd0b4dc76cf2a848528456f78c1ce1ee0c402228d812bd347b6983b -"
|
||||||
) <vamp-plugin-sdk-2.10.0.tar.gz
|
) <vamp-plugin-sdk-2.9.0.tar.gz
|
||||||
tar -xf vamp-plugin-sdk-2.10.0.tar.gz
|
tar -xf vamp-plugin-sdk-2.9.0.tar.gz
|
||||||
rm -- *.tar.gz
|
rm -- *.tar.gz
|
||||||
ls -al
|
ls -al
|
||||||
cd vamp-plugin-sdk-*
|
cd vamp-plugin-sdk-*
|
||||||
printf '%s\n' "int main(int argc, char **argv) { return 0; }" > host/vamp-simple-host.cpp
|
./configure --prefix=$HOME/pe/vamp-sdk
|
||||||
./configure --disable-programs --prefix=$HOME/pe/vamp-sdk
|
|
||||||
make -j1 install
|
make -j1 install
|
||||||
}
|
}
|
||||||
|
|
||||||
cd "$td"
|
cd "$td"
|
||||||
have_beatroot || {
|
have_beatroot || {
|
||||||
printf '\033[33mcould not find the vamp beatroot plugin, building from source\033[0m\n'
|
printf '\033[33mcould not find the vamp beatroot plugin, building from source\033[0m\n'
|
||||||
(dl_files yolo https://ocv.me/mirror/beatroot-vamp-v1.0.tar.gz)
|
(dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/885/beatroot-vamp-v1.0.tar.gz)
|
||||||
sha512sum -c <(
|
sha512sum -c <(
|
||||||
echo "1f444d1d58ccf565c0adfe99f1a1aa62789e19f5071e46857e2adfbc9d453037bc1c4dcb039b02c16240e9b97f444aaff3afb625c86aa2470233e711f55b6874 -"
|
echo "1f444d1d58ccf565c0adfe99f1a1aa62789e19f5071e46857e2adfbc9d453037bc1c4dcb039b02c16240e9b97f444aaff3afb625c86aa2470233e711f55b6874 -"
|
||||||
) <beatroot-vamp-v1.0.tar.gz
|
) <beatroot-vamp-v1.0.tar.gz
|
||||||
|
@ -260,9 +249,8 @@ install_vamp() {
|
||||||
rm -- *.tar.gz
|
rm -- *.tar.gz
|
||||||
cd beatroot-vamp-v1.0
|
cd beatroot-vamp-v1.0
|
||||||
[ -e ~/pe/vamp-sdk ] &&
|
[ -e ~/pe/vamp-sdk ] &&
|
||||||
sed -ri 's`^(CFLAGS :=.*)`\1 -I'$HOME'/pe/vamp-sdk/include`' Makefile.linux ||
|
sed -ri 's`^(CFLAGS :=.*)`\1 -I'$HOME'/pe/vamp-sdk/include`' Makefile.linux
|
||||||
sed -ri 's`^(CFLAGS :=.*)`\1 -I/usr/include/vamp-sdk`' Makefile.linux
|
make -f Makefile.linux -j4 LDFLAGS=-L$HOME/pe/vamp-sdk/lib
|
||||||
make -f Makefile.linux -j4 LDFLAGS="-L$HOME/pe/vamp-sdk/lib -L/usr/lib64"
|
|
||||||
# /home/ed/vamp /home/ed/.vamp /usr/local/lib/vamp
|
# /home/ed/vamp /home/ed/.vamp /usr/local/lib/vamp
|
||||||
mkdir ~/vamp
|
mkdir ~/vamp
|
||||||
cp -pv beatroot-vamp.* ~/vamp/
|
cp -pv beatroot-vamp.* ~/vamp/
|
||||||
|
|
|
@ -1,16 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
"""
|
"""
|
||||||
WARNING -- DANGEROUS PLUGIN --
|
|
||||||
if someone is able to upload files to a copyparty which is
|
|
||||||
running this plugin, they can execute malware on your machine
|
|
||||||
so please keep this on a LAN and protect it with a password
|
|
||||||
|
|
||||||
here is a MUCH BETTER ALTERNATIVE (which also works on Windows):
|
|
||||||
https://github.com/steinuil/kameloso
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
|
|
||||||
use copyparty as a chromecast replacement:
|
use copyparty as a chromecast replacement:
|
||||||
* post a URL and it will open in the default browser
|
* post a URL and it will open in the default browser
|
||||||
* upload a file and it will open in the default application
|
* upload a file and it will open in the default application
|
||||||
|
@ -20,17 +10,16 @@ use copyparty as a chromecast replacement:
|
||||||
|
|
||||||
the android app makes it a breeze to post pics and links:
|
the android app makes it a breeze to post pics and links:
|
||||||
https://github.com/9001/party-up/releases
|
https://github.com/9001/party-up/releases
|
||||||
|
(iOS devices have to rely on the web-UI)
|
||||||
|
|
||||||
iOS devices can use the web-UI or the shortcut instead:
|
goes without saying, but this is HELLA DANGEROUS,
|
||||||
https://github.com/9001/copyparty#ios-shortcuts
|
GIVES RCE TO ANYONE WHO HAVE UPLOAD PERMISSIONS
|
||||||
|
|
||||||
example copyparty config to use this;
|
example copyparty config to use this:
|
||||||
lets the user "kevin" with password "hunter2" use this plugin:
|
--urlform save,get -v.::w:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,kn,c0,bin/mtag/very-bad-idea.py
|
||||||
-a kevin:hunter2 --urlform save,get -v.::w,kevin:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,kn,c0,bin/mtag/very-bad-idea.py
|
|
||||||
|
|
||||||
recommended deps:
|
recommended deps:
|
||||||
apt install xdotool libnotify-bin mpv
|
apt install xdotool libnotify-bin
|
||||||
python3 -m pip install --user -U streamlink yt-dlp
|
|
||||||
https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/meadup.js
|
https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/meadup.js
|
||||||
|
|
||||||
and you probably want `twitter-unmute.user.js` from the res folder
|
and you probably want `twitter-unmute.user.js` from the res folder
|
||||||
|
@ -74,10 +63,8 @@ set -e
|
||||||
EOF
|
EOF
|
||||||
chmod 755 /usr/local/bin/chromium-browser
|
chmod 755 /usr/local/bin/chromium-browser
|
||||||
|
|
||||||
# start the server
|
# start the server (note: replace `-v.::rw:` with `-v.::w:` to disallow retrieving uploaded stuff)
|
||||||
# note 1: replace hunter2 with a better password to access the server
|
cd ~/Downloads; python3 copyparty-sfx.py --urlform save,get -v.::rw:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,kn,very-bad-idea.py
|
||||||
# note 2: replace `-v.::rw` with `-v.::w` to disallow retrieving uploaded stuff
|
|
||||||
cd ~/Downloads; python3 copyparty-sfx.py -a kevin:hunter2 --urlform save,get -v.::rw,kevin:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,kn,very-bad-idea.py
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -85,23 +72,11 @@ cd ~/Downloads; python3 copyparty-sfx.py -a kevin:hunter2 --urlform save,get -v.
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import shutil
|
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
from urllib.parse import unquote_to_bytes as unquote
|
from urllib.parse import unquote_to_bytes as unquote
|
||||||
from urllib.parse import quote
|
|
||||||
|
|
||||||
have_mpv = shutil.which("mpv")
|
|
||||||
have_vlc = shutil.which("vlc")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) > 2 and sys.argv[1] == "x":
|
|
||||||
# invoked on commandline for testing;
|
|
||||||
# python3 very-bad-idea.py x msg=https://youtu.be/dQw4w9WgXcQ
|
|
||||||
txt = " ".join(sys.argv[2:])
|
|
||||||
txt = quote(txt.replace(" ", "+"))
|
|
||||||
return open_post(txt.encode("utf-8"))
|
|
||||||
|
|
||||||
fp = os.path.abspath(sys.argv[1])
|
fp = os.path.abspath(sys.argv[1])
|
||||||
with open(fp, "rb") as f:
|
with open(fp, "rb") as f:
|
||||||
txt = f.read(4096)
|
txt = f.read(4096)
|
||||||
|
@ -117,7 +92,7 @@ def open_post(txt):
|
||||||
try:
|
try:
|
||||||
k, v = txt.split(" ", 1)
|
k, v = txt.split(" ", 1)
|
||||||
except:
|
except:
|
||||||
return open_url(txt)
|
open_url(txt)
|
||||||
|
|
||||||
if k == "key":
|
if k == "key":
|
||||||
sp.call(["xdotool", "key"] + v.split(" "))
|
sp.call(["xdotool", "key"] + v.split(" "))
|
||||||
|
@ -153,17 +128,6 @@ def open_url(txt):
|
||||||
# else:
|
# else:
|
||||||
# sp.call(["xdotool", "getactivewindow", "windowminimize"]) # minimizes the focused windo
|
# sp.call(["xdotool", "getactivewindow", "windowminimize"]) # minimizes the focused windo
|
||||||
|
|
||||||
# mpv is probably smart enough to use streamlink automatically
|
|
||||||
if try_mpv(txt):
|
|
||||||
print("mpv got it")
|
|
||||||
return
|
|
||||||
|
|
||||||
# or maybe streamlink would be a good choice to open this
|
|
||||||
if try_streamlink(txt):
|
|
||||||
print("streamlink got it")
|
|
||||||
return
|
|
||||||
|
|
||||||
# nope,
|
|
||||||
# close any error messages:
|
# close any error messages:
|
||||||
sp.call(["xdotool", "search", "--name", "Error", "windowclose"])
|
sp.call(["xdotool", "search", "--name", "Error", "windowclose"])
|
||||||
# sp.call(["xdotool", "key", "ctrl+alt+d"]) # doesnt work at all
|
# sp.call(["xdotool", "key", "ctrl+alt+d"]) # doesnt work at all
|
||||||
|
@ -172,39 +136,4 @@ def open_url(txt):
|
||||||
sp.call(["xdg-open", txt])
|
sp.call(["xdg-open", txt])
|
||||||
|
|
||||||
|
|
||||||
def try_mpv(url):
|
|
||||||
t0 = time.time()
|
|
||||||
try:
|
|
||||||
print("trying mpv...")
|
|
||||||
sp.check_call(["mpv", "--fs", url])
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
# if it ran for 15 sec it probably succeeded and terminated
|
|
||||||
t = time.time()
|
|
||||||
return t - t0 > 15
|
|
||||||
|
|
||||||
|
|
||||||
def try_streamlink(url):
|
|
||||||
t0 = time.time()
|
|
||||||
try:
|
|
||||||
import streamlink
|
|
||||||
|
|
||||||
print("trying streamlink...")
|
|
||||||
streamlink.Streamlink().resolve_url(url)
|
|
||||||
|
|
||||||
if have_mpv:
|
|
||||||
args = "-m streamlink -p mpv -a --fs"
|
|
||||||
else:
|
|
||||||
args = "-m streamlink"
|
|
||||||
|
|
||||||
cmd = [sys.executable] + args.split() + [url, "best"]
|
|
||||||
t0 = time.time()
|
|
||||||
sp.check_call(cmd)
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
# if it ran for 10 sec it probably succeeded and terminated
|
|
||||||
t = time.time()
|
|
||||||
return t - t0 > 10
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -65,10 +65,6 @@ def main():
|
||||||
if "://" not in url:
|
if "://" not in url:
|
||||||
url = "https://" + url
|
url = "https://" + url
|
||||||
|
|
||||||
proto = url.split("://")[0].lower()
|
|
||||||
if proto not in ("http", "https", "ftp", "ftps"):
|
|
||||||
raise Exception("bad proto {}".format(proto))
|
|
||||||
|
|
||||||
os.chdir(fdir)
|
os.chdir(fdir)
|
||||||
|
|
||||||
name = url.split("?")[0].split("/")[-1]
|
name = url.split("?")[0].split("/")[-1]
|
||||||
|
@ -84,7 +80,7 @@ def main():
|
||||||
# on success, delete the .bin file which contains the URL
|
# on success, delete the .bin file which contains the URL
|
||||||
os.unlink(fp)
|
os.unlink(fp)
|
||||||
except:
|
except:
|
||||||
open("-- FAILED TO DOWNLOAD " + name, "wb").close()
|
open("-- FAILED TO DONWLOAD " + name, "wb").close()
|
||||||
|
|
||||||
os.unlink(tfn)
|
os.unlink(tfn)
|
||||||
print(url)
|
print(url)
|
||||||
|
|
697
bin/partyfuse.py
697
bin/partyfuse.py
File diff suppressed because it is too large
Load diff
|
@ -20,13 +20,12 @@ import sys
|
||||||
import base64
|
import base64
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import argparse
|
import argparse
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime
|
||||||
from urllib.parse import quote_from_bytes as quote
|
from urllib.parse import quote_from_bytes as quote
|
||||||
from urllib.parse import unquote_to_bytes as unquote
|
from urllib.parse import unquote_to_bytes as unquote
|
||||||
|
|
||||||
|
|
||||||
FS_ENCODING = sys.getfilesystemencoding()
|
FS_ENCODING = sys.getfilesystemencoding()
|
||||||
UTC = timezone.utc
|
|
||||||
|
|
||||||
|
|
||||||
class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
|
class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
|
||||||
|
@ -156,10 +155,11 @@ th {
|
||||||
link = txt.decode("utf-8")[4:]
|
link = txt.decode("utf-8")[4:]
|
||||||
|
|
||||||
sz = "{:,}".format(sz)
|
sz = "{:,}".format(sz)
|
||||||
dt = datetime.fromtimestamp(at if at > 0 else mt, UTC)
|
|
||||||
v = [
|
v = [
|
||||||
w[:16],
|
w[:16],
|
||||||
dt.strftime("%Y-%m-%d %H:%M:%S"),
|
datetime.utcfromtimestamp(at if at > 0 else mt).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
sz,
|
sz,
|
||||||
imap.get(ip, ip),
|
imap.get(ip, ip),
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,21 +4,20 @@ set -e
|
||||||
# runs copyparty (or any other program really) in a chroot
|
# runs copyparty (or any other program really) in a chroot
|
||||||
#
|
#
|
||||||
# assumption: these directories, and everything within, are owned by root
|
# assumption: these directories, and everything within, are owned by root
|
||||||
sysdirs=(); for v in /bin /lib /lib32 /lib64 /sbin /usr /etc/alternatives ; do
|
sysdirs=( /bin /lib /lib32 /lib64 /sbin /usr )
|
||||||
[ -e $v ] && sysdirs+=($v)
|
|
||||||
done
|
|
||||||
|
|
||||||
# error-handler
|
# error-handler
|
||||||
help() { cat <<'EOF'
|
help() { cat <<'EOF'
|
||||||
|
|
||||||
usage:
|
usage:
|
||||||
./prisonparty.sh <ROOTDIR> <USER|UID> <GROUP|GID> [VOLDIR [VOLDIR...]] -- python3 copyparty-sfx.py [...]
|
./prisonparty.sh <ROOTDIR> <UID> <GID> [VOLDIR [VOLDIR...]] -- python3 copyparty-sfx.py [...]
|
||||||
|
|
||||||
example:
|
example:
|
||||||
./prisonparty.sh /var/lib/copyparty-jail cpp cpp /mnt/nas/music -- python3 copyparty-sfx.py -v /mnt/nas/music::rwmd
|
./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- python3 copyparty-sfx.py -v /mnt/nas/music::rwmd
|
||||||
|
|
||||||
example for running straight from source (instead of using an sfx):
|
example for running straight from source (instead of using an sfx):
|
||||||
PYTHONPATH=$PWD ./prisonparty.sh /var/lib/copyparty-jail cpp cpp /mnt/nas/music -- python3 -um copyparty -v /mnt/nas/music::rwmd
|
PYTHONPATH=$PWD ./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- python3 -um copyparty -v /mnt/nas/music::rwmd
|
||||||
|
|
||||||
note that if you have python modules installed as --user (such as bpm/key detectors),
|
note that if you have python modules installed as --user (such as bpm/key detectors),
|
||||||
you should add /home/foo/.local as a VOLDIR
|
you should add /home/foo/.local as a VOLDIR
|
||||||
|
@ -28,16 +27,6 @@ exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
errs=
|
|
||||||
for c in awk chroot dirname getent lsof mknod mount realpath sed sort stat uniq; do
|
|
||||||
command -v $c >/dev/null || {
|
|
||||||
echo ERROR: command not found: $c
|
|
||||||
errs=1
|
|
||||||
}
|
|
||||||
done
|
|
||||||
[ $errs ] && exit 1
|
|
||||||
|
|
||||||
|
|
||||||
# read arguments
|
# read arguments
|
||||||
trap help EXIT
|
trap help EXIT
|
||||||
jail="$(realpath "$1")"; shift
|
jail="$(realpath "$1")"; shift
|
||||||
|
@ -49,7 +38,7 @@ while true; do
|
||||||
v="$1"; shift
|
v="$1"; shift
|
||||||
[ "$v" = -- ] && break # end of volumes
|
[ "$v" = -- ] && break # end of volumes
|
||||||
[ "$#" -eq 0 ] && break # invalid usage
|
[ "$#" -eq 0 ] && break # invalid usage
|
||||||
vols+=( "$(realpath "$v" || echo "$v")" )
|
vols+=( "$(realpath "$v")" )
|
||||||
done
|
done
|
||||||
pybin="$1"; shift
|
pybin="$1"; shift
|
||||||
pybin="$(command -v "$pybin")"
|
pybin="$(command -v "$pybin")"
|
||||||
|
@ -68,18 +57,11 @@ cpp="$1"; shift
|
||||||
}
|
}
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
|
|
||||||
usr="$(getent passwd $uid | cut -d: -f1)"
|
|
||||||
[ "$usr" ] || { echo "ERROR invalid username/uid $uid"; exit 1; }
|
|
||||||
uid="$(getent passwd $uid | cut -d: -f3)"
|
|
||||||
|
|
||||||
grp="$(getent group $gid | cut -d: -f1)"
|
|
||||||
[ "$grp" ] || { echo "ERROR invalid groupname/gid $gid"; exit 1; }
|
|
||||||
gid="$(getent group $gid | cut -d: -f3)"
|
|
||||||
|
|
||||||
# debug/vis
|
# debug/vis
|
||||||
echo
|
echo
|
||||||
echo "chroot-dir = $jail"
|
echo "chroot-dir = $jail"
|
||||||
echo "user:group = $uid:$gid ($usr:$grp)"
|
echo "user:group = $uid:$gid"
|
||||||
echo " copyparty = $cpp"
|
echo " copyparty = $cpp"
|
||||||
echo
|
echo
|
||||||
printf '\033[33m%s\033[0m\n' "copyparty can access these folders and all their subdirectories:"
|
printf '\033[33m%s\033[0m\n' "copyparty can access these folders and all their subdirectories:"
|
||||||
|
@ -97,39 +79,32 @@ jail="${jail%/}"
|
||||||
|
|
||||||
|
|
||||||
# bind-mount system directories and volumes
|
# bind-mount system directories and volumes
|
||||||
for a in {1..30}; do mkdir "$jail/.prisonlock" && break; sleep 0.1; done
|
|
||||||
printf '%s\n' "${sysdirs[@]}" "${vols[@]}" | sed -r 's`/$``' | LC_ALL=C sort | uniq |
|
printf '%s\n' "${sysdirs[@]}" "${vols[@]}" | sed -r 's`/$``' | LC_ALL=C sort | uniq |
|
||||||
while IFS= read -r v; do
|
while IFS= read -r v; do
|
||||||
[ -e "$v" ] || {
|
[ -e "$v" ] || {
|
||||||
printf '\033[1;31mfolder does not exist:\033[0m %s\n' "$v"
|
# printf '\033[1;31mfolder does not exist:\033[0m %s\n' "/$v"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
i1=$(stat -c%D.%i "$v/" 2>/dev/null || echo a)
|
i1=$(stat -c%D.%i "$v" 2>/dev/null || echo a)
|
||||||
i2=$(stat -c%D.%i "$jail$v/" 2>/dev/null || echo b)
|
i2=$(stat -c%D.%i "$jail$v" 2>/dev/null || echo b)
|
||||||
|
# echo "v [$v] i1 [$i1] i2 [$i2]"
|
||||||
[ $i1 = $i2 ] && continue
|
[ $i1 = $i2 ] && continue
|
||||||
mount | grep -qF " $jail$v " && echo wtf $i1 $i2 $v && continue
|
|
||||||
mkdir -p "$jail$v"
|
mkdir -p "$jail$v"
|
||||||
mount --bind "$v" "$jail$v"
|
mount --bind "$v" "$jail$v"
|
||||||
done
|
done
|
||||||
rmdir "$jail/.prisonlock" || true
|
|
||||||
|
|
||||||
|
|
||||||
cln() {
|
cln() {
|
||||||
trap - EXIT
|
rv=$?
|
||||||
wait -f -n $p && rv=0 || rv=$?
|
# cleanup if not in use
|
||||||
cd /
|
lsof "$jail" | grep -qF "$jail" &&
|
||||||
echo "stopping chroot..."
|
echo "chroot is in use, will not cleanup" ||
|
||||||
for a in {1..30}; do mkdir "$jail/.prisonlock" && break; sleep 0.1; done
|
|
||||||
lsof "$jail" 2>/dev/null | grep -F "$jail" &&
|
|
||||||
echo "chroot is in use; will not unmount" ||
|
|
||||||
{
|
{
|
||||||
mount | grep -F " on $jail" |
|
mount | grep -F " on $jail" |
|
||||||
awk '{sub(/ type .*/,"");sub(/.* on /,"");print}' |
|
awk '{sub(/ type .*/,"");sub(/.* on /,"");print}' |
|
||||||
LC_ALL=C sort -r | while IFS= read -r v; do
|
LC_ALL=C sort -r | tee /dev/stderr | tr '\n' '\0' | xargs -r0 umount
|
||||||
umount "$v" && echo "umount OK: $v"
|
|
||||||
done
|
|
||||||
}
|
}
|
||||||
rmdir "$jail/.prisonlock" || true
|
|
||||||
exit $rv
|
exit $rv
|
||||||
}
|
}
|
||||||
trap cln EXIT
|
trap cln EXIT
|
||||||
|
@ -140,24 +115,14 @@ mkdir -p "$jail/tmp"
|
||||||
chmod 777 "$jail/tmp"
|
chmod 777 "$jail/tmp"
|
||||||
|
|
||||||
|
|
||||||
# create a dev
|
|
||||||
(cd $jail; mkdir -p dev; cd dev
|
|
||||||
[ -e null ] || mknod -m 666 null c 1 3
|
|
||||||
[ -e zero ] || mknod -m 666 zero c 1 5
|
|
||||||
[ -e random ] || mknod -m 444 random c 1 8
|
|
||||||
[ -e urandom ] || mknod -m 444 urandom c 1 9
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# run copyparty
|
# run copyparty
|
||||||
export HOME="$(getent passwd $uid | cut -d: -f6)"
|
export HOME=$(getent passwd $uid | cut -d: -f6)
|
||||||
export USER="$usr"
|
export USER=$(getent passwd $uid | cut -d: -f1)
|
||||||
export LOGNAME="$USER"
|
export LOGNAME="$USER"
|
||||||
#echo "pybin [$pybin]"
|
#echo "pybin [$pybin]"
|
||||||
#echo "pyarg [$pyarg]"
|
#echo "pyarg [$pyarg]"
|
||||||
#echo "cpp [$cpp]"
|
#echo "cpp [$cpp]"
|
||||||
chroot --userspec=$uid:$gid "$jail" "$pybin" $pyarg "$cpp" "$@" &
|
chroot --userspec=$uid:$gid "$jail" "$pybin" $pyarg "$cpp" "$@" &
|
||||||
p=$!
|
p=$!
|
||||||
trap 'kill -USR1 $p' USR1
|
trap 'kill $p' INT TERM
|
||||||
trap 'trap - INT TERM; kill $p' INT TERM
|
|
||||||
wait
|
wait
|
||||||
|
|
1718
bin/u2c.py
1718
bin/u2c.py
File diff suppressed because it is too large
Load diff
|
@ -66,7 +66,7 @@ def main():
|
||||||
ofs = ln.find("{")
|
ofs = ln.find("{")
|
||||||
j = json.loads(ln[ofs:])
|
j = json.loads(ln[ofs:])
|
||||||
except:
|
except:
|
||||||
continue
|
pass
|
||||||
|
|
||||||
w = j["wark"]
|
w = j["wark"]
|
||||||
if db.execute("select w from up where w = ?", (w,)).fetchone():
|
if db.execute("select w from up where w = ?", (w,)).fetchone():
|
||||||
|
|
1074
bin/up2k.py
Executable file
1074
bin/up2k.py
Executable file
File diff suppressed because it is too large
Load diff
|
@ -1,76 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import zmq
|
|
||||||
|
|
||||||
"""
|
|
||||||
zmq-recv.py: demo zmq receiver
|
|
||||||
2025-01-22, v1.0, ed <irc.rizon.net>, MIT-Licensed
|
|
||||||
https://github.com/9001/copyparty/blob/hovudstraum/bin/zmq-recv.py
|
|
||||||
|
|
||||||
basic zmq-server to receive events from copyparty; try one of
|
|
||||||
the below and then "send a message to serverlog" in the web-ui:
|
|
||||||
|
|
||||||
1) dumb fire-and-forget to any and all listeners;
|
|
||||||
run this script with "sub" and run copyparty with this:
|
|
||||||
--xm zmq:pub:tcp://*:5556
|
|
||||||
|
|
||||||
2) one lucky listener gets the message, blocks if no listeners:
|
|
||||||
run this script with "pull" and run copyparty with this:
|
|
||||||
--xm t3,zmq:push:tcp://*:5557
|
|
||||||
|
|
||||||
3) blocking syn/ack mode, client must ack each message;
|
|
||||||
run this script with "rep" and run copyparty with this:
|
|
||||||
--xm t3,zmq:req:tcp://localhost:5555
|
|
||||||
|
|
||||||
note: to conditionally block uploads based on message contents,
|
|
||||||
use rep_server to answer with "return 1" and run copyparty with
|
|
||||||
--xau t3,c,zmq:req:tcp://localhost:5555
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
ctx = zmq.Context()
|
|
||||||
|
|
||||||
|
|
||||||
def sub_server():
|
|
||||||
# PUB/SUB allows any number of servers/clients, and
|
|
||||||
# messages are fire-and-forget
|
|
||||||
sck = ctx.socket(zmq.SUB)
|
|
||||||
sck.connect("tcp://localhost:5556")
|
|
||||||
sck.setsockopt_string(zmq.SUBSCRIBE, "")
|
|
||||||
while True:
|
|
||||||
print("copyparty says %r" % (sck.recv_string(),))
|
|
||||||
|
|
||||||
|
|
||||||
def pull_server():
|
|
||||||
# PUSH/PULL allows any number of servers/clients, and
|
|
||||||
# each message is sent to a exactly one PULL client
|
|
||||||
sck = ctx.socket(zmq.PULL)
|
|
||||||
sck.connect("tcp://localhost:5557")
|
|
||||||
while True:
|
|
||||||
print("copyparty says %r" % (sck.recv_string(),))
|
|
||||||
|
|
||||||
|
|
||||||
def rep_server():
|
|
||||||
# REP/REQ is a server/client pair where each message must be
|
|
||||||
# acked by the other before another message can be sent, so
|
|
||||||
# copyparty will do a blocking-wait for the ack
|
|
||||||
sck = ctx.socket(zmq.REP)
|
|
||||||
sck.bind("tcp://*:5555")
|
|
||||||
while True:
|
|
||||||
print("copyparty says %r" % (sck.recv_string(),))
|
|
||||||
reply = b"thx"
|
|
||||||
# reply = b"return 1" # non-zero to block an upload
|
|
||||||
sck.send(reply)
|
|
||||||
|
|
||||||
|
|
||||||
mode = sys.argv[1].lower() if len(sys.argv) > 1 else ""
|
|
||||||
|
|
||||||
if mode == "sub":
|
|
||||||
sub_server()
|
|
||||||
elif mode == "pull":
|
|
||||||
pull_server()
|
|
||||||
elif mode == "rep":
|
|
||||||
rep_server()
|
|
||||||
else:
|
|
||||||
print("specify mode as first argument: SUB | PULL | REP")
|
|
|
@ -12,25 +12,15 @@
|
||||||
* assumes the webserver and copyparty is running on the same server/IP
|
* assumes the webserver and copyparty is running on the same server/IP
|
||||||
* modify `10.13.1.1` as necessary if you wish to support browsers without javascript
|
* modify `10.13.1.1` as necessary if you wish to support browsers without javascript
|
||||||
|
|
||||||
### [`sharex.sxcu`](sharex.sxcu) - Windows screenshot uploader
|
### [`sharex.sxcu`](sharex.sxcu)
|
||||||
* [sharex](https://getsharex.com/) config file to upload screenshots and grab the URL
|
* sharex config file to upload screenshots and grab the URL
|
||||||
* `RequestURL`: full URL to the target folder
|
|
||||||
* `pw`: password (remove the `pw` line if anon-write)
|
|
||||||
* the `act:bput` thing is optional since copyparty v1.9.29
|
|
||||||
* using an older sharex version, maybe sharex v12.1.1 for example? dw fam i got your back 👉😎👉 [`sharex12.sxcu`](sharex12.sxcu)
|
|
||||||
|
|
||||||
### [`ishare.iscu`](ishare.iscu) - MacOS screenshot uploader
|
|
||||||
* [ishare](https://isharemac.app/) config file to upload screenshots and grab the URL
|
|
||||||
* `RequestURL`: full URL to the target folder
|
* `RequestURL`: full URL to the target folder
|
||||||
* `pw`: password (remove the `pw` line if anon-write)
|
* `pw`: password (remove the `pw` line if anon-write)
|
||||||
|
|
||||||
### [`flameshot.sh`](flameshot.sh) - Linux screenshot uploader
|
however if your copyparty is behind a reverse-proxy, you may want to use [`sharex-html.sxcu`](sharex-html.sxcu) instead:
|
||||||
* takes a screenshot with [flameshot](https://flameshot.org/) on Linux, uploads it, and writes the URL to clipboard
|
* `RequestURL`: full URL to the target folder
|
||||||
|
* `URL`: full URL to the root folder (with trailing slash) followed by `$regex:1|1$`
|
||||||
### [`send-to-cpp.contextlet.json`](send-to-cpp.contextlet.json)
|
* `pw`: password (remove `Parameters` if anon-write)
|
||||||
* browser integration, kind of? custom rightclick actions and stuff
|
|
||||||
* rightclick a pic and send it to copyparty straight from your browser
|
|
||||||
* for the [contextlet](https://addons.mozilla.org/en-US/firefox/addon/contextlets/) firefox extension
|
|
||||||
|
|
||||||
### [`media-osd-bgone.ps1`](media-osd-bgone.ps1)
|
### [`media-osd-bgone.ps1`](media-osd-bgone.ps1)
|
||||||
* disables the [windows OSD popup](https://user-images.githubusercontent.com/241032/122821375-0e08df80-d2dd-11eb-9fd9-184e8aacf1d0.png) (the thing on the left) which appears every time you hit media hotkeys to adjust volume or change song while playing music with the copyparty web-ui, or most other audio players really
|
* disables the [windows OSD popup](https://user-images.githubusercontent.com/241032/122821375-0e08df80-d2dd-11eb-9fd9-184e8aacf1d0.png) (the thing on the left) which appears every time you hit media hotkeys to adjust volume or change song while playing music with the copyparty web-ui, or most other audio players really
|
||||||
|
@ -50,9 +40,6 @@
|
||||||
* give a 3rd argument to install it to your copyparty config
|
* give a 3rd argument to install it to your copyparty config
|
||||||
* systemd service at [`systemd/cfssl.service`](systemd/cfssl.service)
|
* systemd service at [`systemd/cfssl.service`](systemd/cfssl.service)
|
||||||
|
|
||||||
### [`zfs-tune.py`](zfs-tune.py)
|
|
||||||
* optimizes databases for optimal performance when stored on a zfs filesystem; also see [openzfs docs](https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Workload%20Tuning.html#database-workloads) and specifically the SQLite subsection
|
|
||||||
|
|
||||||
# OS integration
|
# OS integration
|
||||||
init-scripts to start copyparty as a service
|
init-scripts to start copyparty as a service
|
||||||
* [`systemd/copyparty.service`](systemd/copyparty.service) runs the sfx normally
|
* [`systemd/copyparty.service`](systemd/copyparty.service) runs the sfx normally
|
||||||
|
@ -61,10 +48,5 @@ init-scripts to start copyparty as a service
|
||||||
* [`openrc/copyparty`](openrc/copyparty)
|
* [`openrc/copyparty`](openrc/copyparty)
|
||||||
|
|
||||||
# Reverse-proxy
|
# Reverse-proxy
|
||||||
copyparty supports running behind another webserver
|
copyparty has basic support for running behind another webserver
|
||||||
* [`apache/copyparty.conf`](apache/copyparty.conf)
|
* [`nginx/copyparty.conf`](nginx/copyparty.conf)
|
||||||
* [`haproxy/copyparty.conf`](haproxy/copyparty.conf)
|
|
||||||
* [`lighttpd/subdomain.conf`](lighttpd/subdomain.conf)
|
|
||||||
* [`lighttpd/subpath.conf`](lighttpd/subpath.conf)
|
|
||||||
* [`nginx/copyparty.conf`](nginx/copyparty.conf) -- recommended
|
|
||||||
* [`traefik/copyparty.yaml`](traefik/copyparty.yaml)
|
|
||||||
|
|
|
@ -1,29 +1,15 @@
|
||||||
# if you would like to use unix-sockets (recommended),
|
# when running copyparty behind a reverse proxy,
|
||||||
# you must run copyparty with one of the following:
|
# the following arguments are recommended:
|
||||||
#
|
#
|
||||||
# -i unix:777:/dev/shm/party.sock
|
# --http-only lower latency on initial connection
|
||||||
# -i unix:777:/dev/shm/party.sock,127.0.0.1
|
# -i 127.0.0.1 only accept connections from nginx
|
||||||
#
|
#
|
||||||
# if you are doing location-based proxying (such as `/stuff` below)
|
# if you are doing location-based proxying (such as `/stuff` below)
|
||||||
# you must run copyparty with --rp-loc=stuff
|
# you must run copyparty with --rp-loc=stuff
|
||||||
#
|
#
|
||||||
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
||||||
|
|
||||||
|
|
||||||
LoadModule proxy_module modules/mod_proxy.so
|
LoadModule proxy_module modules/mod_proxy.so
|
||||||
|
ProxyPass "/stuff" "http://127.0.0.1:3923/stuff"
|
||||||
|
# do not specify ProxyPassReverse
|
||||||
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
|
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
|
||||||
# NOTE: do not specify ProxyPassReverse
|
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
## then, enable one of the below:
|
|
||||||
|
|
||||||
# use subdomain proxying to unix-socket (best)
|
|
||||||
ProxyPass "/" "unix:///dev/shm/party.sock|http://whatever/"
|
|
||||||
|
|
||||||
# use subdomain proxying to 127.0.0.1 (slower)
|
|
||||||
#ProxyPass "/" "http://127.0.0.1:3923/"
|
|
||||||
|
|
||||||
# use subpath proxying to 127.0.0.1 (slow and maybe buggy)
|
|
||||||
#ProxyPass "/stuff" "http://127.0.0.1:3923/stuff"
|
|
||||||
|
|
|
@ -1,44 +1,14 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
cat >/dev/null <<'EOF'
|
|
||||||
|
|
||||||
NOTE: copyparty is now able to do this automatically;
|
|
||||||
however you may wish to use this script instead if
|
|
||||||
you have specific needs (or if copyparty breaks)
|
|
||||||
|
|
||||||
this script generates a new self-signed TLS certificate and
|
|
||||||
replaces the default insecure one that comes with copyparty
|
|
||||||
|
|
||||||
as it is trivial to impersonate a copyparty server using the
|
|
||||||
default certificate, it is highly recommended to do this
|
|
||||||
|
|
||||||
this will create a self-signed CA, and a Server certificate
|
|
||||||
which gets signed by that CA -- you can run it multiple times
|
|
||||||
with different server-FQDNs / IPs to create additional certs
|
|
||||||
for all your different servers / (non-)copyparty services
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
|
|
||||||
# ca-name and server-fqdn
|
# ca-name and server-fqdn
|
||||||
ca_name="$1"
|
ca_name="$1"
|
||||||
srv_fqdn="$2"
|
srv_fqdn="$2"
|
||||||
|
|
||||||
[ -z "$srv_fqdn" ] && { cat <<'EOF'
|
[ -z "$srv_fqdn" ] && {
|
||||||
need arg 1: ca name
|
echo "need arg 1: ca name"
|
||||||
need arg 2: server fqdn and/or IPs, comma-separated
|
echo "need arg 2: server fqdn and/or IPs, comma-separated"
|
||||||
optional arg 3: if set, write cert into copyparty cfg
|
echo "optional arg 3: if set, write cert into copyparty cfg"
|
||||||
|
|
||||||
example:
|
|
||||||
./cfssl.sh PartyCo partybox.local y
|
|
||||||
EOF
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
command -v cfssljson 2>/dev/null || {
|
|
||||||
echo please install cfssl and try again
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,14 +59,12 @@ show() {
|
||||||
}
|
}
|
||||||
show ca.pem
|
show ca.pem
|
||||||
show "$srv_fqdn.pem"
|
show "$srv_fqdn.pem"
|
||||||
echo
|
|
||||||
echo "successfully generated new certificates"
|
|
||||||
|
|
||||||
# write cert into copyparty config
|
# write cert into copyparty config
|
||||||
[ -z "$3" ] || {
|
[ -z "$3" ] || {
|
||||||
mkdir -p ~/.config/copyparty
|
mkdir -p ~/.config/copyparty
|
||||||
cat "$srv_fqdn".{key,pem} ca.pem >~/.config/copyparty/cert.pem
|
cat "$srv_fqdn".{key,pem} ca.pem >~/.config/copyparty/cert.pem
|
||||||
echo "successfully replaced copyparty certificate"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# take a screenshot with flameshot and send it to copyparty;
|
|
||||||
# the image url will be placed on your clipboard
|
|
||||||
|
|
||||||
password=wark
|
|
||||||
url=https://a.ocv.me/up/
|
|
||||||
filename=$(date +%Y-%m%d-%H%M%S).png
|
|
||||||
|
|
||||||
flameshot gui -s -r |
|
|
||||||
curl -T- $url$filename?pw=$password |
|
|
||||||
tail -n 1 |
|
|
||||||
xsel -ib
|
|
|
@ -1,24 +0,0 @@
|
||||||
# this config is essentially two separate examples;
|
|
||||||
#
|
|
||||||
# foo1 connects to copyparty using tcp, and
|
|
||||||
# foo2 uses unix-sockets for 27% higher performance
|
|
||||||
#
|
|
||||||
# to use foo2 you must run copyparty with one of the following:
|
|
||||||
#
|
|
||||||
# -i unix:777:/dev/shm/party.sock
|
|
||||||
# -i unix:777:/dev/shm/party.sock,127.0.0.1
|
|
||||||
|
|
||||||
defaults
|
|
||||||
mode http
|
|
||||||
option forwardfor
|
|
||||||
timeout connect 1s
|
|
||||||
timeout client 610s
|
|
||||||
timeout server 610s
|
|
||||||
|
|
||||||
listen foo1
|
|
||||||
bind *:8081
|
|
||||||
server srv1 127.0.0.1:3923 maxconn 512
|
|
||||||
|
|
||||||
listen foo2
|
|
||||||
bind *:8082
|
|
||||||
server srv1 /dev/shm/party.sock maxconn 512
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>💾🎉 redirect</title>
|
<title>⇆🎉 redirect</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ a {
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var a = document.getElementById('redir'),
|
var a = document.getElementById('redir'),
|
||||||
proto = location.protocol.indexOf('https') === 0 ? 'https' : 'http',
|
proto = window.location.protocol.indexOf('https') === 0 ? 'https' : 'http',
|
||||||
loc = location.hostname || '127.0.0.1',
|
loc = window.location.hostname || '127.0.0.1',
|
||||||
port = a.getAttribute('href').split(':').pop().split('/')[0],
|
port = a.getAttribute('href').split(':').pop().split('/')[0],
|
||||||
url = proto + '://' + loc + ':' + port + '/';
|
url = proto + '://' + loc + ':' + port + '/';
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ a.setAttribute('href', url);
|
||||||
document.getElementById('desc').innerHTML = 'redirecting to';
|
document.getElementById('desc').innerHTML = 'redirecting to';
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
location.href = url;
|
window.location.href = url;
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
Binary file not shown.
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"Name": "copyparty",
|
|
||||||
"RequestURL": "http://127.0.0.1:3923/screenshots/",
|
|
||||||
"Headers": {
|
|
||||||
"pw": "PUT_YOUR_PASSWORD_HERE_MY_DUDE",
|
|
||||||
"accept": "json"
|
|
||||||
},
|
|
||||||
"FileFormName": "f",
|
|
||||||
"ResponseURL": "{{fileurl}}"
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
# example usage for benchmarking:
|
|
||||||
#
|
|
||||||
# taskset -c 1 lighttpd -Df ~/dev/copyparty/contrib/lighttpd/subdomain.conf
|
|
||||||
#
|
|
||||||
# lighttpd can connect to copyparty using either tcp (127.0.0.1)
|
|
||||||
# or a unix-socket, but unix-sockets are 37% faster because
|
|
||||||
# lighttpd doesn't reuse tcp connections, so we're doing unix-sockets
|
|
||||||
#
|
|
||||||
# this means we must run copyparty with one of the following:
|
|
||||||
#
|
|
||||||
# -i unix:777:/dev/shm/party.sock
|
|
||||||
# -i unix:777:/dev/shm/party.sock,127.0.0.1
|
|
||||||
#
|
|
||||||
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
|
||||||
|
|
||||||
server.port = 80
|
|
||||||
server.document-root = "/var/empty"
|
|
||||||
server.upload-dirs = ( "/dev/shm", "/tmp" )
|
|
||||||
server.modules = ( "mod_proxy" )
|
|
||||||
proxy.forwarded = ( "for" => 1, "proto" => 1 )
|
|
||||||
proxy.server = ( "" => ( ( "host" => "/dev/shm/party.sock" ) ) )
|
|
||||||
|
|
||||||
# if you really need to use tcp instead of unix-sockets, do this instead:
|
|
||||||
#proxy.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "3923" ) ) )
|
|
|
@ -1,31 +0,0 @@
|
||||||
# example usage for benchmarking:
|
|
||||||
#
|
|
||||||
# taskset -c 1 lighttpd -Df ~/dev/copyparty/contrib/lighttpd/subpath.conf
|
|
||||||
#
|
|
||||||
# lighttpd can connect to copyparty using either tcp (127.0.0.1)
|
|
||||||
# or a unix-socket, but unix-sockets are 37% faster because
|
|
||||||
# lighttpd doesn't reuse tcp connections, so we're doing unix-sockets
|
|
||||||
#
|
|
||||||
# this means we must run copyparty with one of the following:
|
|
||||||
#
|
|
||||||
# -i unix:777:/dev/shm/party.sock
|
|
||||||
# -i unix:777:/dev/shm/party.sock,127.0.0.1
|
|
||||||
#
|
|
||||||
# also since this example proxies a subpath instead of the
|
|
||||||
# recommended subdomain-proxying, we must also specify this:
|
|
||||||
#
|
|
||||||
# --rp-loc files
|
|
||||||
#
|
|
||||||
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
|
||||||
|
|
||||||
server.port = 80
|
|
||||||
server.document-root = "/var/empty"
|
|
||||||
server.upload-dirs = ( "/dev/shm", "/tmp" )
|
|
||||||
server.modules = ( "mod_proxy" )
|
|
||||||
$HTTP["url"] =~ "^/files" {
|
|
||||||
proxy.forwarded = ( "for" => 1, "proto" => 1 )
|
|
||||||
proxy.server = ( "" => ( ( "host" => "/dev/shm/party.sock" ) ) )
|
|
||||||
|
|
||||||
# if you really need to use tcp instead of unix-sockets, do this instead:
|
|
||||||
#proxy.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "3923" ) ) )
|
|
||||||
}
|
|
|
@ -1,103 +1,41 @@
|
||||||
# look for "max clients:" when starting copyparty, as nginx should
|
# when running copyparty behind a reverse proxy,
|
||||||
# not accept more consecutive clients than what copyparty is able to;
|
# the following arguments are recommended:
|
||||||
|
#
|
||||||
|
# --http-only lower latency on initial connection
|
||||||
|
# -i 127.0.0.1 only accept connections from nginx
|
||||||
|
#
|
||||||
|
# -nc must match or exceed the webserver's max number of concurrent clients;
|
||||||
|
# copyparty default is 1024 if OS permits it (see "max clients:" on startup),
|
||||||
# nginx default is 512 (worker_processes 1, worker_connections 512)
|
# nginx default is 512 (worker_processes 1, worker_connections 512)
|
||||||
#
|
#
|
||||||
# ======================================================================
|
# you may also consider adding -j0 for CPU-intensive configurations
|
||||||
#
|
# (not that i can really think of any good examples)
|
||||||
# to reverse-proxy a specific path/subpath/location below a domain
|
|
||||||
# (rather than a complete subdomain), for example "/qw/er", you must
|
|
||||||
# run copyparty with --rp-loc /qw/as and also change the following:
|
|
||||||
# location / {
|
|
||||||
# proxy_pass http://cpp_tcp;
|
|
||||||
# to this:
|
|
||||||
# location /qw/er/ {
|
|
||||||
# proxy_pass http://cpp_tcp/qw/er/;
|
|
||||||
#
|
|
||||||
# ======================================================================
|
|
||||||
#
|
|
||||||
# rarely, in some extreme usecases, it can be good to add -j0
|
|
||||||
# (40'000 requests per second, or 20gbps upload/download in parallel)
|
|
||||||
# but this is usually counterproductive and slightly buggy
|
|
||||||
#
|
|
||||||
# ======================================================================
|
|
||||||
#
|
#
|
||||||
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
||||||
#
|
|
||||||
# ======================================================================
|
|
||||||
#
|
|
||||||
# if you are behind cloudflare (or another CDN/WAF/protection service),
|
|
||||||
# remember to reject all connections which are not coming from your
|
|
||||||
# protection service -- for cloudflare in particular, you can
|
|
||||||
# generate the list of permitted IP ranges like so:
|
|
||||||
# (curl -s https://www.cloudflare.com/ips-v{4,6} | sed 's/^/allow /; s/$/;/'; echo; echo "deny all;") > /etc/nginx/cloudflare-only.conf
|
|
||||||
#
|
|
||||||
# and then enable it below by uncommenting the cloudflare-only.conf line
|
|
||||||
#
|
|
||||||
# ======================================================================
|
|
||||||
|
|
||||||
|
upstream cpp {
|
||||||
upstream cpp_tcp {
|
server 127.0.0.1:3923;
|
||||||
# alternative 1: connect to copyparty using tcp;
|
|
||||||
# cpp_uds is slightly faster and more secure, but
|
|
||||||
# cpp_tcp is easier to setup and "just works"
|
|
||||||
# ...you should however restrict copyparty to only
|
|
||||||
# accept connections from nginx by adding these args:
|
|
||||||
# -i 127.0.0.1
|
|
||||||
|
|
||||||
server 127.0.0.1:3923 fail_timeout=1s;
|
|
||||||
keepalive 1;
|
keepalive 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
upstream cpp_uds {
|
|
||||||
# alternative 2: unix-socket, aka. "unix domain socket";
|
|
||||||
# 5-10% faster, and better isolation from other software,
|
|
||||||
# but there must be at least one unix-group which both
|
|
||||||
# nginx and copyparty is a member of; if that group is
|
|
||||||
# "www" then run copyparty with the following args:
|
|
||||||
# -i unix:770:www:/dev/shm/party.sock
|
|
||||||
|
|
||||||
server unix:/dev/shm/party.sock fail_timeout=1s;
|
|
||||||
keepalive 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
listen [::]:443 ssl;
|
listen [::]:443 ssl;
|
||||||
|
|
||||||
server_name fs.example.com;
|
server_name fs.example.com;
|
||||||
|
|
||||||
# uncomment the following line to reject non-cloudflare connections, ensuring client IPs cannot be spoofed:
|
|
||||||
#include /etc/nginx/cloudflare-only.conf;
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
# recommendation: replace cpp_tcp with cpp_uds below
|
proxy_pass http://cpp;
|
||||||
proxy_pass http://cpp_tcp;
|
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
# disable buffering (next 4 lines)
|
# disable buffering (next 4 lines)
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
client_max_body_size 0;
|
client_max_body_size 0;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_request_buffering off;
|
proxy_request_buffering off;
|
||||||
# improve download speed from 600 to 1500 MiB/s
|
|
||||||
proxy_buffers 32 8k;
|
|
||||||
proxy_buffer_size 16k;
|
|
||||||
proxy_busy_buffers_size 24k;
|
|
||||||
|
|
||||||
proxy_set_header Connection "Keep-Alive";
|
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
# NOTE: with cloudflare you want this X-Forwarded-For instead:
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
#proxy_set_header X-Forwarded-For $http_cf_connecting_ip;
|
proxy_set_header Connection "Keep-Alive";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# default client_max_body_size (1M) blocks uploads larger than 256 MiB
|
|
||||||
client_max_body_size 1024M;
|
|
||||||
client_header_timeout 610m;
|
|
||||||
client_body_timeout 610m;
|
|
||||||
send_timeout 610m;
|
|
||||||
|
|
|
@ -1,375 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
with lib;
|
|
||||||
let
|
|
||||||
mkKeyValue =
|
|
||||||
key: value:
|
|
||||||
if value == true then
|
|
||||||
# sets with a true boolean value are coerced to just the key name
|
|
||||||
key
|
|
||||||
else if value == false then
|
|
||||||
# or omitted completely when false
|
|
||||||
""
|
|
||||||
else
|
|
||||||
(generators.mkKeyValueDefault { inherit mkValueString; } ": " key value);
|
|
||||||
|
|
||||||
mkAttrsString = value: (generators.toKeyValue { inherit mkKeyValue; } value);
|
|
||||||
|
|
||||||
mkValueString =
|
|
||||||
value:
|
|
||||||
if isList value then
|
|
||||||
(concatStringsSep "," (map mkValueString value))
|
|
||||||
else if isAttrs value then
|
|
||||||
"\n" + (mkAttrsString value)
|
|
||||||
else
|
|
||||||
(generators.mkValueStringDefault { } value);
|
|
||||||
|
|
||||||
mkSectionName = value: "[" + (escape [ "[" "]" ] value) + "]";
|
|
||||||
|
|
||||||
mkSection = name: attrs: ''
|
|
||||||
${mkSectionName name}
|
|
||||||
${mkAttrsString attrs}
|
|
||||||
'';
|
|
||||||
|
|
||||||
mkVolume = name: attrs: ''
|
|
||||||
${mkSectionName name}
|
|
||||||
${attrs.path}
|
|
||||||
${mkAttrsString {
|
|
||||||
accs = attrs.access;
|
|
||||||
flags = attrs.flags;
|
|
||||||
}}
|
|
||||||
'';
|
|
||||||
|
|
||||||
passwordPlaceholder = name: "{{password-${name}}}";
|
|
||||||
|
|
||||||
accountsWithPlaceholders = mapAttrs (name: attrs: passwordPlaceholder name);
|
|
||||||
|
|
||||||
configStr = ''
|
|
||||||
${mkSection "global" cfg.settings}
|
|
||||||
${mkSection "accounts" (accountsWithPlaceholders cfg.accounts)}
|
|
||||||
${concatStringsSep "\n" (mapAttrsToList mkVolume cfg.volumes)}
|
|
||||||
'';
|
|
||||||
|
|
||||||
cfg = config.services.copyparty;
|
|
||||||
configFile = pkgs.writeText "copyparty.conf" configStr;
|
|
||||||
runtimeConfigPath = "/run/copyparty/copyparty.conf";
|
|
||||||
externalCacheDir = "/var/cache/copyparty";
|
|
||||||
externalStateDir = "/var/lib/copyparty";
|
|
||||||
defaultShareDir = "${externalStateDir}/data";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.services.copyparty = {
|
|
||||||
enable = mkEnableOption "web-based file manager";
|
|
||||||
|
|
||||||
package = mkOption {
|
|
||||||
type = types.package;
|
|
||||||
default = pkgs.copyparty;
|
|
||||||
defaultText = "pkgs.copyparty";
|
|
||||||
description = ''
|
|
||||||
Package of the application to run, exposed for overriding purposes.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
mkHashWrapper = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
description = ''
|
|
||||||
Make a shell script wrapper called 'copyparty-hash' with all options set here,
|
|
||||||
that launches the hashing cli.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
user = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "copyparty";
|
|
||||||
description = ''
|
|
||||||
The user that copyparty will run under.
|
|
||||||
|
|
||||||
If changed from default, you are responsible for making sure the user exists.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
group = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "copyparty";
|
|
||||||
description = ''
|
|
||||||
The group that copyparty will run under.
|
|
||||||
|
|
||||||
If changed from default, you are responsible for making sure the user exists.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
openFilesLimit = mkOption {
|
|
||||||
default = 4096;
|
|
||||||
type = types.either types.int types.str;
|
|
||||||
description = "Number of files to allow copyparty to open.";
|
|
||||||
};
|
|
||||||
|
|
||||||
settings = mkOption {
|
|
||||||
type = types.attrs;
|
|
||||||
description = ''
|
|
||||||
Global settings to apply.
|
|
||||||
Directly maps to values in the [global] section of the copyparty config.
|
|
||||||
Cannot set "c" or "hist", those are set by this module.
|
|
||||||
See `${getExe cfg.package} --help` for more details.
|
|
||||||
'';
|
|
||||||
default = {
|
|
||||||
i = "127.0.0.1";
|
|
||||||
no-reload = true;
|
|
||||||
hist = externalCacheDir;
|
|
||||||
};
|
|
||||||
example = literalExpression ''
|
|
||||||
{
|
|
||||||
i = "0.0.0.0";
|
|
||||||
no-reload = true;
|
|
||||||
hist = ${externalCacheDir};
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
accounts = mkOption {
|
|
||||||
type = types.attrsOf (
|
|
||||||
types.submodule (
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
passwordFile = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = ''
|
|
||||||
Runtime file path to a file containing the user password.
|
|
||||||
Must be readable by the copyparty user.
|
|
||||||
'';
|
|
||||||
example = "/run/keys/copyparty/ed";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
description = ''
|
|
||||||
A set of copyparty accounts to create.
|
|
||||||
'';
|
|
||||||
default = { };
|
|
||||||
example = literalExpression ''
|
|
||||||
{
|
|
||||||
ed.passwordFile = "/run/keys/copyparty/ed";
|
|
||||||
};
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
volumes = mkOption {
|
|
||||||
type = types.attrsOf (
|
|
||||||
types.submodule (
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
path = mkOption {
|
|
||||||
type = types.path;
|
|
||||||
description = ''
|
|
||||||
Path of a directory to share.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
access = mkOption {
|
|
||||||
type = types.attrs;
|
|
||||||
description = ''
|
|
||||||
Attribute list of permissions and the users to apply them to.
|
|
||||||
|
|
||||||
The key must be a string containing any combination of allowed permission:
|
|
||||||
"r" (read): list folder contents, download files
|
|
||||||
"w" (write): upload files; need "r" to see the uploads
|
|
||||||
"m" (move): move files and folders; need "w" at destination
|
|
||||||
"d" (delete): permanently delete files and folders
|
|
||||||
"g" (get): download files, but cannot see folder contents
|
|
||||||
"G" (upget): "get", but can see filekeys of their own uploads
|
|
||||||
"h" (html): "get", but folders return their index.html
|
|
||||||
"a" (admin): can see uploader IPs, config-reload
|
|
||||||
|
|
||||||
For example: "rwmd"
|
|
||||||
|
|
||||||
The value must be one of:
|
|
||||||
an account name, defined in `accounts`
|
|
||||||
a list of account names
|
|
||||||
"*", which means "any account"
|
|
||||||
'';
|
|
||||||
example = literalExpression ''
|
|
||||||
{
|
|
||||||
# wG = write-upget = see your own uploads only
|
|
||||||
wG = "*";
|
|
||||||
# read-write-modify-delete for users "ed" and "k"
|
|
||||||
rwmd = ["ed" "k"];
|
|
||||||
};
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
flags = mkOption {
|
|
||||||
type = types.attrs;
|
|
||||||
description = ''
|
|
||||||
Attribute list of volume flags to apply.
|
|
||||||
See `${getExe cfg.package} --help-flags` for more details.
|
|
||||||
'';
|
|
||||||
example = literalExpression ''
|
|
||||||
{
|
|
||||||
# "fk" enables filekeys (necessary for upget permission) (4 chars long)
|
|
||||||
fk = 4;
|
|
||||||
# scan for new files every 60sec
|
|
||||||
scan = 60;
|
|
||||||
# volflag "e2d" enables the uploads database
|
|
||||||
e2d = true;
|
|
||||||
# "d2t" disables multimedia parsers (in case the uploads are malicious)
|
|
||||||
d2t = true;
|
|
||||||
# skips hashing file contents if path matches *.iso
|
|
||||||
nohash = "\.iso$";
|
|
||||||
};
|
|
||||||
'';
|
|
||||||
default = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
description = "A set of copyparty volumes to create";
|
|
||||||
default = {
|
|
||||||
"/" = {
|
|
||||||
path = defaultShareDir;
|
|
||||||
access = {
|
|
||||||
r = "*";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
example = literalExpression ''
|
|
||||||
{
|
|
||||||
"/" = {
|
|
||||||
path = ${defaultShareDir};
|
|
||||||
access = {
|
|
||||||
# wG = write-upget = see your own uploads only
|
|
||||||
wG = "*";
|
|
||||||
# read-write-modify-delete for users "ed" and "k"
|
|
||||||
rwmd = ["ed" "k"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = mkIf cfg.enable (
|
|
||||||
let
|
|
||||||
command = "${getExe cfg.package} -c ${runtimeConfigPath}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
systemd.services.copyparty = {
|
|
||||||
description = "http file sharing hub";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
|
|
||||||
environment = {
|
|
||||||
PYTHONUNBUFFERED = "true";
|
|
||||||
XDG_CONFIG_HOME = externalStateDir;
|
|
||||||
};
|
|
||||||
|
|
||||||
preStart =
|
|
||||||
let
|
|
||||||
replaceSecretCommand =
|
|
||||||
name: attrs:
|
|
||||||
"${getExe pkgs.replace-secret} '${passwordPlaceholder name}' '${attrs.passwordFile}' ${runtimeConfigPath}";
|
|
||||||
in
|
|
||||||
''
|
|
||||||
set -euo pipefail
|
|
||||||
install -m 600 ${configFile} ${runtimeConfigPath}
|
|
||||||
${concatStringsSep "\n" (mapAttrsToList replaceSecretCommand cfg.accounts)}
|
|
||||||
'';
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "simple";
|
|
||||||
ExecStart = command;
|
|
||||||
# Hardening options
|
|
||||||
User = cfg.user;
|
|
||||||
Group = cfg.group;
|
|
||||||
RuntimeDirectory = [ "copyparty" ];
|
|
||||||
RuntimeDirectoryMode = "0700";
|
|
||||||
StateDirectory = [ "copyparty" ];
|
|
||||||
StateDirectoryMode = "0700";
|
|
||||||
CacheDirectory = lib.mkIf (cfg.settings ? hist) [ "copyparty" ];
|
|
||||||
CacheDirectoryMode = lib.mkIf (cfg.settings ? hist) "0700";
|
|
||||||
WorkingDirectory = externalStateDir;
|
|
||||||
BindReadOnlyPaths = [
|
|
||||||
"/nix/store"
|
|
||||||
"-/etc/resolv.conf"
|
|
||||||
"-/etc/nsswitch.conf"
|
|
||||||
"-/etc/group"
|
|
||||||
"-/etc/hosts"
|
|
||||||
"-/etc/localtime"
|
|
||||||
] ++ (mapAttrsToList (k: v: "-${v.passwordFile}") cfg.accounts);
|
|
||||||
BindPaths =
|
|
||||||
(if cfg.settings ? hist then [ cfg.settings.hist ] else [ ])
|
|
||||||
++ [ externalStateDir ]
|
|
||||||
++ (mapAttrsToList (k: v: v.path) cfg.volumes);
|
|
||||||
# ProtectSystem = "strict";
|
|
||||||
# Note that unlike what 'ro' implies,
|
|
||||||
# this actually makes it impossible to read anything in the root FS,
|
|
||||||
# except for things explicitly mounted via `RuntimeDirectory`, `StateDirectory`, `CacheDirectory`, and `BindReadOnlyPaths`.
|
|
||||||
# This is because TemporaryFileSystem creates a *new* *empty* filesystem for the process, so only bindmounts are visible.
|
|
||||||
TemporaryFileSystem = "/:ro";
|
|
||||||
PrivateTmp = true;
|
|
||||||
PrivateDevices = true;
|
|
||||||
ProtectKernelTunables = true;
|
|
||||||
ProtectControlGroups = true;
|
|
||||||
RestrictSUIDSGID = true;
|
|
||||||
PrivateMounts = true;
|
|
||||||
ProtectKernelModules = true;
|
|
||||||
ProtectKernelLogs = true;
|
|
||||||
ProtectHostname = true;
|
|
||||||
ProtectClock = true;
|
|
||||||
ProtectProc = "invisible";
|
|
||||||
ProcSubset = "pid";
|
|
||||||
RestrictNamespaces = true;
|
|
||||||
RemoveIPC = true;
|
|
||||||
UMask = "0077";
|
|
||||||
LimitNOFILE = cfg.openFilesLimit;
|
|
||||||
NoNewPrivileges = true;
|
|
||||||
LockPersonality = true;
|
|
||||||
RestrictRealtime = true;
|
|
||||||
MemoryDenyWriteExecute = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# ensure volumes exist:
|
|
||||||
systemd.tmpfiles.settings."copyparty" = (
|
|
||||||
lib.attrsets.mapAttrs' (
|
|
||||||
name: value:
|
|
||||||
lib.attrsets.nameValuePair (value.path) {
|
|
||||||
d = {
|
|
||||||
#: in front of things means it wont change it if the directory already exists.
|
|
||||||
group = ":${cfg.group}";
|
|
||||||
user = ":${cfg.user}";
|
|
||||||
mode = ":755";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
) cfg.volumes
|
|
||||||
);
|
|
||||||
|
|
||||||
users.groups.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") { };
|
|
||||||
users.users.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") {
|
|
||||||
description = "Service user for copyparty";
|
|
||||||
group = "copyparty";
|
|
||||||
home = externalStateDir;
|
|
||||||
isSystemUser = true;
|
|
||||||
};
|
|
||||||
environment.systemPackages = lib.mkIf cfg.mkHashWrapper [
|
|
||||||
(pkgs.writeShellScriptBin "copyparty-hash" ''
|
|
||||||
set -a # automatically export variables
|
|
||||||
# set same environment variables as the systemd service
|
|
||||||
${lib.pipe config.systemd.services.copyparty.environment [
|
|
||||||
(lib.filterAttrs (n: v: v != null && n != "PATH"))
|
|
||||||
(lib.mapAttrs (_: v: "${v}"))
|
|
||||||
(lib.toShellVars)
|
|
||||||
]}
|
|
||||||
PATH=${config.systemd.services.copyparty.environment.PATH}:$PATH
|
|
||||||
|
|
||||||
exec ${command} --ah-cli
|
|
||||||
'')
|
|
||||||
];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
# Maintainer: icxes <dev.null@need.moe>
|
|
||||||
# Contributor: Morgan Adamiec <morganamilo@archlinux.org>
|
|
||||||
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
|
|
||||||
|
|
||||||
pkgname=copyparty
|
|
||||||
pkgver="1.19.5"
|
|
||||||
pkgrel=1
|
|
||||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
|
||||||
arch=("any")
|
|
||||||
url="https://github.com/9001/${pkgname}"
|
|
||||||
license=('MIT')
|
|
||||||
depends=("bash" "python" "lsof" "python-jinja")
|
|
||||||
makedepends=("python-wheel" "python-setuptools" "python-build" "python-installer" "make" "pigz")
|
|
||||||
optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tags"
|
|
||||||
"cfssl: generate TLS certificates on startup"
|
|
||||||
"python-mutagen: music tags (alternative)"
|
|
||||||
"python-pillow: thumbnails for images"
|
|
||||||
"python-pyvips: thumbnails for images (higher quality, faster, uses more ram)"
|
|
||||||
"libkeyfinder: detection of musical keys"
|
|
||||||
"python-pyopenssl: ftps functionality"
|
|
||||||
"python-pyzmq: send zeromq messages from event-hooks"
|
|
||||||
"python-argon2-cffi: hashed passwords in config"
|
|
||||||
)
|
|
||||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
|
||||||
backup=("etc/${pkgname}/copyparty.conf" )
|
|
||||||
sha256sums=("366e0e73d0e322e1dba9315baa328a6cd488f272cb431b9187da326361c2d7da")
|
|
||||||
|
|
||||||
build() {
|
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
|
||||||
make
|
|
||||||
|
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
|
||||||
python -m build --wheel --no-isolation
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
|
||||||
python -m installer --destdir="$pkgdir" dist/*.whl
|
|
||||||
|
|
||||||
install -dm755 "${pkgdir}/etc/${pkgname}"
|
|
||||||
install -Dm755 "bin/prisonparty.sh" "${pkgdir}/usr/bin/prisonparty"
|
|
||||||
install -Dm644 "contrib/systemd/${pkgname}.conf" "${pkgdir}/etc/${pkgname}/copyparty.conf"
|
|
||||||
install -Dm644 "contrib/systemd/${pkgname}@.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}@.service"
|
|
||||||
install -Dm644 "contrib/systemd/${pkgname}-user.service" "${pkgdir}/usr/lib/systemd/user/${pkgname}.service"
|
|
||||||
install -Dm644 "contrib/systemd/prisonparty@.service" "${pkgdir}/usr/lib/systemd/system/prisonparty@.service"
|
|
||||||
install -Dm644 "contrib/systemd/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md"
|
|
||||||
install -Dm644 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
# Contributor: Beethoven <beethovenisadog@protonmail.com>
|
|
||||||
|
|
||||||
|
|
||||||
pkgname=copyparty
|
|
||||||
pkgver=1.19.5
|
|
||||||
pkgrel=1
|
|
||||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
|
||||||
arch=("any")
|
|
||||||
url="https://github.com/9001/${pkgname}"
|
|
||||||
license=('MIT')
|
|
||||||
depends=("bash" "python3" "lsof" "python3-jinja2")
|
|
||||||
makedepends=("python3-wheel" "python3-setuptools" "python3-build" "python3-installer" "make" "pigz")
|
|
||||||
optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tags"
|
|
||||||
"golang-cfssl: generate TLS certificates on startup"
|
|
||||||
"python3-mutagen: music tags (alternative)"
|
|
||||||
"python3-pil: thumbnails for images"
|
|
||||||
"python3-openssl: ftps functionality"
|
|
||||||
"python3-zmq: send zeromq messages from event-hooks"
|
|
||||||
"python3-argon2: hashed passwords in config"
|
|
||||||
)
|
|
||||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
|
||||||
backup=("/etc/${pkgname}.d/init" )
|
|
||||||
sha256sums=("366e0e73d0e322e1dba9315baa328a6cd488f272cb431b9187da326361c2d7da")
|
|
||||||
|
|
||||||
build() {
|
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
|
||||||
make
|
|
||||||
|
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
|
||||||
python -m build --wheel --no-isolation
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
|
||||||
python -m installer --destdir="$pkgdir" dist/*.whl
|
|
||||||
|
|
||||||
install -dm755 "${pkgdir}/etc/${pkgname}.d"
|
|
||||||
install -Dm755 "bin/prisonparty.sh" "${pkgdir}/usr/bin/prisonparty"
|
|
||||||
install -Dm644 "contrib/package/makedeb-mpr/${pkgname}.conf" "${pkgdir}/etc/${pkgname}.d/init"
|
|
||||||
install -Dm644 "contrib/package/makedeb-mpr/${pkgname}.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}.service"
|
|
||||||
install -Dm644 "contrib/package/makedeb-mpr/prisonparty.service" "${pkgdir}/usr/lib/systemd/system/prisonparty.service"
|
|
||||||
install -Dm644 "contrib/package/makedeb-mpr/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md"
|
|
||||||
install -Dm644 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
## import all *.conf files from the current folder (/etc/copyparty.d)
|
|
||||||
% ./
|
|
||||||
|
|
||||||
# add additional .conf files to this folder;
|
|
||||||
# see example config files for reference:
|
|
||||||
# https://github.com/9001/copyparty/blob/hovudstraum/docs/example.conf
|
|
||||||
# https://github.com/9001/copyparty/tree/hovudstraum/docs/copyparty.d
|
|
|
@ -1,32 +0,0 @@
|
||||||
# this will start `/usr/bin/copyparty-sfx.py`
|
|
||||||
# and read config from `/etc/copyparty.d/*.conf`
|
|
||||||
#
|
|
||||||
# you probably want to:
|
|
||||||
# change "User=cpp" and "/home/cpp/" to another user
|
|
||||||
#
|
|
||||||
# unless you add -q to disable logging, you may want to remove the
|
|
||||||
# following line to allow buffering (slightly better performance):
|
|
||||||
# Environment=PYTHONUNBUFFERED=x
|
|
||||||
|
|
||||||
[Unit]
|
|
||||||
Description=copyparty file server
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=notify
|
|
||||||
SyslogIdentifier=copyparty
|
|
||||||
Environment=PYTHONUNBUFFERED=x
|
|
||||||
WorkingDirectory=/var/lib/copyparty-jail
|
|
||||||
ExecReload=/bin/kill -s USR1 $MAINPID
|
|
||||||
|
|
||||||
# user to run as + where the TLS certificate is (if any)
|
|
||||||
User=cpp
|
|
||||||
Environment=XDG_CONFIG_HOME=/home/cpp/.config
|
|
||||||
|
|
||||||
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
|
|
||||||
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
|
|
||||||
|
|
||||||
# run copyparty
|
|
||||||
ExecStart=/usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
|
@ -1,3 +0,0 @@
|
||||||
this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured
|
|
||||||
|
|
||||||
please add some `*.conf` files to `/etc/copyparty.d/`
|
|
|
@ -1,33 +0,0 @@
|
||||||
# this will start `/usr/bin/copyparty-sfx.py`
|
|
||||||
# in a chroot, preventing accidental access elsewhere,
|
|
||||||
# and read copyparty config from `/etc/copyparty.d/*.conf`
|
|
||||||
#
|
|
||||||
# expose additional filesystem locations to copyparty
|
|
||||||
# by listing them between the last `cpp` and `--`
|
|
||||||
#
|
|
||||||
# `cpp cpp` = user/group to run copyparty as; can be IDs (1000 1000)
|
|
||||||
#
|
|
||||||
# unless you add -q to disable logging, you may want to remove the
|
|
||||||
# following line to allow buffering (slightly better performance):
|
|
||||||
# Environment=PYTHONUNBUFFERED=x
|
|
||||||
|
|
||||||
[Unit]
|
|
||||||
Description=copyparty file server
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
SyslogIdentifier=prisonparty
|
|
||||||
Environment=PYTHONUNBUFFERED=x
|
|
||||||
WorkingDirectory=/var/lib/copyparty-jail
|
|
||||||
ExecReload=/bin/kill -s USR1 $MAINPID
|
|
||||||
|
|
||||||
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
|
|
||||||
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
|
|
||||||
|
|
||||||
# run copyparty
|
|
||||||
ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail cpp cpp \
|
|
||||||
/etc/copyparty.d \
|
|
||||||
-- \
|
|
||||||
/usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
|
@ -1,118 +0,0 @@
|
||||||
{
|
|
||||||
lib,
|
|
||||||
buildPythonApplication,
|
|
||||||
fetchurl,
|
|
||||||
util-linux,
|
|
||||||
python,
|
|
||||||
setuptools,
|
|
||||||
jinja2,
|
|
||||||
impacket,
|
|
||||||
pyopenssl,
|
|
||||||
cfssl,
|
|
||||||
argon2-cffi,
|
|
||||||
pillow,
|
|
||||||
pyvips,
|
|
||||||
pyzmq,
|
|
||||||
ffmpeg,
|
|
||||||
mutagen,
|
|
||||||
pyftpdlib,
|
|
||||||
magic,
|
|
||||||
partftpy,
|
|
||||||
fusepy, # for partyfuse
|
|
||||||
|
|
||||||
# use argon2id-hashed passwords in config files (sha2 is always available)
|
|
||||||
withHashedPasswords ? true,
|
|
||||||
|
|
||||||
# generate TLS certificates on startup (pointless when reverse-proxied)
|
|
||||||
withCertgen ? false,
|
|
||||||
|
|
||||||
# create thumbnails with Pillow; faster than FFmpeg / MediaProcessing
|
|
||||||
withThumbnails ? true,
|
|
||||||
|
|
||||||
# create thumbnails with PyVIPS; even faster, uses more memory
|
|
||||||
# -- can be combined with Pillow to support more filetypes
|
|
||||||
withFastThumbnails ? false,
|
|
||||||
|
|
||||||
# enable FFmpeg; thumbnails for most filetypes (also video and audio), extract audio metadata, transcode audio to opus
|
|
||||||
# -- possibly dangerous if you allow anonymous uploads, since FFmpeg has a huge attack surface
|
|
||||||
# -- can be combined with Thumbnails and/or FastThumbnails, since FFmpeg is slower than both
|
|
||||||
withMediaProcessing ? true,
|
|
||||||
|
|
||||||
# if MediaProcessing is not enabled, you probably want this instead (less accurate, but much safer and faster)
|
|
||||||
withBasicAudioMetadata ? false,
|
|
||||||
|
|
||||||
# send ZeroMQ messages from event-hooks
|
|
||||||
withZeroMQ ? true,
|
|
||||||
|
|
||||||
# enable FTP server
|
|
||||||
withFTP ? true,
|
|
||||||
|
|
||||||
# enable FTPS support in the FTP server
|
|
||||||
withFTPS ? false,
|
|
||||||
|
|
||||||
# enable TFTP server
|
|
||||||
withTFTP ? false,
|
|
||||||
|
|
||||||
# samba/cifs server; dangerous and buggy, enable if you really need it
|
|
||||||
withSMB ? false,
|
|
||||||
|
|
||||||
# enables filetype detection for nameless uploads
|
|
||||||
withMagic ? false,
|
|
||||||
|
|
||||||
# extra packages to add to the PATH
|
|
||||||
extraPackages ? [ ],
|
|
||||||
|
|
||||||
# function that accepts a python packageset and returns a list of packages to
|
|
||||||
# be added to the python venv. useful for scripts and such that require
|
|
||||||
# additional dependencies
|
|
||||||
extraPythonPackages ? (_p: [ ]),
|
|
||||||
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
pinData = lib.importJSON ./pin.json;
|
|
||||||
runtimeDeps = ([ util-linux ] ++ extraPackages ++ lib.optional withMediaProcessing ffmpeg);
|
|
||||||
in
|
|
||||||
buildPythonApplication {
|
|
||||||
pname = "copyparty";
|
|
||||||
inherit (pinData) version;
|
|
||||||
src = fetchurl {
|
|
||||||
inherit (pinData) url hash;
|
|
||||||
};
|
|
||||||
dependencies =
|
|
||||||
[
|
|
||||||
jinja2
|
|
||||||
fusepy
|
|
||||||
]
|
|
||||||
++ lib.optional withSMB impacket
|
|
||||||
++ lib.optional withFTP pyftpdlib
|
|
||||||
++ lib.optional withFTPS pyopenssl
|
|
||||||
++ lib.optional withTFTP partftpy
|
|
||||||
++ lib.optional withCertgen cfssl
|
|
||||||
++ lib.optional withThumbnails pillow
|
|
||||||
++ lib.optional withFastThumbnails pyvips
|
|
||||||
++ lib.optional withMediaProcessing ffmpeg
|
|
||||||
++ lib.optional withBasicAudioMetadata mutagen
|
|
||||||
++ lib.optional withHashedPasswords argon2-cffi
|
|
||||||
++ lib.optional withZeroMQ pyzmq
|
|
||||||
++ lib.optional withMagic magic
|
|
||||||
++ (extraPythonPackages python.pkgs);
|
|
||||||
makeWrapperArgs = [ "--prefix PATH : ${lib.makeBinPath runtimeDeps}" ];
|
|
||||||
|
|
||||||
pyproject = true;
|
|
||||||
build-system = [
|
|
||||||
setuptools
|
|
||||||
];
|
|
||||||
meta = {
|
|
||||||
description = "Turn almost any device into a file server";
|
|
||||||
longDescription = ''
|
|
||||||
Portable file server with accelerated resumable uploads, dedup, WebDAV,
|
|
||||||
FTP, TFTP, zeroconf, media indexer, thumbnails++ all in one file, no deps
|
|
||||||
'';
|
|
||||||
homepage = "https://github.com/9001/copyparty";
|
|
||||||
changelog = "https://github.com/9001/copyparty/releases/tag/v${pinData.version}";
|
|
||||||
license = lib.licenses.mit;
|
|
||||||
mainProgram = "copyparty";
|
|
||||||
sourceProvenance = [ lib.sourceTypes.fromSource ];
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"url": "https://github.com/9001/copyparty/releases/download/v1.19.5/copyparty-1.19.5.tar.gz",
|
|
||||||
"version": "1.19.5",
|
|
||||||
"hash": "sha256-Nm4Oc9DjIuHbqTFbqjKKbNSI8nLLQxuRh9oyY2HC19o="
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Update the Nix package pin
|
|
||||||
#
|
|
||||||
# Usage: ./update.sh [PATH]
|
|
||||||
# When the [PATH] is not set, it will fetch the latest release from the repo.
|
|
||||||
# With [PATH] set, it will hash the given file and generate the URL,
|
|
||||||
# base on the version contained within the file
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import json
|
|
||||||
import hashlib
|
|
||||||
import sys
|
|
||||||
import tarfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
OUTPUT_FILE = Path("pin.json")
|
|
||||||
TARGET_ASSET = lambda version: f"copyparty-{version}.tar.gz"
|
|
||||||
HASH_TYPE = "sha256"
|
|
||||||
LATEST_RELEASE_URL = "https://api.github.com/repos/9001/copyparty/releases/latest"
|
|
||||||
DOWNLOAD_URL = lambda version: f"https://github.com/9001/copyparty/releases/download/v{version}/{TARGET_ASSET(version)}"
|
|
||||||
|
|
||||||
|
|
||||||
def get_formatted_hash(binary):
|
|
||||||
hasher = hashlib.new("sha256")
|
|
||||||
hasher.update(binary)
|
|
||||||
asset_hash = hasher.digest()
|
|
||||||
encoded_hash = base64.b64encode(asset_hash).decode("ascii")
|
|
||||||
return f"{HASH_TYPE}-{encoded_hash}"
|
|
||||||
|
|
||||||
|
|
||||||
def version_from_tar_gz(path):
|
|
||||||
with tarfile.open(path) as tarball:
|
|
||||||
release_name = tarball.getmembers()[0].name
|
|
||||||
prefix = "copyparty-"
|
|
||||||
|
|
||||||
if release_name.startswith(prefix):
|
|
||||||
return release_name.replace(prefix, "")
|
|
||||||
raise ValueError("version not found in provided file")
|
|
||||||
|
|
||||||
|
|
||||||
def remote_release_pin():
|
|
||||||
import requests
|
|
||||||
|
|
||||||
response = requests.get(LATEST_RELEASE_URL).json()
|
|
||||||
version = response["tag_name"].lstrip("v")
|
|
||||||
asset_info = [a for a in response["assets"] if a["name"] == TARGET_ASSET(version)][0]
|
|
||||||
download_url = asset_info["browser_download_url"]
|
|
||||||
asset = requests.get(download_url)
|
|
||||||
formatted_hash = get_formatted_hash(asset.content)
|
|
||||||
|
|
||||||
result = {"url": download_url, "version": version, "hash": formatted_hash}
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def local_release_pin(path):
|
|
||||||
version = version_from_tar_gz(path)
|
|
||||||
download_url = DOWNLOAD_URL(version)
|
|
||||||
formatted_hash = get_formatted_hash(path.read_bytes())
|
|
||||||
|
|
||||||
result = {"url": download_url, "version": version, "hash": formatted_hash}
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
asset_path = Path(sys.argv[1])
|
|
||||||
result = local_release_pin(asset_path)
|
|
||||||
else:
|
|
||||||
result = remote_release_pin()
|
|
||||||
|
|
||||||
print(result)
|
|
||||||
json_result = json.dumps(result, indent=4)
|
|
||||||
OUTPUT_FILE.write_text(json_result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,11 +0,0 @@
|
||||||
final: prev: {
|
|
||||||
copyparty = final.python3.pkgs.callPackage ./copyparty {
|
|
||||||
ffmpeg = final.ffmpeg-full;
|
|
||||||
};
|
|
||||||
|
|
||||||
python3 = prev.python3.override {
|
|
||||||
packageOverrides = pyFinal: pyPrev: {
|
|
||||||
partftpy = pyFinal.callPackage ./partftpy { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
lib,
|
|
||||||
buildPythonPackage,
|
|
||||||
fetchurl,
|
|
||||||
setuptools,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
pinData = lib.importJSON ./pin.json;
|
|
||||||
in
|
|
||||||
|
|
||||||
buildPythonPackage rec {
|
|
||||||
pname = "partftpy";
|
|
||||||
inherit (pinData) version;
|
|
||||||
pyproject = true;
|
|
||||||
|
|
||||||
src = fetchurl {
|
|
||||||
inherit (pinData) url hash;
|
|
||||||
};
|
|
||||||
|
|
||||||
build-system = [ setuptools ];
|
|
||||||
|
|
||||||
pythonImportsCheck = [ "partftpy.TftpServer" ];
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
description = "Pure Python TFTP library (copyparty edition)";
|
|
||||||
homepage = "https://github.com/9001/partftpy";
|
|
||||||
changelog = "https://github.com/9001/partftpy/releases/tag/${version}";
|
|
||||||
license = lib.licenses.mit;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"url": "https://github.com/9001/partftpy/releases/download/v0.4.0/partftpy-0.4.0.tar.gz",
|
|
||||||
"version": "0.4.0",
|
|
||||||
"hash": "sha256-5Q2zyuJ892PGZmb+YXg0ZPW/DK8RDL1uE0j5HPd4We0="
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Update the Nix package pin
|
|
||||||
#
|
|
||||||
# Usage: ./update.sh
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import json
|
|
||||||
import hashlib
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
OUTPUT_FILE = Path("pin.json")
|
|
||||||
TARGET_ASSET = lambda version: f"partftpy-{version}.tar.gz"
|
|
||||||
HASH_TYPE = "sha256"
|
|
||||||
LATEST_RELEASE_URL = "https://api.github.com/repos/9001/partftpy/releases/latest"
|
|
||||||
|
|
||||||
|
|
||||||
def get_formatted_hash(binary):
|
|
||||||
hasher = hashlib.new("sha256")
|
|
||||||
hasher.update(binary)
|
|
||||||
asset_hash = hasher.digest()
|
|
||||||
encoded_hash = base64.b64encode(asset_hash).decode("ascii")
|
|
||||||
return f"{HASH_TYPE}-{encoded_hash}"
|
|
||||||
|
|
||||||
|
|
||||||
def remote_release_pin():
|
|
||||||
import requests
|
|
||||||
|
|
||||||
response = requests.get(LATEST_RELEASE_URL).json()
|
|
||||||
version = response["tag_name"].lstrip("v")
|
|
||||||
asset_info = [a for a in response["assets"] if a["name"] == TARGET_ASSET(version)][0]
|
|
||||||
download_url = asset_info["browser_download_url"]
|
|
||||||
asset = requests.get(download_url)
|
|
||||||
formatted_hash = get_formatted_hash(asset.content)
|
|
||||||
|
|
||||||
result = {"url": download_url, "version": version, "hash": formatted_hash}
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
result = remote_release_pin()
|
|
||||||
|
|
||||||
print(result)
|
|
||||||
json_result = json.dumps(result, indent=4)
|
|
||||||
OUTPUT_FILE.write_text(json_result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,62 +0,0 @@
|
||||||
Name: copyparty
|
|
||||||
Version: $pkgver
|
|
||||||
Release: $pkgrel
|
|
||||||
License: MIT
|
|
||||||
Group: Utilities
|
|
||||||
URL: https://github.com/9001/copyparty
|
|
||||||
Source0: copyparty-$pkgver.tar.gz
|
|
||||||
Summary: File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++
|
|
||||||
BuildArch: noarch
|
|
||||||
BuildRequires: python3, python3-devel, pyproject-rpm-macros, python-setuptools, python-wheel, make
|
|
||||||
Requires: python3, (python3-jinja2 or python-jinja2), lsof
|
|
||||||
Recommends: ffmpeg, (golang-github-cloudflare-cfssl or cfssl), python-mutagen, python-pillow, python-pyvips
|
|
||||||
Recommends: qm-vamp-plugins, python-argon2-cffi, (python-pyopenssl or pyopenssl), python-impacket
|
|
||||||
|
|
||||||
%description
|
|
||||||
Portable file server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++ all in one file, no deps
|
|
||||||
|
|
||||||
See release at https://github.com/9001/copyparty/releases
|
|
||||||
|
|
||||||
%global debug_package %{nil}
|
|
||||||
|
|
||||||
%generate_buildrequires
|
|
||||||
%pyproject_buildrequires
|
|
||||||
|
|
||||||
%prep
|
|
||||||
%setup -q
|
|
||||||
|
|
||||||
%build
|
|
||||||
cd "copyparty/web"
|
|
||||||
make
|
|
||||||
cd -
|
|
||||||
%pyproject_wheel
|
|
||||||
|
|
||||||
%install
|
|
||||||
mkdir -p %{buildroot}%{_bindir}
|
|
||||||
mkdir -p %{buildroot}%{_libdir}/systemd/{system,user}
|
|
||||||
mkdir -p %{buildroot}/etc/%{name}
|
|
||||||
mkdir -p %{buildroot}/var/lib/%{name}-jail
|
|
||||||
mkdir -p %{buildroot}%{_datadir}/licenses/%{name}
|
|
||||||
|
|
||||||
%pyproject_install
|
|
||||||
%pyproject_save_files copyparty
|
|
||||||
|
|
||||||
install -m 0755 bin/prisonparty.sh %{buildroot}%{_bindir}/prisonpary.sh
|
|
||||||
install -m 0644 contrib/systemd/%{name}.conf %{buildroot}/etc/%{name}/%{name}.conf
|
|
||||||
install -m 0644 contrib/systemd/%{name}@.service %{buildroot}%{_libdir}/systemd/system/%{name}@.service
|
|
||||||
install -m 0644 contrib/systemd/%{name}-user.service %{buildroot}%{_libdir}/systemd/user/%{name}.service
|
|
||||||
install -m 0644 contrib/systemd/prisonparty@.service %{buildroot}%{_libdir}/systemd/system/prisonparty@.service
|
|
||||||
install -m 0644 contrib/systemd/index.md %{buildroot}/var/lib/%{name}-jail/README.md
|
|
||||||
install -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/%{name}/LICENSE
|
|
||||||
|
|
||||||
%files -n copyparty -f %{pyproject_files}
|
|
||||||
%license LICENSE
|
|
||||||
%{_bindir}/copyparty
|
|
||||||
%{_bindir}/partyfuse
|
|
||||||
%{_bindir}/u2c
|
|
||||||
%{_bindir}/prisonpary.sh
|
|
||||||
/etc/%{name}/%{name}.conf
|
|
||||||
%{_libdir}/systemd/system/%{name}@.service
|
|
||||||
%{_libdir}/systemd/user/%{name}.service
|
|
||||||
%{_libdir}/systemd/system/prisonparty@.service
|
|
||||||
/var/lib/%{name}-jail/README.md
|
|
|
@ -15,19 +15,11 @@ save one of these as `.epilogue.html` inside a folder to customize it:
|
||||||
point `--js-browser` to one of these by URL:
|
point `--js-browser` to one of these by URL:
|
||||||
|
|
||||||
* [`minimal-up2k.js`](minimal-up2k.js) is similar to the above `minimal-up2k.html` except it applies globally to all write-only folders
|
* [`minimal-up2k.js`](minimal-up2k.js) is similar to the above `minimal-up2k.html` except it applies globally to all write-only folders
|
||||||
* [`quickmove.js`](quickmove.js) adds a hotkey to move selected files into a subfolder
|
|
||||||
* [`up2k-hooks.js`](up2k-hooks.js) lets you specify a ruleset for files to skip uploading
|
* [`up2k-hooks.js`](up2k-hooks.js) lets you specify a ruleset for files to skip uploading
|
||||||
* [`up2k-hook-ytid.js`](up2k-hook-ytid.js) is a more specific example checking youtube-IDs against some API
|
* [`up2k-hook-ytid.js`](up2k-hook-ytid.js) is a more specific example checking youtube-IDs against some API
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## example any-js
|
|
||||||
point `--js-browser` and/or `--js-other` to one of these by URL:
|
|
||||||
|
|
||||||
* [`banner.js`](banner.js) shows a very enterprise [legal-banner](https://github.com/user-attachments/assets/8ae8e087-b209-449c-b08d-74e040f0284b)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## example browser-css
|
## example browser-css
|
||||||
point `--css-browser` to one of these by URL:
|
point `--css-browser` to one of these by URL:
|
||||||
|
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
(function() {
|
|
||||||
|
|
||||||
// usage: copy this to '.banner.js' in your webroot,
|
|
||||||
// and run copyparty with the following arguments:
|
|
||||||
// --js-browser /.banner.js --js-other /.banner.js
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// had to pick the most chuuni one as the default
|
|
||||||
var bannertext = '' +
|
|
||||||
'<h3>You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only.</h3>' +
|
|
||||||
'<p>By using this IS (which includes any device attached to this IS), you consent to the following conditions:</p>' +
|
|
||||||
'<ul>' +
|
|
||||||
'<li>The USG routinely intercepts and monitors communications on this IS for purposes including, but not limited to, penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM), law enforcement (LE), and counterintelligence (CI) investigations.</li>' +
|
|
||||||
'<li>At any time, the USG may inspect and seize data stored on this IS.</li>' +
|
|
||||||
'<li>Communications using, or data stored on, this IS are not private, are subject to routine monitoring, interception, and search, and may be disclosed or used for any USG-authorized purpose.</li>' +
|
|
||||||
'<li>This IS includes security measures (e.g., authentication and access controls) to protect USG interests -- not for your personal benefit or privacy.</li>' +
|
|
||||||
'<li>Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative searching or monitoring of the content of privileged communications, or work product, related to personal representation or services by attorneys, psychotherapists, or clergy, and their assistants. Such communications and work product are private and confidential. See User Agreement for details.</li>' +
|
|
||||||
'</ul>';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// fancy div to insert into pages
|
|
||||||
function bannerdiv(border) {
|
|
||||||
var ret = mknod('div', null, bannertext);
|
|
||||||
if (border)
|
|
||||||
ret.setAttribute("style", "border:1em solid var(--fg); border-width:.3em 0; margin:3em 0");
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// keep all of these false and then selectively enable them in the if-blocks below
|
|
||||||
var show_msgbox = false,
|
|
||||||
login_top = false,
|
|
||||||
top = false,
|
|
||||||
bottom = false,
|
|
||||||
top_bordered = false,
|
|
||||||
bottom_bordered = false;
|
|
||||||
|
|
||||||
if (QS("h1#cc") && QS("a#k")) {
|
|
||||||
// this is the controlpanel
|
|
||||||
// (you probably want to keep just one of these enabled)
|
|
||||||
show_msgbox = true;
|
|
||||||
login_top = true;
|
|
||||||
bottom = true;
|
|
||||||
}
|
|
||||||
else if (ebi("swin") && ebi("smac")) {
|
|
||||||
// this is the connect-page, same deal here
|
|
||||||
show_msgbox = true;
|
|
||||||
top_bordered = true;
|
|
||||||
bottom_bordered = true;
|
|
||||||
}
|
|
||||||
else if (ebi("op_cfg") || ebi("div#mw") ) {
|
|
||||||
// we're running in the main filebrowser (op_cfg) or markdown-viewer/editor (div#mw),
|
|
||||||
// fragile pages which break if you do something too fancy
|
|
||||||
show_msgbox = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// shows a fullscreen messagebox; works on all pages
|
|
||||||
if (show_msgbox) {
|
|
||||||
var now = Math.floor(Date.now() / 1000),
|
|
||||||
last_shown = sread("bannerts") || 0;
|
|
||||||
|
|
||||||
// 60 * 60 * 17 = 17 hour cooldown
|
|
||||||
if (now - last_shown > 60 * 60 * 17) {
|
|
||||||
swrite("bannerts", now);
|
|
||||||
modal.confirm(bannertext, null, function () {
|
|
||||||
location = 'https://this-page-intentionally-left-blank.org/';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// show a message on the page footer; only works on the connect-page
|
|
||||||
if (top || top_bordered) {
|
|
||||||
var dst = ebi('wrap');
|
|
||||||
dst.insertBefore(bannerdiv(top_bordered), dst.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
// show a message on the page footer; only works on the controlpanel and connect-page
|
|
||||||
if (bottom || bottom_bordered) {
|
|
||||||
ebi('wrap').appendChild(bannerdiv(bottom_bordered));
|
|
||||||
}
|
|
||||||
|
|
||||||
// show a message on the top of the page; only works on the controlpanel
|
|
||||||
if (login_top) {
|
|
||||||
var dst = QS('h1');
|
|
||||||
dst.parentNode.insertBefore(bannerdiv(false), dst);
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,117 +0,0 @@
|
||||||
// USAGE:
|
|
||||||
// place this file somewhere in the webroot and then
|
|
||||||
// python3 -m copyparty --js-browser /.res/graft-thumbs.js
|
|
||||||
//
|
|
||||||
// DESCRIPTION:
|
|
||||||
// this is a gridview plugin which, for each file in a folder,
|
|
||||||
// looks for another file with the same filename (but with a
|
|
||||||
// different file extension)
|
|
||||||
//
|
|
||||||
// if one of those files is an image and the other is not,
|
|
||||||
// then this plugin assumes the image is a "sidecar thumbnail"
|
|
||||||
// for the other file, and it will graft the image thumbnail
|
|
||||||
// onto the non-image file (for example an mp3)
|
|
||||||
//
|
|
||||||
// optional feature 1, default-enabled:
|
|
||||||
// the image-file is then hidden from the directory listing
|
|
||||||
//
|
|
||||||
// optional feature 2, default-enabled:
|
|
||||||
// when clicking the audio file, the image will also open
|
|
||||||
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
// `graft_thumbs` assumes the gridview has just been rendered;
|
|
||||||
// it looks for sidecars, and transplants those thumbnails onto
|
|
||||||
// the other file with the same basename (filename sans extension)
|
|
||||||
|
|
||||||
var graft_thumbs = function () {
|
|
||||||
if (!thegrid.en)
|
|
||||||
return; // not in grid mode
|
|
||||||
|
|
||||||
var files = msel.getall(),
|
|
||||||
pairs = {};
|
|
||||||
|
|
||||||
console.log(files);
|
|
||||||
|
|
||||||
for (var a = 0; a < files.length; a++) {
|
|
||||||
var file = files[a],
|
|
||||||
is_pic = /\.(jpe?g|png|gif|webp)$/i.exec(file.vp),
|
|
||||||
is_audio = re_au_all.exec(file.vp),
|
|
||||||
basename = file.vp.replace(/\.[^\.]+$/, ""),
|
|
||||||
entry = pairs[basename];
|
|
||||||
|
|
||||||
if (!entry)
|
|
||||||
// first time seeing this basename; create a new entry in pairs
|
|
||||||
entry = pairs[basename] = {};
|
|
||||||
|
|
||||||
if (is_pic)
|
|
||||||
entry.thumb = file;
|
|
||||||
else if (is_audio)
|
|
||||||
entry.audio = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
var basenames = Object.keys(pairs);
|
|
||||||
for (var a = 0; a < basenames.length; a++)
|
|
||||||
(function(a) {
|
|
||||||
var pair = pairs[basenames[a]];
|
|
||||||
|
|
||||||
if (!pair.thumb || !pair.audio)
|
|
||||||
return; // not a matching pair of files
|
|
||||||
|
|
||||||
var img_thumb = QS('#ggrid a[ref="' + pair.thumb.id + '"] img[onload]'),
|
|
||||||
img_audio = QS('#ggrid a[ref="' + pair.audio.id + '"] img[onload]');
|
|
||||||
|
|
||||||
if (!img_thumb || !img_audio)
|
|
||||||
return; // something's wrong... let's bail
|
|
||||||
|
|
||||||
// alright, graft the thumb...
|
|
||||||
img_audio.src = img_thumb.src;
|
|
||||||
|
|
||||||
// ...and hide the sidecar
|
|
||||||
img_thumb.closest('a').style.display = 'none';
|
|
||||||
|
|
||||||
// ...and add another onclick-handler to the audio,
|
|
||||||
// so it also opens the pic while playing the song
|
|
||||||
img_audio.addEventListener('click', function() {
|
|
||||||
img_thumb.click();
|
|
||||||
return false; // let it bubble to the next listener
|
|
||||||
});
|
|
||||||
|
|
||||||
})(a);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ...and then the trick! near the end of loadgrid,
|
|
||||||
// thegrid.bagit is called to initialize the baguettebox
|
|
||||||
// (image/video gallery); this is the perfect function to
|
|
||||||
// "hook" (hijack) so we can run our code :^)
|
|
||||||
|
|
||||||
// need to grab a backup of the original function first,
|
|
||||||
var orig_func = thegrid.bagit;
|
|
||||||
|
|
||||||
// and then replace it with our own:
|
|
||||||
thegrid.bagit = function (isrc) {
|
|
||||||
|
|
||||||
if (isrc !== '#ggrid')
|
|
||||||
// we only want to modify the grid, so
|
|
||||||
// let the original function handle this one
|
|
||||||
return orig_func(isrc);
|
|
||||||
|
|
||||||
graft_thumbs();
|
|
||||||
|
|
||||||
// when changing directories, the grid is
|
|
||||||
// rendered before msel returns the correct
|
|
||||||
// filenames, so schedule another run:
|
|
||||||
setTimeout(graft_thumbs, 1);
|
|
||||||
|
|
||||||
// and finally, call the original thegrid.bagit function
|
|
||||||
return orig_func(isrc);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ls0) {
|
|
||||||
// the server included an initial listing json (ls0),
|
|
||||||
// so the grid has already been rendered without our hook
|
|
||||||
graft_thumbs();
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,9 +1,4 @@
|
||||||
<!--
|
<!--
|
||||||
NOTE: DEPRECATED; please use the javascript version instead:
|
|
||||||
https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/minimal-up2k.js
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
save this as .epilogue.html inside a write-only folder to declutter the UI, makes it look like
|
save this as .epilogue.html inside a write-only folder to declutter the UI, makes it look like
|
||||||
https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png
|
https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png
|
||||||
|
|
||||||
|
@ -16,7 +11,7 @@
|
||||||
|
|
||||||
/* make the up2k ui REALLY minimal by hiding a bunch of stuff: */
|
/* make the up2k ui REALLY minimal by hiding a bunch of stuff: */
|
||||||
|
|
||||||
#ops, #tree, #path, #wfp, /* main tabs and navigators (tree/breadcrumbs) */
|
#ops, #tree, #path, #epi+h2, /* main tabs and navigators (tree/breadcrumbs) */
|
||||||
|
|
||||||
#u2conf tr:first-child>td[rowspan]:not(#u2btn_cw), /* most of the config options */
|
#u2conf tr:first-child>td[rowspan]:not(#u2btn_cw), /* most of the config options */
|
||||||
|
|
||||||
|
|
|
@ -12,29 +12,12 @@ almost the same as minimal-up2k.html except this one...:
|
||||||
|
|
||||||
-- looks slightly better
|
-- looks slightly better
|
||||||
|
|
||||||
|
|
||||||
========================
|
|
||||||
== USAGE INSTRUCTIONS ==
|
|
||||||
|
|
||||||
1. create a volume which anyone can read from (if you haven't already)
|
|
||||||
2. copy this file into that volume, so anyone can download it
|
|
||||||
3. enable the plugin by telling the webbrowser to load this file;
|
|
||||||
assuming the URL to the public volume is /res/, and
|
|
||||||
assuming you're using config-files, then add this to your config:
|
|
||||||
|
|
||||||
[global]
|
|
||||||
js-browser: /res/minimal-up2k.js
|
|
||||||
|
|
||||||
alternatively, if you're not using config-files, then
|
|
||||||
add the following commandline argument instead:
|
|
||||||
--js-browser=/res/minimal-up2k.js
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var u2min = `
|
var u2min = `
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
#ops, #path, #tree, #files, #wfp,
|
#ops, #path, #tree, #files, #epi+div+h2,
|
||||||
#u2conf td.c+.c, #u2cards, #srch_dz, #srch_zd {
|
#u2conf td.c+.c, #u2cards, #srch_dz, #srch_zd {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
@ -72,5 +55,5 @@ var u2min = `
|
||||||
if (!has(perms, 'read')) {
|
if (!has(perms, 'read')) {
|
||||||
var e2 = mknod('div');
|
var e2 = mknod('div');
|
||||||
e2.innerHTML = u2min;
|
e2.innerHTML = u2min;
|
||||||
ebi('wrap').insertBefore(e2, QS('#wfp'));
|
ebi('wrap').insertBefore(e2, QS('#epi+h2'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,140 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
|
|
||||||
// USAGE:
|
|
||||||
// place this file somewhere in the webroot,
|
|
||||||
// for example in a folder named ".res" to hide it, and then
|
|
||||||
// python3 copyparty-sfx.py -v .::A --js-browser /.res/quickmove.js
|
|
||||||
//
|
|
||||||
// DESCRIPTION:
|
|
||||||
// the command above launches copyparty with one single volume;
|
|
||||||
// ".::A" = current folder as webroot, and everyone has Admin
|
|
||||||
//
|
|
||||||
// the plugin adds hotkey "W" which moves all selected files
|
|
||||||
// into a subfolder named "foobar" inside the current folder
|
|
||||||
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
var action_to_perform = ask_for_confirmation_and_then_move;
|
|
||||||
// this decides what the new hotkey should do;
|
|
||||||
// ask_for_confirmation_and_then_move = show a yes/no box,
|
|
||||||
// move_selected_files = just move the files immediately
|
|
||||||
|
|
||||||
var move_destination = "foobar";
|
|
||||||
// this is the target folder to move files to;
|
|
||||||
// by default it is a subfolder of the current folder,
|
|
||||||
// but it can also be an absolute path like "/foo/bar"
|
|
||||||
|
|
||||||
// ===
|
|
||||||
// === END OF CONFIG
|
|
||||||
// ===
|
|
||||||
|
|
||||||
var main_hotkey_handler, // copyparty's original hotkey handler
|
|
||||||
plugin_enabler, // timer to engage this plugin when safe
|
|
||||||
files_to_move; // list of files to move
|
|
||||||
|
|
||||||
function ask_for_confirmation_and_then_move() {
|
|
||||||
var num_files = msel.getsel().length,
|
|
||||||
msg = "move the selected " + num_files + " files?";
|
|
||||||
|
|
||||||
if (!num_files)
|
|
||||||
return toast.warn(2, 'no files were selected to be moved');
|
|
||||||
|
|
||||||
modal.confirm(msg, move_selected_files, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
function move_selected_files() {
|
|
||||||
var selection = msel.getsel();
|
|
||||||
|
|
||||||
if (!selection.length)
|
|
||||||
return toast.warn(2, 'no files were selected to be moved');
|
|
||||||
|
|
||||||
if (thegrid.bbox) {
|
|
||||||
// close image/video viewer
|
|
||||||
thegrid.bbox = null;
|
|
||||||
baguetteBox.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
files_to_move = [];
|
|
||||||
for (var a = 0; a < selection.length; a++)
|
|
||||||
files_to_move.push(selection[a].vp);
|
|
||||||
|
|
||||||
move_next_file();
|
|
||||||
}
|
|
||||||
|
|
||||||
function move_next_file() {
|
|
||||||
var num_files = files_to_move.length,
|
|
||||||
filepath = files_to_move.pop(),
|
|
||||||
filename = vsplit(filepath)[1];
|
|
||||||
|
|
||||||
toast.inf(10, "moving " + num_files + " files...\n\n" + filename);
|
|
||||||
|
|
||||||
var dst = move_destination;
|
|
||||||
|
|
||||||
if (!dst.endsWith('/'))
|
|
||||||
// must have a trailing slash, so add it
|
|
||||||
dst += '/';
|
|
||||||
|
|
||||||
if (!dst.startsWith('/'))
|
|
||||||
// destination is a relative path, so prefix current folder path
|
|
||||||
dst = get_evpath() + dst;
|
|
||||||
|
|
||||||
// and finally append the filename
|
|
||||||
dst += '/' + filename;
|
|
||||||
|
|
||||||
// prepare the move-request to be sent
|
|
||||||
var xhr = new XHR();
|
|
||||||
xhr.onload = xhr.onerror = function() {
|
|
||||||
if (this.status !== 201)
|
|
||||||
return toast.err(30, 'move failed: ' + esc(this.responseText));
|
|
||||||
|
|
||||||
if (files_to_move.length)
|
|
||||||
return move_next_file(); // still more files to go
|
|
||||||
|
|
||||||
toast.ok(1, 'move OK');
|
|
||||||
treectl.goto(); // reload the folder contents
|
|
||||||
};
|
|
||||||
xhr.open('POST', filepath + '?move=' + dst);
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function our_hotkey_handler(e) {
|
|
||||||
// bail if either ALT, CTRL, or SHIFT is pressed
|
|
||||||
if (e.altKey || e.shiftKey || e.isComposing || ctrl(e))
|
|
||||||
return main_hotkey_handler(e); // let copyparty handle this keystroke
|
|
||||||
|
|
||||||
var key_name = (e.code || e.key) + '',
|
|
||||||
ae = document.activeElement,
|
|
||||||
aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : '';
|
|
||||||
|
|
||||||
// check the current aet (active element type),
|
|
||||||
// only continue if one of the following currently has input focus:
|
|
||||||
// nothing | link | button | table-row | table-cell | div | text
|
|
||||||
if (aet && !/^(a|button|tr|td|div|pre)$/.test(aet))
|
|
||||||
return main_hotkey_handler(e); // let copyparty handle this keystroke
|
|
||||||
|
|
||||||
if (key_name == 'KeyW') {
|
|
||||||
// okay, this one's for us... do the thing
|
|
||||||
action_to_perform();
|
|
||||||
return ev(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return main_hotkey_handler(e); // let copyparty handle this keystroke
|
|
||||||
}
|
|
||||||
|
|
||||||
function enable_plugin() {
|
|
||||||
if (!window.hotkeys_attached)
|
|
||||||
return console.log('quickmove is waiting for the page to finish loading');
|
|
||||||
|
|
||||||
clearInterval(plugin_enabler);
|
|
||||||
main_hotkey_handler = document.onkeydown;
|
|
||||||
document.onkeydown = our_hotkey_handler;
|
|
||||||
console.log('quickmove is now enabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
// copyparty doesn't enable its hotkeys until the page
|
|
||||||
// has finished loading, so we'll wait for that too
|
|
||||||
plugin_enabler = setInterval(enable_plugin, 100);
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,208 +0,0 @@
|
||||||
/* untz untz untz untz */
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
|
|
||||||
var can, ctx, W, H, fft, buf, bars, barw, pv,
|
|
||||||
hue = 0,
|
|
||||||
ibeat = 0,
|
|
||||||
beats = [9001],
|
|
||||||
beats_url = '',
|
|
||||||
uofs = 0,
|
|
||||||
ops = ebi('ops'),
|
|
||||||
raving = false,
|
|
||||||
recalc = 0,
|
|
||||||
cdown = 0,
|
|
||||||
FC = 0.9,
|
|
||||||
css = `<style>
|
|
||||||
|
|
||||||
#fft {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
box-shadow: inset 0 0 0 white;
|
|
||||||
}
|
|
||||||
#ops>a,
|
|
||||||
#path>a {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
body.untz {
|
|
||||||
animation: untz-body 200ms ease-out;
|
|
||||||
}
|
|
||||||
@keyframes untz-body {
|
|
||||||
0% {inset 0 0 20em white}
|
|
||||||
100% {inset 0 0 0 white}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
:root, html.a, html.b, html.c, html.d, html.e {
|
|
||||||
--row-alt: rgba(48,52,78,0.2);
|
|
||||||
}
|
|
||||||
#files td {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>`;
|
|
||||||
|
|
||||||
QS('body').appendChild(mknod('div', null, css));
|
|
||||||
|
|
||||||
function rave_load() {
|
|
||||||
console.log('rave_load');
|
|
||||||
can = mknod('canvas', 'fft');
|
|
||||||
QS('body').appendChild(can);
|
|
||||||
ctx = can.getContext('2d');
|
|
||||||
|
|
||||||
fft = new AnalyserNode(actx, {
|
|
||||||
"fftSize": 2048,
|
|
||||||
"maxDecibels": 0,
|
|
||||||
"smoothingTimeConstant": 0.7,
|
|
||||||
});
|
|
||||||
ibeat = 0;
|
|
||||||
beats = [9001];
|
|
||||||
buf = new Uint8Array(fft.frequencyBinCount);
|
|
||||||
bars = buf.length * FC;
|
|
||||||
afilt.filters.push(fft);
|
|
||||||
if (!raving) {
|
|
||||||
raving = true;
|
|
||||||
raver();
|
|
||||||
}
|
|
||||||
beats_url = mp.au.src.split('?')[0].replace(/(.*\/)(.*)/, '$1.beats/$2.txt');
|
|
||||||
console.log("reading beats from", beats_url);
|
|
||||||
var xhr = new XHR();
|
|
||||||
xhr.open('GET', beats_url, true);
|
|
||||||
xhr.onload = readbeats;
|
|
||||||
xhr.url = beats_url;
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function rave_unload() {
|
|
||||||
qsr('#fft');
|
|
||||||
can = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readbeats() {
|
|
||||||
if (this.url != beats_url)
|
|
||||||
return console.log('old beats??', this.url, beats_url);
|
|
||||||
|
|
||||||
var sbeats = this.responseText.replace(/\r/g, '').split(/\n/g);
|
|
||||||
if (sbeats.length < 3)
|
|
||||||
return;
|
|
||||||
|
|
||||||
beats = [];
|
|
||||||
for (var a = 0; a < sbeats.length; a++)
|
|
||||||
beats.push(parseFloat(sbeats[a]));
|
|
||||||
|
|
||||||
var end = beats.slice(-2),
|
|
||||||
t = end[1],
|
|
||||||
d = t - end[0];
|
|
||||||
|
|
||||||
while (d > 0.1 && t < 1200)
|
|
||||||
beats.push(t += d);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hrand() {
|
|
||||||
return Math.random() - 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
function raver() {
|
|
||||||
if (!can) {
|
|
||||||
raving = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(raver);
|
|
||||||
if (!mp || !mp.au || mp.au.paused)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (--uofs >= 0) {
|
|
||||||
document.body.style.marginLeft = hrand() * uofs + 'px';
|
|
||||||
ebi('tree').style.marginLeft = hrand() * uofs + 'px';
|
|
||||||
for (var a of QSA('#ops>a, #path>a, #pctl>a'))
|
|
||||||
a.style.transform = 'translate(' + hrand() * uofs * 1 + 'px, ' + hrand() * uofs * 0.7 + 'px) rotate(' + Math.random() * uofs * 0.7 + 'deg)'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (--recalc < 0) {
|
|
||||||
recalc = 60;
|
|
||||||
var tree = ebi('tree'),
|
|
||||||
x = tree.style.display == 'none' ? 0 : tree.offsetWidth;
|
|
||||||
|
|
||||||
//W = can.width = window.innerWidth - x;
|
|
||||||
//H = can.height = window.innerHeight;
|
|
||||||
//H = ebi('widget').offsetTop;
|
|
||||||
W = can.width = bars;
|
|
||||||
H = can.height = 512;
|
|
||||||
barw = 1; //parseInt(0.8 + W / bars);
|
|
||||||
can.style.left = x + 'px';
|
|
||||||
can.style.width = (window.innerWidth - x) + 'px';
|
|
||||||
can.style.height = ebi('widget').offsetTop + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
//if (--cdown == 1)
|
|
||||||
// clmod(ops, 'untz');
|
|
||||||
|
|
||||||
fft.getByteFrequencyData(buf);
|
|
||||||
|
|
||||||
var imax = 0, vmax = 0;
|
|
||||||
for (var a = 10; a < 50; a++)
|
|
||||||
if (vmax < buf[a]) {
|
|
||||||
vmax = buf[a];
|
|
||||||
imax = a;
|
|
||||||
}
|
|
||||||
|
|
||||||
hue = hue * 0.93 + imax * 0.07;
|
|
||||||
|
|
||||||
ctx.fillStyle = 'rgba(0,0,0,0)';
|
|
||||||
ctx.fillRect(0, 0, W, H);
|
|
||||||
ctx.clearRect(0, 0, W, H);
|
|
||||||
ctx.fillStyle = 'hsla(' + (hue * 2.5) + ',100%,50%,0.7)';
|
|
||||||
|
|
||||||
var x = 0, mul = (H / 256) * 0.5;
|
|
||||||
for (var a = 0; a < buf.length * FC; a++) {
|
|
||||||
var v = buf[a] * mul * (1 + 0.69 * a / buf.length);
|
|
||||||
ctx.fillRect(x, H - v, barw, v);
|
|
||||||
x += barw;
|
|
||||||
}
|
|
||||||
|
|
||||||
var t = mp.au.currentTime + 0.05;
|
|
||||||
|
|
||||||
if (ibeat >= beats.length || beats[ibeat] > t)
|
|
||||||
return;
|
|
||||||
|
|
||||||
while (ibeat < beats.length && beats[ibeat++] < t)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
return untz();
|
|
||||||
|
|
||||||
var cv = 0;
|
|
||||||
for (var a = 0; a < 128; a++)
|
|
||||||
cv += buf[a];
|
|
||||||
|
|
||||||
if (cv - pv > 1000) {
|
|
||||||
console.log(pv, cv, cv - pv);
|
|
||||||
if (cdown < 0) {
|
|
||||||
clmod(ops, 'untz', 1);
|
|
||||||
cdown = 20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pv = cv;
|
|
||||||
}
|
|
||||||
|
|
||||||
function untz() {
|
|
||||||
console.log('untz');
|
|
||||||
uofs = 14;
|
|
||||||
document.body.animate([
|
|
||||||
{ boxShadow: 'inset 0 0 1em #f0c' },
|
|
||||||
{ boxShadow: 'inset 0 0 20em #f0c', offset: 0.2 },
|
|
||||||
{ boxShadow: 'inset 0 0 0 #f0c' },
|
|
||||||
], { duration: 200, iterations: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
afilt.plugs.push({
|
|
||||||
"en": true,
|
|
||||||
"load": rave_load,
|
|
||||||
"unload": rave_unload
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
|
@ -10,7 +10,7 @@ name="copyparty"
|
||||||
rcvar="copyparty_enable"
|
rcvar="copyparty_enable"
|
||||||
copyparty_user="copyparty"
|
copyparty_user="copyparty"
|
||||||
copyparty_args="-e2dsa -v /storage:/storage:r" # change as you see fit
|
copyparty_args="-e2dsa -v /storage:/storage:r" # change as you see fit
|
||||||
copyparty_command="/usr/local/bin/python3.9 /usr/local/copyparty/copyparty-sfx.py ${copyparty_args}"
|
copyparty_command="/usr/local/bin/python3.8 /usr/local/copyparty/copyparty-sfx.py ${copyparty_args}"
|
||||||
pidfile="/var/run/copyparty/${name}.pid"
|
pidfile="/var/run/copyparty/${name}.pid"
|
||||||
command="/usr/sbin/daemon"
|
command="/usr/sbin/daemon"
|
||||||
command_args="-P ${pidfile} -r -f ${copyparty_command}"
|
command_args="-P ${pidfile} -r -f ${copyparty_command}"
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"code": "// https://addons.mozilla.org/en-US/firefox/addon/contextlets/\n// https://github.com/davidmhammond/contextlets\n\nvar url = 'http://partybox.local:3923/';\nvar pw = 'wark';\n\nvar xhr = new XMLHttpRequest();\nxhr.msg = this.info.linkUrl || this.info.srcUrl;\nxhr.open('POST', url, true);\nxhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');\nxhr.setRequestHeader('PW', pw);\nxhr.send('msg=' + xhr.msg);\n",
|
|
||||||
"contexts": [
|
|
||||||
"link"
|
|
||||||
],
|
|
||||||
"icons": null,
|
|
||||||
"patterns": "",
|
|
||||||
"scope": "background",
|
|
||||||
"title": "send to cpp",
|
|
||||||
"type": "normal"
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# this script will install copyparty onto an iOS device (iPhone/iPad)
|
|
||||||
#
|
|
||||||
# step 1: install a-Shell:
|
|
||||||
# https://apps.apple.com/us/app/a-shell/id1473805438
|
|
||||||
#
|
|
||||||
# step 2: copypaste the following command into a-Shell:
|
|
||||||
# curl https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh
|
|
||||||
#
|
|
||||||
# step 3: launch copyparty with this command: cpp
|
|
||||||
#
|
|
||||||
# if you ever want to upgrade copyparty, just repeat step 2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cd "$HOME/Documents"
|
|
||||||
curl -Locopyparty https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# create the config file? (cannot use heredoc because body too large)
|
|
||||||
[ -e cpc ] || {
|
|
||||||
echo '[global]' >cpc
|
|
||||||
echo ' p: 80, 443, 3923 # enable http and https on these ports' >>cpc
|
|
||||||
echo ' e2dsa # enable file indexing and filesystem scanning' >>cpc
|
|
||||||
echo ' e2ts # and enable multimedia indexing' >>cpc
|
|
||||||
echo ' ver # show copyparty version in the controlpanel' >>cpc
|
|
||||||
echo ' qrz: 2 # enable qr-code and make it big' >>cpc
|
|
||||||
echo ' qrp: 1 # reduce qr-code padding' >>cpc
|
|
||||||
echo ' qr-fg: -1 # optimize for basic/simple terminals' >>cpc
|
|
||||||
echo ' qr-wait: 0.3 # less chance of getting scrolled away' >>cpc
|
|
||||||
echo '' >>cpc
|
|
||||||
echo ' # enable these by uncommenting them:' >>cpc
|
|
||||||
echo ' # ftp: 21 # enable ftp server on port 21' >>cpc
|
|
||||||
echo ' # tftp: 69 # enable tftp server on port 69' >>cpc
|
|
||||||
echo '' >>cpc
|
|
||||||
echo '[/]' >>cpc
|
|
||||||
echo ' ~/Documents' >>cpc
|
|
||||||
echo ' accs:' >>cpc
|
|
||||||
echo ' A: *' >>cpc
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# create the launcher?
|
|
||||||
[ -e cpp ] || {
|
|
||||||
echo '#!/bin/sh' >cpp
|
|
||||||
echo '' >>cpp
|
|
||||||
echo '# change the font so the qr-code draws correctly:' >>cpp
|
|
||||||
echo 'config -n "Menlo" # name' >>cpp
|
|
||||||
echo 'config -s 8 # size' >>cpp
|
|
||||||
echo '' >>cpp
|
|
||||||
echo '# launch copyparty' >>cpp
|
|
||||||
echo 'exec copyparty -c cpc "$@"' >>cpp
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
chmod 755 copyparty cpp
|
|
||||||
echo
|
|
||||||
echo =================================
|
|
||||||
echo
|
|
||||||
echo 'okay, all done!'
|
|
||||||
echo
|
|
||||||
echo 'you can edit your config'
|
|
||||||
echo 'with this command: vim cpc'
|
|
||||||
echo
|
|
||||||
echo 'you can run copyparty'
|
|
||||||
echo 'with this command: cpp'
|
|
||||||
echo
|
|
19
contrib/sharex-html.sxcu
Normal file
19
contrib/sharex-html.sxcu
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"Version": "13.5.0",
|
||||||
|
"Name": "copyparty-html",
|
||||||
|
"DestinationType": "ImageUploader",
|
||||||
|
"RequestMethod": "POST",
|
||||||
|
"RequestURL": "http://127.0.0.1:3923/sharex",
|
||||||
|
"Parameters": {
|
||||||
|
"pw": "wark"
|
||||||
|
},
|
||||||
|
"Body": "MultipartFormData",
|
||||||
|
"Arguments": {
|
||||||
|
"act": "bput"
|
||||||
|
},
|
||||||
|
"FileFormName": "f",
|
||||||
|
"RegexList": [
|
||||||
|
"bytes // <a href=\"/([^\"]+)\""
|
||||||
|
],
|
||||||
|
"URL": "http://127.0.0.1:3923/$regex:1|1$"
|
||||||
|
}
|
|
@ -1,19 +1,17 @@
|
||||||
{
|
{
|
||||||
"Version": "15.0.0",
|
"Version": "13.5.0",
|
||||||
"Name": "copyparty",
|
"Name": "copyparty",
|
||||||
"DestinationType": "ImageUploader",
|
"DestinationType": "ImageUploader",
|
||||||
"RequestMethod": "POST",
|
"RequestMethod": "POST",
|
||||||
"RequestURL": "http://127.0.0.1:3923/sharex",
|
"RequestURL": "http://127.0.0.1:3923/sharex",
|
||||||
"Parameters": {
|
"Parameters": {
|
||||||
|
"pw": "wark",
|
||||||
"j": null
|
"j": null
|
||||||
},
|
},
|
||||||
"Headers": {
|
|
||||||
"pw": "PUT_YOUR_PASSWORD_HERE_MY_DUDE"
|
|
||||||
},
|
|
||||||
"Body": "MultipartFormData",
|
"Body": "MultipartFormData",
|
||||||
"Arguments": {
|
"Arguments": {
|
||||||
"act": "bput"
|
"act": "bput"
|
||||||
},
|
},
|
||||||
"FileFormName": "f",
|
"FileFormName": "f",
|
||||||
"URL": "{json:files[0].url}"
|
"URL": "$json:files[0].url$"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"Name": "copyparty",
|
|
||||||
"DestinationType": "ImageUploader, TextUploader, FileUploader",
|
|
||||||
"RequestURL": "http://127.0.0.1:3923/sharex",
|
|
||||||
"FileFormName": "f",
|
|
||||||
"Arguments": {
|
|
||||||
"act": "bput"
|
|
||||||
},
|
|
||||||
"Headers": {
|
|
||||||
"accept": "url",
|
|
||||||
"pw": "PUT_YOUR_PASSWORD_HERE_MY_DUDE"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,3 @@
|
||||||
# NOTE: this is now a built-in feature in copyparty
|
|
||||||
# but you may still want this if you have specific needs
|
|
||||||
#
|
|
||||||
# systemd service which generates a new TLS certificate on each boot,
|
# systemd service which generates a new TLS certificate on each boot,
|
||||||
# that way the one-year expiry time won't cause any issues --
|
# that way the one-year expiry time won't cause any issues --
|
||||||
# just have everyone trust the ca.pem once every 10 years
|
# just have everyone trust the ca.pem once every 10 years
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
# this will start `/usr/bin/copyparty`
|
|
||||||
# and read config from `$HOME/.config/copyparty.conf`
|
|
||||||
#
|
|
||||||
# unless you add -q to disable logging, you may want to remove the
|
|
||||||
# following line to allow buffering (slightly better performance):
|
|
||||||
# Environment=PYTHONUNBUFFERED=x
|
|
||||||
|
|
||||||
[Unit]
|
|
||||||
Description=copyparty file server
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=notify
|
|
||||||
SyslogIdentifier=copyparty
|
|
||||||
WorkingDirectory=/var/lib/copyparty-jail
|
|
||||||
Environment=PYTHONUNBUFFERED=x
|
|
||||||
Environment=PRTY_CONFIG=%h/.config/copyparty/copyparty.conf
|
|
||||||
ExecReload=/bin/kill -s USR1 $MAINPID
|
|
||||||
|
|
||||||
# ensure there is a config
|
|
||||||
ExecStartPre=/bin/bash -c 'if [[ ! -f %h/.config/copyparty/copyparty.conf ]]; then mkdir -p %h/.config/copyparty; cp /etc/copyparty/copyparty.conf %h/.config/copyparty/copyparty.conf; fi'
|
|
||||||
|
|
||||||
# run copyparty
|
|
||||||
ExecStart=/usr/bin/python3 /usr/bin/copyparty
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
|
@ -1,13 +0,0 @@
|
||||||
[global]
|
|
||||||
i: 127.0.0.1
|
|
||||||
|
|
||||||
[accounts]
|
|
||||||
user: password
|
|
||||||
|
|
||||||
[/]
|
|
||||||
/var/lib/copyparty-jail
|
|
||||||
accs:
|
|
||||||
r: *
|
|
||||||
rwdma: user
|
|
||||||
flags:
|
|
||||||
grid
|
|
|
@ -1,42 +0,0 @@
|
||||||
# not actually YAML but lets pretend:
|
|
||||||
# -*- mode: yaml -*-
|
|
||||||
# vim: ft=yaml:
|
|
||||||
|
|
||||||
|
|
||||||
# put this file in /etc/
|
|
||||||
|
|
||||||
|
|
||||||
[global]
|
|
||||||
e2dsa # enable file indexing and filesystem scanning
|
|
||||||
e2ts # and enable multimedia indexing
|
|
||||||
ansi # and colors in log messages
|
|
||||||
|
|
||||||
# disable logging to stdout/journalctl and log to a file instead;
|
|
||||||
# $LOGS_DIRECTORY is usually /var/log/copyparty (comes from systemd)
|
|
||||||
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the
|
|
||||||
# full path will be something like /var/log/copyparty/2023-1130.txt
|
|
||||||
# (note: enable compression by adding .xz at the end)
|
|
||||||
q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
|
|
||||||
|
|
||||||
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
|
|
||||||
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)
|
|
||||||
# ftp: 3921 # enable ftp server on port 3921
|
|
||||||
# p: 3939 # listen on another port
|
|
||||||
# df: 16 # stop accepting uploads if less than 16 GB free disk space
|
|
||||||
# ver # show copyparty version in the controlpanel
|
|
||||||
# grid # show thumbnails/grid-view by default
|
|
||||||
# theme: 2 # monokai
|
|
||||||
# name: datasaver # change the server-name that's displayed in the browser
|
|
||||||
# stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow)
|
|
||||||
# no-robots, force-js # make it harder for search engines to read your server
|
|
||||||
|
|
||||||
|
|
||||||
[accounts]
|
|
||||||
ed: wark # username: password
|
|
||||||
|
|
||||||
|
|
||||||
[/] # create a volume at "/" (the webroot), which will
|
|
||||||
/mnt # share the contents of the "/mnt" folder
|
|
||||||
accs:
|
|
||||||
rw: * # everyone gets read-write access, but
|
|
||||||
rwmda: ed # the user "ed" gets read-write-move-delete-admin
|
|
|
@ -1,34 +1,23 @@
|
||||||
# this will start `/usr/local/bin/copyparty-sfx.py` and
|
# this will start `/usr/local/bin/copyparty-sfx.py`
|
||||||
# read copyparty config from `/etc/copyparty.conf`, for example:
|
# and share '/mnt' with anonymous read+write
|
||||||
# https://github.com/9001/copyparty/blob/hovudstraum/contrib/systemd/copyparty.conf
|
|
||||||
#
|
#
|
||||||
# installation:
|
# installation:
|
||||||
# wget https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py -O /usr/local/bin/copyparty-sfx.py
|
# cp -pv copyparty.service /etc/systemd/system
|
||||||
# useradd -r -s /sbin/nologin -m -d /var/lib/copyparty copyparty
|
# restorecon -vr /etc/systemd/system/copyparty.service
|
||||||
# firewall-cmd --permanent --add-port=3923/tcp # --zone=libvirt
|
# firewall-cmd --permanent --add-port={80,443,3923}/tcp # --zone=libvirt
|
||||||
# firewall-cmd --reload
|
# firewall-cmd --reload
|
||||||
# cp -pv copyparty.service /etc/systemd/system/
|
|
||||||
# cp -pv copyparty.conf /etc/
|
|
||||||
# restorecon -vr /etc/systemd/system/copyparty.service # on fedora/rhel
|
|
||||||
# systemctl daemon-reload && systemctl enable --now copyparty
|
# systemctl daemon-reload && systemctl enable --now copyparty
|
||||||
#
|
#
|
||||||
# every time you edit this file, you must "systemctl daemon-reload"
|
|
||||||
# for the changes to take effect and then "systemctl restart copyparty"
|
|
||||||
#
|
|
||||||
# if it fails to start, first check this: systemctl status copyparty
|
|
||||||
# then try starting it while viewing logs:
|
|
||||||
# journalctl -fan 100
|
|
||||||
# tail -Fn 100 /var/log/copyparty/$(date +%Y-%m%d.log)
|
|
||||||
#
|
|
||||||
# if you run into any issues, for example thumbnails not working,
|
|
||||||
# try removing the "some quick hardening" section and then please
|
|
||||||
# let me know if that actually helped so we can look into it
|
|
||||||
#
|
|
||||||
# you may want to:
|
# you may want to:
|
||||||
# - change "User=copyparty" and "/var/lib/copyparty/" to another user
|
# change "User=cpp" and "/home/cpp/" to another user
|
||||||
# - edit /etc/copyparty.conf to configure copyparty
|
# remove the nft lines to only listen on port 3923
|
||||||
# and in the ExecStart= line:
|
# and in the ExecStart= line:
|
||||||
# - change '/usr/bin/python3' to another interpreter
|
# change '/usr/bin/python3' to another interpreter
|
||||||
|
# change '/mnt::rw' to another location or permission-set
|
||||||
|
# add '-q' to disable logging on busy servers
|
||||||
|
# add '-i 127.0.0.1' to only allow local connections
|
||||||
|
# add '-e2dsa' to enable filesystem scanning + indexing
|
||||||
|
# add '-e2ts' to enable metadata indexing
|
||||||
#
|
#
|
||||||
# with `Type=notify`, copyparty will signal systemd when it is ready to
|
# with `Type=notify`, copyparty will signal systemd when it is ready to
|
||||||
# accept connections; correctly delaying units depending on copyparty.
|
# accept connections; correctly delaying units depending on copyparty.
|
||||||
|
@ -36,9 +25,11 @@
|
||||||
# python disabling line-buffering, so messages are out-of-order:
|
# python disabling line-buffering, so messages are out-of-order:
|
||||||
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
||||||
#
|
#
|
||||||
########################################################################
|
# unless you add -q to disable logging, you may want to remove the
|
||||||
########################################################################
|
# following line to allow buffering (slightly better performance):
|
||||||
|
# Environment=PYTHONUNBUFFERED=x
|
||||||
|
#
|
||||||
|
# keep ExecStartPre before ExecStart, at least on rhel8
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=copyparty file server
|
Description=copyparty file server
|
||||||
|
@ -48,52 +39,23 @@ Type=notify
|
||||||
SyslogIdentifier=copyparty
|
SyslogIdentifier=copyparty
|
||||||
Environment=PYTHONUNBUFFERED=x
|
Environment=PYTHONUNBUFFERED=x
|
||||||
ExecReload=/bin/kill -s USR1 $MAINPID
|
ExecReload=/bin/kill -s USR1 $MAINPID
|
||||||
PermissionsStartOnly=true
|
|
||||||
|
|
||||||
## user to run as + where the TLS certificate is (if any)
|
# user to run as + where the TLS certificate is (if any)
|
||||||
##
|
User=cpp
|
||||||
User=copyparty
|
Environment=XDG_CONFIG_HOME=/home/cpp/.config
|
||||||
Group=copyparty
|
|
||||||
WorkingDirectory=/var/lib/copyparty
|
|
||||||
Environment=XDG_CONFIG_HOME=/var/lib/copyparty/.config
|
|
||||||
|
|
||||||
## OPTIONAL: allow copyparty to listen on low ports (like 80/443);
|
# setup forwarding from ports 80 and 443 to port 3923
|
||||||
## you need to uncomment the "p: 80,443,3923" in the config too
|
ExecStartPre=+/bin/bash -c 'nft -n -a list table nat | awk "/ to :3923 /{print\$NF}" | xargs -rL1 nft delete rule nat prerouting handle; true'
|
||||||
## ------------------------------------------------------------
|
ExecStartPre=+nft add table ip nat
|
||||||
## a slightly safer alternative is to enable partyalone.service
|
ExecStartPre=+nft -- add chain ip nat prerouting { type nat hook prerouting priority -100 \; }
|
||||||
## which does portforwarding with nftables instead, but an even
|
ExecStartPre=+nft add rule ip nat prerouting tcp dport 80 redirect to :3923
|
||||||
## better option is to use a reverse-proxy (nginx/caddy/...)
|
ExecStartPre=+nft add rule ip nat prerouting tcp dport 443 redirect to :3923
|
||||||
##
|
|
||||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
|
||||||
|
|
||||||
## some quick hardening; TODO port more from the nixos package
|
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
|
||||||
##
|
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
|
||||||
MemoryMax=50%
|
|
||||||
MemorySwapMax=50%
|
|
||||||
ProtectClock=true
|
|
||||||
ProtectControlGroups=true
|
|
||||||
ProtectHostname=true
|
|
||||||
ProtectKernelLogs=true
|
|
||||||
ProtectKernelModules=true
|
|
||||||
ProtectKernelTunables=true
|
|
||||||
ProtectProc=invisible
|
|
||||||
RemoveIPC=true
|
|
||||||
RestrictNamespaces=true
|
|
||||||
RestrictRealtime=true
|
|
||||||
RestrictSUIDSGID=true
|
|
||||||
|
|
||||||
## create a directory for logfiles;
|
# copyparty settings
|
||||||
## this defines $LOGS_DIRECTORY which is used in copyparty.conf
|
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -e2d -v /mnt::rw
|
||||||
##
|
|
||||||
LogsDirectory=copyparty
|
|
||||||
|
|
||||||
## finally, start copyparty and give it the config file:
|
|
||||||
##
|
|
||||||
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -c /etc/copyparty.conf
|
|
||||||
|
|
||||||
# NOTE: if you installed copyparty from an OS package repo (nice)
|
|
||||||
# then you probably want something like this instead:
|
|
||||||
#ExecStart=/usr/bin/copyparty -c /etc/copyparty.conf
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
# this will start `/usr/bin/copyparty`
|
|
||||||
# and read config from `/etc/copyparty/copyparty.conf`
|
|
||||||
#
|
|
||||||
# the %i refers to whatever you put after the copyparty@
|
|
||||||
# so with copyparty@foo.service, %i == foo
|
|
||||||
#
|
|
||||||
# unless you add -q to disable logging, you may want to remove the
|
|
||||||
# following line to allow buffering (slightly better performance):
|
|
||||||
# Environment=PYTHONUNBUFFERED=x
|
|
||||||
|
|
||||||
[Unit]
|
|
||||||
Description=copyparty file server
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=notify
|
|
||||||
SyslogIdentifier=copyparty
|
|
||||||
WorkingDirectory=/var/lib/copyparty-jail
|
|
||||||
Environment=PYTHONUNBUFFERED=x
|
|
||||||
Environment=PRTY_CONFIG=/etc/copyparty/copyparty.conf
|
|
||||||
ExecReload=/bin/kill -s USR1 $MAINPID
|
|
||||||
|
|
||||||
# user to run as + where the TLS certificate is (if any)
|
|
||||||
User=%i
|
|
||||||
Environment=XDG_CONFIG_HOME=/home/%i/.config
|
|
||||||
|
|
||||||
# run copyparty
|
|
||||||
ExecStart=/usr/bin/python3 /usr/bin/copyparty
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue