copyparty 1.19.1__tar.gz → 1.19.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. {copyparty-1.19.1 → copyparty-1.19.2}/PKG-INFO +39 -3
  2. {copyparty-1.19.1 → copyparty-1.19.2}/README.md +38 -2
  3. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/__main__.py +127 -28
  4. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/__version__.py +2 -2
  5. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/authsrv.py +33 -7
  6. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/cfg.py +7 -1
  7. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/dxml.py +3 -0
  8. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/ftpd.py +21 -6
  9. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/httpcli.py +81 -16
  10. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/httpsrv.py +6 -0
  11. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/mtag.py +88 -6
  12. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/svchub.py +76 -5
  13. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/tcpsrv.py +6 -0
  14. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/th_cli.py +5 -1
  15. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/th_srv.py +160 -51
  16. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/u2idx.py +1 -1
  17. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/up2k.py +80 -39
  18. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/util.py +25 -1
  19. copyparty-1.19.2/copyparty/web/baguettebox.js.gz +0 -0
  20. copyparty-1.19.2/copyparty/web/browser.css.gz +0 -0
  21. copyparty-1.19.2/copyparty/web/browser.js.gz +0 -0
  22. copyparty-1.19.2/copyparty/web/rups.js.gz +0 -0
  23. copyparty-1.19.2/copyparty/web/splash.css.gz +0 -0
  24. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/splash.html +8 -1
  25. copyparty-1.19.2/copyparty/web/splash.js.gz +0 -0
  26. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/svcs.html +1 -1
  27. copyparty-1.19.2/copyparty/web/up2k.js.gz +0 -0
  28. copyparty-1.19.2/copyparty/web/util.js.gz +0 -0
  29. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty.egg-info/PKG-INFO +39 -3
  30. copyparty-1.19.1/copyparty/web/baguettebox.js.gz +0 -0
  31. copyparty-1.19.1/copyparty/web/browser.css.gz +0 -0
  32. copyparty-1.19.1/copyparty/web/browser.js.gz +0 -0
  33. copyparty-1.19.1/copyparty/web/rups.js.gz +0 -0
  34. copyparty-1.19.1/copyparty/web/splash.css.gz +0 -0
  35. copyparty-1.19.1/copyparty/web/splash.js.gz +0 -0
  36. copyparty-1.19.1/copyparty/web/up2k.js.gz +0 -0
  37. copyparty-1.19.1/copyparty/web/util.js.gz +0 -0
  38. {copyparty-1.19.1 → copyparty-1.19.2}/LICENSE +0 -0
  39. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/__init__.py +0 -0
  40. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/bos/__init__.py +0 -0
  41. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/bos/bos.py +0 -0
  42. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/bos/path.py +0 -0
  43. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/broker_mp.py +0 -0
  44. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/broker_mpw.py +0 -0
  45. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/broker_thr.py +0 -0
  46. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/broker_util.py +0 -0
  47. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/cert.py +0 -0
  48. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/fsutil.py +0 -0
  49. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/httpconn.py +0 -0
  50. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/ico.py +0 -0
  51. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/mdns.py +0 -0
  52. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/metrics.py +0 -0
  53. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/multicast.py +0 -0
  54. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/pwhash.py +0 -0
  55. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/res/COPYING.txt +0 -0
  56. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/res/__init__.py +0 -0
  57. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/res/insecure.pem +0 -0
  58. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/smbd.py +0 -0
  59. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/ssdp.py +0 -0
  60. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/star.py +0 -0
  61. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/__init__.py +0 -0
  62. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/dnslib/__init__.py +0 -0
  63. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/dnslib/bimap.py +0 -0
  64. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/dnslib/bit.py +0 -0
  65. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/dnslib/buffer.py +0 -0
  66. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/dnslib/dns.py +0 -0
  67. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/dnslib/label.py +0 -0
  68. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/dnslib/lex.py +0 -0
  69. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/dnslib/ranges.py +0 -0
  70. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/ifaddr/__init__.py +0 -0
  71. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/ifaddr/_posix.py +0 -0
  72. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/ifaddr/_shared.py +0 -0
  73. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/ifaddr/_win32.py +0 -0
  74. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/qrcodegen.py +0 -0
  75. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/stolen/surrogateescape.py +0 -0
  76. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/sutil.py +0 -0
  77. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/szip.py +0 -0
  78. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/tftpd.py +0 -0
  79. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/a/__init__.py +0 -0
  80. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/a/partyfuse.py +0 -0
  81. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/a/u2c.py +0 -0
  82. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/a/webdav-cfg.bat +0 -0
  83. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/browser.html +0 -0
  84. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/browser2.html +0 -0
  85. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/cf.html +0 -0
  86. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/dbg-audio.js.gz +0 -0
  87. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/__init__.py +0 -0
  88. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/busy.mp3.gz +0 -0
  89. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/easymde.css.gz +0 -0
  90. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/easymde.js.gz +0 -0
  91. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/fuse.py +0 -0
  92. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/marked.js.gz +0 -0
  93. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/mini-fa.css.gz +0 -0
  94. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/mini-fa.woff +0 -0
  95. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/prism.css.gz +0 -0
  96. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/prism.js.gz +0 -0
  97. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/prismd.css.gz +0 -0
  98. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/scp.woff2 +0 -0
  99. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  100. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  101. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/idp.html +0 -0
  102. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/md.css.gz +0 -0
  103. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/md.html +0 -0
  104. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/md.js.gz +0 -0
  105. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/md2.css.gz +0 -0
  106. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/md2.js.gz +0 -0
  107. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/mde.css.gz +0 -0
  108. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/mde.html +0 -0
  109. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/mde.js.gz +0 -0
  110. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/msg.css.gz +0 -0
  111. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/msg.html +0 -0
  112. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/rups.css.gz +0 -0
  113. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/rups.html +0 -0
  114. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/shares.css.gz +0 -0
  115. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/shares.html +0 -0
  116. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/shares.js.gz +0 -0
  117. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/svcs.js.gz +0 -0
  118. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/ui.css.gz +0 -0
  119. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty/web/w.hash.js.gz +0 -0
  120. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty.egg-info/SOURCES.txt +0 -0
  121. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty.egg-info/dependency_links.txt +0 -0
  122. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty.egg-info/entry_points.txt +0 -0
  123. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty.egg-info/requires.txt +0 -0
  124. {copyparty-1.19.1 → copyparty-1.19.2}/copyparty.egg-info/top_level.txt +0 -0
  125. {copyparty-1.19.1 → copyparty-1.19.2}/pyproject.toml +0 -0
  126. {copyparty-1.19.1 → copyparty-1.19.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.19.1
3
+ Version: 1.19.2
4
4
  Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
5
5
  Author-email: ed <copyparty@ocv.me>
6
6
  License: MIT
@@ -155,7 +155,9 @@ made in Norway 🇳🇴
155
155
  * [upload events](#upload-events) - the older, more powerful approach ([examples](./bin/mtag/))
156
156
  * [handlers](#handlers) - redefine behavior with plugins ([examples](./bin/handlers/))
157
157
  * [ip auth](#ip-auth) - autologin based on IP range (CIDR)
158
+ * [restrict to ip](#restrict-to-ip) - limit a user to certain IP ranges (CIDR)
158
159
  * [identity providers](#identity-providers) - replace copyparty passwords with oauth and such
160
+ * [generic header auth](#generic-header-auth) - other ways to auth by header
159
161
  * [user-changeable passwords](#user-changeable-passwords) - if permitted, users can change their own passwords
160
162
  * [using the cloud as storage](#using-the-cloud-as-storage) - connecting to an aws s3 bucket and similar
161
163
  * [hiding from google](#hiding-from-google) - tell search engines you don't wanna be indexed
@@ -331,6 +333,7 @@ also see [comparison to similar software](./docs/versus.md)
331
333
  * ☑ realtime streaming of growing files (logfiles and such)
332
334
  * ☑ [thumbnails](#thumbnails)
333
335
  * ☑ ...of images using Pillow, pyvips, or FFmpeg
336
+ * ☑ ...of RAW images using rawpy
334
337
  * ☑ ...of videos using FFmpeg
335
338
  * ☑ ...of audio (spectrograms) using FFmpeg
336
339
  * ☑ cache eviction (max-age; maybe max-size eventually)
@@ -577,6 +580,8 @@ examples:
577
580
  * replacing the `g` permission with `wg` would let anonymous users upload files, but not see the required filekey to access it
578
581
  * replacing the `g` permission with `wG` would let anonymous users upload files, receiving a working direct link in return
579
582
 
583
+ if you want to grant access to all users who are logged in, the group `acct` will always contain all known users, so for example `-v /mnt/music:music:r,@acct`
584
+
580
585
  anyone trying to bruteforce a password gets banned according to `--ban-pw`; default is 24h ban for 9 failed attempts in 1 hour
581
586
 
582
587
  and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
@@ -602,6 +607,7 @@ and if you want to use config files instead of commandline args (good!) then her
602
607
  accs:
603
608
  r: u1, u2 # only these accounts can read,
604
609
  r: @g1 # (exactly the same, just with a group instead)
610
+ r: @acct # (alternatively, ALL users who are logged in)
605
611
  rw: u3 # and only u3 can read-write
606
612
 
607
613
  [/inc]
@@ -1957,6 +1963,20 @@ repeat the option to map additional subnets
1957
1963
  **be careful with this one!** if you have a reverseproxy, then you definitely want to make sure you have [real-ip](#real-ip) configured correctly, and it's probably a good idea to nullmap the reverseproxy's IP just in case; so if your reverseproxy is sending requests from `172.24.27.9` then that would be `--ipu=172.24.27.9/32=`
1958
1964
 
1959
1965
 
1966
+ ### restrict to ip
1967
+
1968
+ limit a user to certain IP ranges (CIDR) , using the global-option `--ipr`
1969
+
1970
+ for example, if the user `spartacus` should get rejected if they're not connecting from an IP that starts with `192.168.123` or `172.16`, then you can either specify `--ipr=192.168.123.0/24,172.16.0.0/16=spartacus` as a commandline option, or put this in a config file:
1971
+
1972
+ ```yaml
1973
+ [global]
1974
+ ipr: 192.168.123.0/24,172.16.0.0/16=spartacus
1975
+ ```
1976
+
1977
+ repeat the option to map additional users
1978
+
1979
+
1960
1980
  ## identity providers
1961
1981
 
1962
1982
  replace copyparty passwords with oauth and such
@@ -1976,6 +1996,20 @@ a more complete example of the copyparty configuration options [look like this](
1976
1996
  but if you just want to let users change their own passwords, then you probably want [user-changeable passwords](#user-changeable-passwords) instead
1977
1997
 
1978
1998
 
1999
+ ### generic header auth
2000
+
2001
+ other ways to auth by header
2002
+
2003
+ if you have a middleware which adds a header with a user identifier, for example tailscale's `Tailscale-User-Login: alice.m@forest.net` then you can automatically auth as `alice` by defining that mapping with `--idp-hm-usr '^Tailscale-User-Login^alice.m@forest.net^alice'` or the following config file:
2004
+
2005
+ ```yaml
2006
+ [global]
2007
+ idp-hm-usr: ^Tailscale-User-Login^alice.m@forest.net^alice
2008
+ ```
2009
+
2010
+ repeat the whole `idp-hm-usr` option to add more mappings
2011
+
2012
+
1979
2013
  ## user-changeable passwords
1980
2014
 
1981
2015
  if permitted, users can change their own passwords in the control-panel
@@ -2857,9 +2891,10 @@ enable [music tags](#metadata-from-audio-files):
2857
2891
  enable [thumbnails](#thumbnails) of...
2858
2892
  * **images:** `Pillow` and/or `pyvips` and/or `ffmpeg` (requires py2.7 or py3.5+)
2859
2893
  * **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH`
2860
- * **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler)
2894
+ * **HEIF pictures:** `pyvips` or `ffmpeg` or `pillow-heif`
2861
2895
  * **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin` or pillow v11.3+
2862
2896
  * **JPEG XL pictures:** `pyvips` or `ffmpeg`
2897
+ * **RAW images:** `rawpy`, plus one of `pyvips` or `Pillow` (for some formats)
2863
2898
 
2864
2899
  enable sending [zeromq messages](#zeromq) from event-hooks: `pyzmq`
2865
2900
 
@@ -2890,9 +2925,10 @@ set any of the following environment variables to disable its associated optiona
2890
2925
  | `PRTY_NO_PIL` | disable all [Pillow](https://pypi.org/project/pillow/)-based thumbnail support; will fallback to libvips or ffmpeg |
2891
2926
  | `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
2892
2927
  | `PRTY_NO_PIL_AVIF` | disable Pillow avif support (internal and/or [plugin](https://pypi.org/project/pillow-avif-plugin/)) |
2893
- | `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pyheif-pillow-opener/) |
2928
+ | `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pillow-heif/) |
2894
2929
  | `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow |
2895
2930
  | `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows |
2931
+ | `PRTY_NO_RAW` | disable all [rawpy](https://pypi.org/project/rawpy/)-based thumbnail support for RAW images |
2896
2932
  | `PRTY_NO_VIPS` | disable all [libvips](https://pypi.org/project/pyvips/)-based thumbnail support; will fallback to Pillow or ffmpeg |
2897
2933
 
2898
2934
  example: `PRTY_NO_PIL=1 python3 copyparty-sfx.py`
@@ -90,7 +90,9 @@ made in Norway 🇳🇴
90
90
  * [upload events](#upload-events) - the older, more powerful approach ([examples](./bin/mtag/))
91
91
  * [handlers](#handlers) - redefine behavior with plugins ([examples](./bin/handlers/))
92
92
  * [ip auth](#ip-auth) - autologin based on IP range (CIDR)
93
+ * [restrict to ip](#restrict-to-ip) - limit a user to certain IP ranges (CIDR)
93
94
  * [identity providers](#identity-providers) - replace copyparty passwords with oauth and such
95
+ * [generic header auth](#generic-header-auth) - other ways to auth by header
94
96
  * [user-changeable passwords](#user-changeable-passwords) - if permitted, users can change their own passwords
95
97
  * [using the cloud as storage](#using-the-cloud-as-storage) - connecting to an aws s3 bucket and similar
96
98
  * [hiding from google](#hiding-from-google) - tell search engines you don't wanna be indexed
@@ -266,6 +268,7 @@ also see [comparison to similar software](./docs/versus.md)
266
268
  * ☑ realtime streaming of growing files (logfiles and such)
267
269
  * ☑ [thumbnails](#thumbnails)
268
270
  * ☑ ...of images using Pillow, pyvips, or FFmpeg
271
+ * ☑ ...of RAW images using rawpy
269
272
  * ☑ ...of videos using FFmpeg
270
273
  * ☑ ...of audio (spectrograms) using FFmpeg
271
274
  * ☑ cache eviction (max-age; maybe max-size eventually)
@@ -512,6 +515,8 @@ examples:
512
515
  * replacing the `g` permission with `wg` would let anonymous users upload files, but not see the required filekey to access it
513
516
  * replacing the `g` permission with `wG` would let anonymous users upload files, receiving a working direct link in return
514
517
 
518
+ if you want to grant access to all users who are logged in, the group `acct` will always contain all known users, so for example `-v /mnt/music:music:r,@acct`
519
+
515
520
  anyone trying to bruteforce a password gets banned according to `--ban-pw`; default is 24h ban for 9 failed attempts in 1 hour
516
521
 
517
522
  and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
@@ -537,6 +542,7 @@ and if you want to use config files instead of commandline args (good!) then her
537
542
  accs:
538
543
  r: u1, u2 # only these accounts can read,
539
544
  r: @g1 # (exactly the same, just with a group instead)
545
+ r: @acct # (alternatively, ALL users who are logged in)
540
546
  rw: u3 # and only u3 can read-write
541
547
 
542
548
  [/inc]
@@ -1892,6 +1898,20 @@ repeat the option to map additional subnets
1892
1898
  **be careful with this one!** if you have a reverseproxy, then you definitely want to make sure you have [real-ip](#real-ip) configured correctly, and it's probably a good idea to nullmap the reverseproxy's IP just in case; so if your reverseproxy is sending requests from `172.24.27.9` then that would be `--ipu=172.24.27.9/32=`
1893
1899
 
1894
1900
 
1901
+ ### restrict to ip
1902
+
1903
+ limit a user to certain IP ranges (CIDR) , using the global-option `--ipr`
1904
+
1905
+ for example, if the user `spartacus` should get rejected if they're not connecting from an IP that starts with `192.168.123` or `172.16`, then you can either specify `--ipr=192.168.123.0/24,172.16.0.0/16=spartacus` as a commandline option, or put this in a config file:
1906
+
1907
+ ```yaml
1908
+ [global]
1909
+ ipr: 192.168.123.0/24,172.16.0.0/16=spartacus
1910
+ ```
1911
+
1912
+ repeat the option to map additional users
1913
+
1914
+
1895
1915
  ## identity providers
1896
1916
 
1897
1917
  replace copyparty passwords with oauth and such
@@ -1911,6 +1931,20 @@ a more complete example of the copyparty configuration options [look like this](
1911
1931
  but if you just want to let users change their own passwords, then you probably want [user-changeable passwords](#user-changeable-passwords) instead
1912
1932
 
1913
1933
 
1934
+ ### generic header auth
1935
+
1936
+ other ways to auth by header
1937
+
1938
+ if you have a middleware which adds a header with a user identifier, for example tailscale's `Tailscale-User-Login: alice.m@forest.net` then you can automatically auth as `alice` by defining that mapping with `--idp-hm-usr '^Tailscale-User-Login^alice.m@forest.net^alice'` or the following config file:
1939
+
1940
+ ```yaml
1941
+ [global]
1942
+ idp-hm-usr: ^Tailscale-User-Login^alice.m@forest.net^alice
1943
+ ```
1944
+
1945
+ repeat the whole `idp-hm-usr` option to add more mappings
1946
+
1947
+
1914
1948
  ## user-changeable passwords
1915
1949
 
1916
1950
  if permitted, users can change their own passwords in the control-panel
@@ -2792,9 +2826,10 @@ enable [music tags](#metadata-from-audio-files):
2792
2826
  enable [thumbnails](#thumbnails) of...
2793
2827
  * **images:** `Pillow` and/or `pyvips` and/or `ffmpeg` (requires py2.7 or py3.5+)
2794
2828
  * **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH`
2795
- * **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler)
2829
+ * **HEIF pictures:** `pyvips` or `ffmpeg` or `pillow-heif`
2796
2830
  * **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin` or pillow v11.3+
2797
2831
  * **JPEG XL pictures:** `pyvips` or `ffmpeg`
2832
+ * **RAW images:** `rawpy`, plus one of `pyvips` or `Pillow` (for some formats)
2798
2833
 
2799
2834
  enable sending [zeromq messages](#zeromq) from event-hooks: `pyzmq`
2800
2835
 
@@ -2825,9 +2860,10 @@ set any of the following environment variables to disable its associated optiona
2825
2860
  | `PRTY_NO_PIL` | disable all [Pillow](https://pypi.org/project/pillow/)-based thumbnail support; will fallback to libvips or ffmpeg |
2826
2861
  | `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
2827
2862
  | `PRTY_NO_PIL_AVIF` | disable Pillow avif support (internal and/or [plugin](https://pypi.org/project/pillow-avif-plugin/)) |
2828
- | `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pyheif-pillow-opener/) |
2863
+ | `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pillow-heif/) |
2829
2864
  | `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow |
2830
2865
  | `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows |
2866
+ | `PRTY_NO_RAW` | disable all [rawpy](https://pypi.org/project/rawpy/)-based thumbnail support for RAW images |
2831
2867
  | `PRTY_NO_VIPS` | disable all [libvips](https://pypi.org/project/pyvips/)-based thumbnail support; will fallback to Pillow or ffmpeg |
2832
2868
 
2833
2869
  example: `PRTY_NO_PIL=1 python3 copyparty-sfx.py`
@@ -428,6 +428,40 @@ def args_from_cfg(cfg_path ) :
428
428
  return ret
429
429
 
430
430
 
431
+ def expand_cfg(argv) :
432
+ if CFG_DEF:
433
+ supp = args_from_cfg(CFG_DEF[0])
434
+ argv = supp + argv
435
+
436
+ n = spins = 0
437
+ while n < len(argv):
438
+ if not n:
439
+ if spins % 1000 == 999:
440
+ t = "still expanding config files... giving up after %d more"
441
+ print(t % (9999 - spins))
442
+ if spins > 9999:
443
+ t = "got stuck expanding config files; do you have a config-file which imports itself? this is where I gave up:\n%r"
444
+ raise Exception(t % (argv[:1000]))
445
+ v1 = argv[n]
446
+ v1v = v1[2:].lstrip("=")
447
+ try:
448
+ v2 = argv[n + 1]
449
+ except:
450
+ v2 = ""
451
+
452
+ if v1 == "-c" and v2 and os.path.isfile(v2):
453
+ argv = argv[:n] + args_from_cfg(v2) + argv[n + 2 :]
454
+ spins += 1
455
+ n = 0
456
+ elif v1.startswith("-c") and v1v and os.path.isfile(v1v):
457
+ argv = argv[:n] + args_from_cfg(v1v) + argv[n + 1 :]
458
+ spins += 1
459
+ n = 0
460
+ else:
461
+ n += 1
462
+ return argv
463
+
464
+
431
465
  def sighandler(sig = None, frame = None) :
432
466
  msg = [""] * 5
433
467
  for th in threading.enumerate():
@@ -601,8 +635,41 @@ def get_sects():
601
635
  if no accounts or volumes are configured,
602
636
  current folder will be read/write for everyone
603
637
 
638
+ the group @acct will always have every user with an account
639
+ (the name of that group can be changed with --grp-all)
640
+
604
641
  consider the config file for more flexible account/volume management,
605
642
  including dynamic reload at runtime (and being more readable w)
643
+
644
+ see \033[32m--help-auth\033[0m for ways to provide the password in requests;
645
+ see \033[32m--help-idp\033[0m for replacing it with SSO and auth-middlewares
646
+ """
647
+ ),
648
+ ],
649
+ [
650
+ "auth",
651
+ "how to login from a client",
652
+ dedent(
653
+ """
654
+ different ways to provide the password so you become authenticated:
655
+
656
+ login with the ui:
657
+ go to \033[36mhttp://127.0.0.1:3923/?h\033[0m and login there
658
+
659
+ send the password in the '\033[36mPW\033[0m' http-header:
660
+ \033[36mPW: \033[35mhunter2\033[0m
661
+ or if you have \033[33m--accounts\033[0m enabled,
662
+ \033[36mPW: \033[35med:hunter2\033[0m
663
+
664
+ send the password in the URL itself:
665
+ \033[36mhttp://127.0.0.1:3923/\033[35m?pw=hunter2\033[0m
666
+ or if you have \033[33m--accounts\033[0m enabled,
667
+ \033[36mhttp://127.0.0.1:3923/\033[35m?pw=ed:hunter2\033[0m
668
+
669
+ use basic-authentication:
670
+ \033[36mhttp://\033[35med:hunter2\033[36m@127.0.0.1:3923/\033[0m
671
+ which should be the same as this header:
672
+ \033[36mAuthorization: Basic \033[35mZWQ6aHVudGVyMg==\033[0m
606
673
  """
607
674
  ),
608
675
  ],
@@ -754,6 +821,36 @@ def get_sects():
754
821
  the upload speed can easily drop to 10% for small files)"""
755
822
  ),
756
823
  ],
824
+ [
825
+ "idp",
826
+ "replacing the login system with fancy middleware",
827
+ dedent(
828
+ """
829
+ if you already have a centralized service which handles
830
+ user-authentication for other services already, you can
831
+ integrate copyparty with that for automatic login
832
+
833
+ if the middleware is providing the username in an http-header
834
+ named '\033[35mtheUsername\033[0m' then do this: \033[36m--idp-h-usr theUsername\033[0m
835
+
836
+ if the middleware is providing a list of groups in the header
837
+ named '\033[35mtheGroups\033[0m' then do this: \033[36m--idp-h-grp theGroup\033[0m
838
+
839
+ if the list of groups is separated by '\033[35m%\033[0m' then \033[36m--idp-gsep %\033[0m
840
+
841
+ if the middleware is providing a header named '\033[35mAccount\033[0m'
842
+ and the value is '\033[35malice@forest.net\033[0m' but the username is
843
+ actually '\033[35mmarisa\033[0m' then do this for each user:
844
+ \033[36m--idp-hm-usr ^Account^alice@forest.net^marisa\033[0m
845
+ (the separator '\033[35m^\033[0m' can be any character)
846
+
847
+ make ABSOLUTELY SURE that the header can only be set by your
848
+ middleware and not by clients! and, as an extra precaution,
849
+ send a header named '\033[36mfinalmasterspark\033[0m' (a secret keyword)
850
+ and then \033[36m--idp-h-key finalmasterspark\033[0m to require that
851
+ """
852
+ ),
853
+ ],
757
854
  [
758
855
  "urlform",
759
856
  "how to handle url-form POSTs",
@@ -1011,14 +1108,15 @@ def add_general(ap, nc, srvname):
1011
1108
 
1012
1109
  def add_qr(ap, tty):
1013
1110
  ap2 = ap.add_argument_group("qr options")
1014
- ap2.add_argument("--qr", action="store_true", help="show http:// QR-code on startup")
1015
- ap2.add_argument("--qrs", action="store_true", help="show https:// QR-code on startup")
1111
+ ap2.add_argument("--qr", action="store_true", help="show QR-code on startup")
1112
+ ap2.add_argument("--qrs", action="store_true", help="change the QR-code URL to https://")
1016
1113
  ap2.add_argument("--qrl", metavar="PATH", type=u, default="", help="location to include in the url, for example [\033[32mpriv/?pw=hunter2\033[0m]")
1017
1114
  ap2.add_argument("--qri", metavar="PREFIX", type=u, default="", help="select IP which starts with \033[33mPREFIX\033[0m; [\033[32m.\033[0m] to force default IP when mDNS URL would have been used instead")
1018
- ap2.add_argument("--qr-fg", metavar="COLOR", type=int, default=0 if tty else 16, help="foreground; try [\033[32m0\033[0m] if the qr-code is unreadable")
1115
+ ap2.add_argument("--qr-fg", metavar="COLOR", type=int, default=0 if tty else 16, help="foreground; try [\033[32m0\033[0m] or [\033[32m-1\033[0m] if the qr-code is unreadable")
1019
1116
  ap2.add_argument("--qr-bg", metavar="COLOR", type=int, default=229, help="background (white=255)")
1020
1117
  ap2.add_argument("--qrp", metavar="CELLS", type=int, default=4, help="padding (spec says 4 or more, but 1 is usually fine)")
1021
1118
  ap2.add_argument("--qrz", metavar="N", type=int, default=0, help="[\033[32m1\033[0m]=1x, [\033[32m2\033[0m]=2x, [\033[32m0\033[0m]=auto (try [\033[32m2\033[0m] on broken fonts)")
1119
+ ap2.add_argument("--qr-pin", metavar="N", type=int, default=0, help="sticky/pin the qr-code to always stay on-screen; [\033[32m0\033[0m]=disabled, [\033[32m1\033[0m]=with-url, [\033[32m2\033[0m]=just-qr")
1022
1120
 
1023
1121
 
1024
1122
  def add_fs(ap):
@@ -1048,6 +1146,7 @@ def add_upload(ap):
1048
1146
  ap2.add_argument("--put-ck", metavar="ALG", type=u, default="sha512", help="default checksum-hasher for PUT/WebDAV uploads: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=put_ck)")
1049
1147
  ap2.add_argument("--bup-ck", metavar="ALG", type=u, default="sha512", help="default checksum-hasher for bup/basic-uploader: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=bup_ck)")
1050
1148
  ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled, default=12h")
1149
+ ap2.add_argument("--unp-who", metavar="NUM", type=int, default=1, help="clients can undo recent uploads by using the unpost tab (requires \033[33m-e2d\033[0m). [\033[32m0\033[0m] = never allowed (disable feature), [\033[32m1\033[0m] = allow if client has the same IP as the upload AND is using the same account, [\033[32m2\033[0m] = just check the IP, [\033[32m3\033[0m] = just check account-name (volflag=unp_who)")
1051
1150
  ap2.add_argument("--u2abort", metavar="NUM", type=int, default=1, help="clients can abort incomplete uploads by using the unpost tab (requires \033[33m-e2d\033[0m). [\033[32m0\033[0m] = never allowed (disable feature), [\033[32m1\033[0m] = allow if client has the same IP as the upload AND is using the same account, [\033[32m2\033[0m] = just check the IP, [\033[32m3\033[0m] = just check account-name (volflag=u2abort)")
1052
1151
  ap2.add_argument("--blank-wt", metavar="SEC", type=int, default=300, help="file write grace period (any client can write to a blank file last-modified more recently than \033[33mSEC\033[0m seconds ago)")
1053
1152
  ap2.add_argument("--reg-cap", metavar="N", type=int, default=38400, help="max number of uploads to keep in memory when running without \033[33m-e2d\033[0m; roughly 1 MiB RAM per 600")
@@ -1141,7 +1240,8 @@ def add_auth(ap):
1141
1240
  idp_db = os.path.join(E.cfg, "idp.db")
1142
1241
  ses_db = os.path.join(E.cfg, "sessions.db")
1143
1242
  ap2 = ap.add_argument_group("IdP / identity provider / user authentication options")
1144
- ap2.add_argument("--idp-h-usr", metavar="HN", type=u, default="", help="bypass the copyparty authentication checks if the request-header \033[33mHN\033[0m contains a username to associate the request with (for use with authentik/oauth/...)\n\033[1;31mWARNING:\033[0m if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy")
1243
+ ap2.add_argument("--idp-h-usr", metavar="HN", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mHN\033[0m contains a username to associate the request with (for use with authentik/oauth/...)\n\033[1;31mWARNING:\033[0m if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy")
1244
+ ap2.add_argument("--idp-hm-usr", metavar="TXT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mHN\033[0m is provided, and its value exists in a mapping defined by this option; see --help-idp")
1145
1245
  ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control")
1146
1246
  ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present")
1147
1247
  ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m")
@@ -1154,7 +1254,11 @@ def add_auth(ap):
1154
1254
  ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)")
1155
1255
  ap2.add_argument("--ses-len", metavar="CHARS", type=int, default=20, help="session key length; default is 120 bits ((20//4)*4*6)")
1156
1256
  ap2.add_argument("--no-ses", action="store_true", help="disable sessions; use plaintext passwords in cookies")
1257
+ ap2.add_argument("--grp-all", metavar="NAME", type=u, default="acct", help="the name of the auto-generated group which contains every username which is known")
1157
1258
  ap2.add_argument("--ipu", metavar="CIDR=USR", type=u, action="append", help="\033[34mREPEATABLE:\033[0m users with IP matching \033[33mCIDR\033[0m are auto-authenticated as username \033[33mUSR\033[0m; example: [\033[32m172.16.24.0/24=dave]")
1259
+ ap2.add_argument("--ipr", metavar="CIDR=USR", type=u, action="append", help="\033[34mREPEATABLE:\033[0m username \033[33mUSR\033[0m can only connect from an IP matching one or more \033[33mCIDR\033[0m (comma-sep.); example: [\033[32m192.168.123.0/24,172.16.0.0/16=dave]")
1260
+ ap2.add_argument("--have-idp-hdrs", type=u, default="", help=argparse.SUPPRESS)
1261
+ ap2.add_argument("--have-ipu-or-ipr", type=u, default="", help=argparse.SUPPRESS)
1158
1262
 
1159
1263
 
1160
1264
  def add_chpw(ap):
@@ -1310,6 +1414,7 @@ def add_optouts(ap):
1310
1414
  ap2.add_argument("--no-del", action="store_true", help="disable delete operations")
1311
1415
  ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations")
1312
1416
  ap2.add_argument("--no-cp", action="store_true", help="disable copy operations")
1417
+ ap2.add_argument("--no-fs-abrt", action="store_true", help="disable ability to abort ongoing copy/move")
1313
1418
  ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show \033[33m--name\033[0m in <title>")
1314
1419
  ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
1315
1420
  ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
@@ -1331,7 +1436,7 @@ def add_optouts(ap):
1331
1436
  def add_safety(ap):
1332
1437
  ap2 = ap.add_argument_group("safety options")
1333
1438
  ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
1334
- ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --vague-403 -nih")
1439
+ ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --dav-auth --vague-403 -nih")
1335
1440
  ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r")
1336
1441
  ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, default="", help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m (see \033[33m--help-ls\033[0m); example [\033[32m**,*,ln,p,r\033[0m]")
1337
1442
  ap2.add_argument("--xvol", action="store_true", help="never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access (volflag=xvol)")
@@ -1353,6 +1458,8 @@ def add_safety(ap):
1353
1458
  ap2.add_argument("--sus-urls", metavar="R", type=u, default=r"\.php$|(^|/)wp-(admin|content|includes)/", help="URLs which are considered sus / eligible for banning; disable with blank or [\033[32mno\033[0m]")
1354
1459
  ap2.add_argument("--nonsus-urls", metavar="R", type=u, default=r"^(favicon\.ico|robots\.txt)$|^apple-touch-icon|^\.well-known", help="harmless URLs ignored from 404-bans; disable with blank or [\033[32mno\033[0m]")
1355
1460
  ap2.add_argument("--early-ban", action="store_true", help="if a client is banned, reject its connection as soon as possible; not a good idea to enable when proxied behind cloudflare since it could ban your reverse-proxy")
1461
+ ap2.add_argument("--cookie-nmax", metavar="N", type=int, default=50, help="reject HTTP-request from client if they send more than N cookies")
1462
+ ap2.add_argument("--cookie-cmax", metavar="N", type=int, default=8192, help="reject HTTP-request from client if more than N characters in Cookie header")
1356
1463
  ap2.add_argument("--aclose", metavar="MIN", type=int, default=10, help="if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for \033[33mMIN\033[0m minutes (and also kill its active connections) -- disable with 0")
1357
1464
  ap2.add_argument("--loris", metavar="B", type=int, default=60, help="if a client maxes out the server connection limit without sending headers, ban it for \033[33mB\033[0m minutes; disable with [\033[32m0\033[0m]")
1358
1465
  ap2.add_argument("--acao", metavar="V[,V]", type=u, default="*", help="Access-Control-Allow-Origin; list of origins (domains/IPs without port) to accept requests from; [\033[32mhttps://1.2.3.4\033[0m]. Default [\033[32m*\033[0m] allows requests from all sites but removes cookies and http-auth; only ?pw=hunter2 survives")
@@ -1419,11 +1526,12 @@ def add_thumbnail(ap):
1419
1526
  ap2.add_argument("--no-athumb", action="store_true", help="disable audio thumbnails (spectrograms) (volflag=dathumb)")
1420
1527
  ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res (volflag=thsize)")
1421
1528
  ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
1422
- ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60.0, help="conversion timeout in seconds (volflag=convt)")
1529
+ ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60.0, help="convert-to-image timeout in seconds (volflag=convt)")
1530
+ ap2.add_argument("--ac-convt", metavar="SEC", type=float, default=150.0, help="convert-to-audio timeout in seconds (volflag=aconvt)")
1423
1531
  ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=th_ram, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
1424
1532
  ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32my\033[0m]=crop, [\033[32mn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
1425
1533
  ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32my\033[0m]=yes, [\033[32mn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
1426
- ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
1534
+ ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,raw,ff", help="image decoders, in order of preference")
1427
1535
  ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
1428
1536
  ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
1429
1537
  ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg output for video thumbs (avoids issues on some FFmpeg builds)")
@@ -1432,16 +1540,19 @@ def add_thumbnail(ap):
1432
1540
  ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
1433
1541
  ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age -- folders which haven't been poked for longer than \033[33m--th-poke\033[0m seconds will get deleted every \033[33m--th-clean\033[0m seconds")
1434
1542
  ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern")
1543
+ ap2.add_argument("--th-spec-p", metavar="N", type=u, default=1, help="for music, do spectrograms or embedded coverart? [\033[32m0\033[0m]=only-art, [\033[32m1\033[0m]=prefer-art, [\033[32m2\033[0m]=only-spec")
1435
1544
  # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
1436
1545
  # https://github.com/libvips/libvips
1546
+ # https://stackoverflow.com/a/47612661
1437
1547
  # ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
1438
- ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
1439
- ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
1440
- ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
1548
+ ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
1549
+ ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
1550
+ ap2.add_argument("--th-r-raw", metavar="T,T", type=u, default="arw,cr2,cr3,crw,dcr,dng,erf,k25,kdc,mrw,nef,orf,pef,raf,raw,sr2,srf,x3f", help="image formats to decode using rawpy")
1551
+ ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
1441
1552
  ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
1442
1553
  ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
1443
1554
  ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)")
1444
- ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz", help="audio/image formats to decompress before passing to ffmpeg")
1555
+ ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub", help="audio/image formats to decompress before passing to ffmpeg")
1445
1556
 
1446
1557
 
1447
1558
  def add_transcoding(ap):
@@ -1553,13 +1664,14 @@ def add_og(ap):
1553
1664
 
1554
1665
 
1555
1666
  def add_ui(ap, retry):
1667
+ THEMES = 10
1556
1668
  ap2 = ap.add_argument_group("ui options")
1557
1669
  ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
1558
1670
  ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
1559
1671
  ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC")
1560
- ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor chi\033[0m")
1561
- ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..7)")
1562
- ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
1672
+ ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language, for example \033[32meng\033[0m / \033[32mnor\033[0m / ...")
1673
+ ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..%d)" % (THEMES - 1,))
1674
+ ap2.add_argument("--themes", metavar="NUM", type=int, default=THEMES, help="number of themes installed")
1563
1675
  ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
1564
1676
  ap2.add_argument("--sort", metavar="C,C,C", type=u, default="href", help="default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: \033[32mhref -href ext sz ts tags/Album tags/.tn\033[0m (volflag=sort)")
1565
1677
  ap2.add_argument("--nsort", action="store_true", help="default-enable natural sort of filenames with leading numbers (volflag=nsort)")
@@ -1760,20 +1872,7 @@ def main(argv = None) :
1760
1872
 
1761
1873
  ensure_webdeps()
1762
1874
 
1763
- if CFG_DEF:
1764
- supp = args_from_cfg(CFG_DEF[0])
1765
- argv.extend(supp)
1766
-
1767
- for k, v in zip(argv[1:], argv[2:]):
1768
- if k == "-c" and os.path.isfile(v):
1769
- supp = args_from_cfg(v)
1770
- argv.extend(supp)
1771
-
1772
- for k in argv[1:]:
1773
- v = k[2:]
1774
- if k.startswith("-c") and v and os.path.isfile(v):
1775
- supp = args_from_cfg(v)
1776
- argv.extend(supp)
1875
+ argv = expand_cfg(argv)
1777
1876
 
1778
1877
  deprecated = [
1779
1878
  ("--salt", "--warksalt"),
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 19, 1)
3
+ VERSION = (1, 19, 2)
4
4
  CODENAME = "usernames"
5
- BUILD_DT = (2025, 8, 10)
5
+ BUILD_DT = (2025, 8, 17)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
@@ -874,6 +874,15 @@ class VFS(object):
874
874
  return None
875
875
 
876
876
  if "xvol" in self.flags:
877
+ self_ap = self.realpath + os.sep
878
+ if aps.startswith(self_ap):
879
+ vp = aps[len(self_ap) :]
880
+ if ANYWIN:
881
+ vp = vp.replace(os.sep, "/")
882
+ vn2, _ = self._find(vp)
883
+ if self == vn2:
884
+ return self
885
+
877
886
  all_aps = self.shr_all_aps or self.root.all_aps
878
887
 
879
888
  for vap, vns in all_aps:
@@ -1091,6 +1100,9 @@ class AuthSrv(object):
1091
1100
  if rejected:
1092
1101
  continue
1093
1102
 
1103
+ if gn == self.args.grp_all:
1104
+ gn = ""
1105
+
1094
1106
  # if ap/vp has a user/group placeholder, make sure to keep
1095
1107
  # track so the same user/group is mapped when setting perms;
1096
1108
  # otherwise clear un/gn to indicate it's a regular volume
@@ -1200,6 +1212,7 @@ class AuthSrv(object):
1200
1212
  self.load_idp_db(bool(self.idp_accs))
1201
1213
  ret = {un: gns[:] for un, gns in self.idp_accs.items()}
1202
1214
  ret.update({zs: [""] for zs in acct if zs not in ret})
1215
+ grps[self.args.grp_all] = list(ret.keys())
1203
1216
  for gn, uns in grps.items():
1204
1217
  for un in uns:
1205
1218
  try:
@@ -1677,6 +1690,9 @@ class AuthSrv(object):
1677
1690
  self.log("\n{0}\n{1}{0}".format(t, "\n".join(slns)))
1678
1691
  raise
1679
1692
 
1693
+ self.args.have_idp_hdrs = bool(self.args.idp_h_usr or self.args.idp_hm_usr)
1694
+ self.args.have_ipu_or_ipr = bool(self.args.ipu or self.args.ipr)
1695
+
1680
1696
  self.setup_pwhash(acct)
1681
1697
  defpw = acct.copy()
1682
1698
  self.setup_chpw(acct)
@@ -1689,7 +1705,7 @@ class AuthSrv(object):
1689
1705
 
1690
1706
  mount = cased
1691
1707
 
1692
- if not mount and not self.args.idp_h_usr:
1708
+ if not mount and not self.args.have_idp_hdrs:
1693
1709
  # -h says our defaults are CWD at root and read/write for everyone
1694
1710
  axs = AXS(["*"], ["*"], None, None)
1695
1711
  ehint = ""
@@ -1861,7 +1877,7 @@ class AuthSrv(object):
1861
1877
 
1862
1878
  if missing_users:
1863
1879
  zs = ", ".join(k for k in sorted(missing_users))
1864
- if self.args.idp_h_usr:
1880
+ if self.args.have_idp_hdrs:
1865
1881
  t = "the following users are unknown, and assumed to come from IdP: "
1866
1882
  self.log(t + zs, c=6)
1867
1883
  else:
@@ -1872,6 +1888,16 @@ class AuthSrv(object):
1872
1888
  if LEELOO_DALLAS in all_users:
1873
1889
  raise Exception("sorry, reserved username: " + LEELOO_DALLAS)
1874
1890
 
1891
+ zsl = []
1892
+ for usr in list(acct)[:]:
1893
+ zs = acct[usr].strip()
1894
+ if not zs:
1895
+ zs = ub64enc(os.urandom(48)).decode("ascii")
1896
+ zsl.append(usr)
1897
+ acct[usr] = zs
1898
+ if zsl:
1899
+ self.log("generated random passwords for users %r" % (zsl,), 6)
1900
+
1875
1901
  seenpwds = {}
1876
1902
  for usr, pwd in acct.items():
1877
1903
  if pwd in seenpwds:
@@ -2192,12 +2218,12 @@ class AuthSrv(object):
2192
2218
  if vf not in vol.flags:
2193
2219
  vol.flags[vf] = getattr(self.args, ga)
2194
2220
 
2195
- zs = "forget_ip gid nrand tail_who u2abort u2ow uid ups_who zip_who"
2221
+ zs = "forget_ip gid nrand tail_who th_spec_p u2abort u2ow uid unp_who ups_who zip_who"
2196
2222
  for k in zs.split():
2197
2223
  if k in vol.flags:
2198
2224
  vol.flags[k] = int(vol.flags[k])
2199
2225
 
2200
- zs = "convt tail_fd tail_rate tail_tmax"
2226
+ zs = "aconvt convt tail_fd tail_rate tail_tmax"
2201
2227
  for k in zs.split():
2202
2228
  if k in vol.flags:
2203
2229
  vol.flags[k] = float(vol.flags[k])
@@ -2528,7 +2554,7 @@ class AuthSrv(object):
2528
2554
  if not self.args.no_voldump:
2529
2555
  self.log(t)
2530
2556
 
2531
- if have_e2d or self.args.idp_h_usr:
2557
+ if have_e2d or self.args.have_idp_hdrs:
2532
2558
  t = self.chk_sqlite_threadsafe()
2533
2559
  if t:
2534
2560
  self.log("\n\033[{}\033[0m\n".format(t))
@@ -2817,7 +2843,7 @@ class AuthSrv(object):
2817
2843
  def load_idp_db(self, quiet=False) :
2818
2844
  # mutex me
2819
2845
  level = self.args.idp_store
2820
- if level < 2 or not self.args.idp_h_usr:
2846
+ if level < 2 or not self.args.have_idp_hdrs:
2821
2847
  return
2822
2848
 
2823
2849
 
@@ -2872,7 +2898,7 @@ class AuthSrv(object):
2872
2898
  n = []
2873
2899
  q = "insert into us values (?,?,?)"
2874
2900
  accs = list(self.acct)
2875
- if self.args.idp_h_usr and self.args.idp_cookie:
2901
+ if self.args.have_idp_hdrs and self.args.idp_cookie:
2876
2902
  accs.extend(self.idp_accs.keys())
2877
2903
  for uname in accs:
2878
2904
  if uname not in ases: