copyparty 1.18.2__tar.gz → 1.18.4__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.18.2 → copyparty-1.18.4}/PKG-INFO +8 -2
  2. {copyparty-1.18.2 → copyparty-1.18.4}/README.md +7 -1
  3. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/__main__.py +41 -1
  4. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/__version__.py +2 -2
  5. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/authsrv.py +125 -17
  6. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/bos/bos.py +18 -6
  7. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/cfg.py +5 -0
  8. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/ftpd.py +9 -4
  9. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/httpcli.py +34 -19
  10. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/smbd.py +2 -2
  11. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/svchub.py +5 -1
  12. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/tcpsrv.py +1 -1
  13. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/tftpd.py +6 -2
  14. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/th_srv.py +3 -2
  15. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/up2k.py +40 -11
  16. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/util.py +17 -3
  17. copyparty-1.18.4/copyparty/web/baguettebox.js.gz +0 -0
  18. copyparty-1.18.4/copyparty/web/browser.css.gz +0 -0
  19. copyparty-1.18.4/copyparty/web/browser.js.gz +0 -0
  20. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty.egg-info/PKG-INFO +8 -2
  21. copyparty-1.18.2/copyparty/web/baguettebox.js.gz +0 -0
  22. copyparty-1.18.2/copyparty/web/browser.css.gz +0 -0
  23. copyparty-1.18.2/copyparty/web/browser.js.gz +0 -0
  24. {copyparty-1.18.2 → copyparty-1.18.4}/LICENSE +0 -0
  25. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/__init__.py +0 -0
  26. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/bos/__init__.py +0 -0
  27. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/bos/path.py +0 -0
  28. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/broker_mp.py +0 -0
  29. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/broker_mpw.py +0 -0
  30. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/broker_thr.py +0 -0
  31. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/broker_util.py +0 -0
  32. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/cert.py +0 -0
  33. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/dxml.py +0 -0
  34. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/fsutil.py +0 -0
  35. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/httpconn.py +0 -0
  36. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/httpsrv.py +0 -0
  37. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/ico.py +0 -0
  38. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/mdns.py +0 -0
  39. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/metrics.py +0 -0
  40. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/mtag.py +0 -0
  41. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/multicast.py +0 -0
  42. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/pwhash.py +0 -0
  43. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/res/COPYING.txt +0 -0
  44. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/res/__init__.py +0 -0
  45. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/res/insecure.pem +0 -0
  46. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/ssdp.py +0 -0
  47. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/star.py +0 -0
  48. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/__init__.py +0 -0
  49. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/dnslib/__init__.py +0 -0
  50. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/dnslib/bimap.py +0 -0
  51. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/dnslib/bit.py +0 -0
  52. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/dnslib/buffer.py +0 -0
  53. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/dnslib/dns.py +0 -0
  54. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/dnslib/label.py +0 -0
  55. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/dnslib/lex.py +0 -0
  56. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/dnslib/ranges.py +0 -0
  57. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/ifaddr/__init__.py +0 -0
  58. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/ifaddr/_posix.py +0 -0
  59. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/ifaddr/_shared.py +0 -0
  60. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/ifaddr/_win32.py +0 -0
  61. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/qrcodegen.py +0 -0
  62. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/stolen/surrogateescape.py +0 -0
  63. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/sutil.py +0 -0
  64. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/szip.py +0 -0
  65. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/th_cli.py +0 -0
  66. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/u2idx.py +0 -0
  67. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/a/__init__.py +0 -0
  68. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/a/partyfuse.py +0 -0
  69. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/a/u2c.py +0 -0
  70. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/a/webdav-cfg.bat +0 -0
  71. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/browser.html +0 -0
  72. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/browser2.html +0 -0
  73. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/cf.html +0 -0
  74. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/dbg-audio.js.gz +0 -0
  75. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/dd/2.png +0 -0
  76. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/dd/3.png +0 -0
  77. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/dd/4.png +0 -0
  78. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/dd/5.png +0 -0
  79. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/dd/__init__.py +0 -0
  80. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/__init__.py +0 -0
  81. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/busy.mp3.gz +0 -0
  82. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/easymde.css.gz +0 -0
  83. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/easymde.js.gz +0 -0
  84. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/fuse.py +0 -0
  85. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/marked.js.gz +0 -0
  86. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/mini-fa.css.gz +0 -0
  87. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/mini-fa.woff +0 -0
  88. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/prism.css.gz +0 -0
  89. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/prism.js.gz +0 -0
  90. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/prismd.css.gz +0 -0
  91. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/scp.woff2 +0 -0
  92. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  93. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  94. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/idp.html +0 -0
  95. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/md.css.gz +0 -0
  96. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/md.html +0 -0
  97. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/md.js.gz +0 -0
  98. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/md2.css.gz +0 -0
  99. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/md2.js.gz +0 -0
  100. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/mde.css.gz +0 -0
  101. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/mde.html +0 -0
  102. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/mde.js.gz +0 -0
  103. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/msg.css.gz +0 -0
  104. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/msg.html +0 -0
  105. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/rups.css.gz +0 -0
  106. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/rups.html +0 -0
  107. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/rups.js.gz +0 -0
  108. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/shares.css.gz +0 -0
  109. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/shares.html +0 -0
  110. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/shares.js.gz +0 -0
  111. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/splash.css.gz +0 -0
  112. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/splash.html +0 -0
  113. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/splash.js.gz +0 -0
  114. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/svcs.html +0 -0
  115. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/svcs.js.gz +0 -0
  116. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/ui.css.gz +0 -0
  117. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/up2k.js.gz +0 -0
  118. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/util.js.gz +0 -0
  119. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty/web/w.hash.js.gz +0 -0
  120. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty.egg-info/SOURCES.txt +0 -0
  121. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty.egg-info/dependency_links.txt +0 -0
  122. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty.egg-info/entry_points.txt +0 -0
  123. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty.egg-info/requires.txt +0 -0
  124. {copyparty-1.18.2 → copyparty-1.18.4}/copyparty.egg-info/top_level.txt +0 -0
  125. {copyparty-1.18.2 → copyparty-1.18.4}/pyproject.toml +0 -0
  126. {copyparty-1.18.2 → copyparty-1.18.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.18.2
3
+ Version: 1.18.4
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
@@ -1663,7 +1663,7 @@ config file example:
1663
1663
  w: * # anyone can upload here
1664
1664
  rw: ed # only user "ed" can read-write
1665
1665
  flags:
1666
- e2ds: # filesystem indexing is required for many of these:
1666
+ e2ds # filesystem indexing is required for many of these:
1667
1667
  sz: 1k-3m # accept upload only if filesize in this range
1668
1668
  df: 4g # free disk space cannot go lower than this
1669
1669
  vmaxb: 1g # volume can never exceed 1 GiB
@@ -1720,6 +1720,8 @@ this can instead be kept in a single place using the `--hist` argument, or the `
1720
1720
 
1721
1721
  by default, the per-volume `up2k.db` sqlite3-database for `-e2d` and `-e2t` is stored next to the thumbnails according to the `--hist` option, but the global-option `--dbpath` and/or volflag `dbpath` can be used to put the database somewhere else
1722
1722
 
1723
+ if your storage backend is unreliable (NFS or bad HDDs), you can specify one or more "landmarks" to look for before doing anything database-related. A landmark is a file which is always expected to exist inside the volume. This avoids spurious filesystem rescans in the event of an outage. One line per landmark (see example below)
1724
+
1723
1725
  note:
1724
1726
  * putting the hist-folders on an SSD is strongly recommended for performance
1725
1727
  * markdown edits are always stored in a local `.hist` subdirectory
@@ -1737,6 +1739,8 @@ config file example:
1737
1739
  flags:
1738
1740
  hist: - # restore the default (/mnt/nas/pics/.hist/)
1739
1741
  hist: /mnt/nas/cache/pics/ # can be absolute path
1742
+ landmark: me.jpg # /mnt/nas/pics/me.jpg must be readable to enable db
1743
+ landmark: info/a.txt^=ok # and this textfile must start with "ok"
1740
1744
  ```
1741
1745
 
1742
1746
 
@@ -2422,8 +2426,10 @@ TLDR: yes
2422
2426
  | send message | yep | yep | yep | yep | yep | yep | yep | yep |
2423
2427
  | set sort order | - | yep | yep | yep | yep | yep | yep | yep |
2424
2428
  | zip selection | - | yep | yep | yep | yep | yep | yep | yep |
2429
+ | file search | - | yep | yep | yep | yep | yep | yep | yep |
2425
2430
  | file rename | - | yep | yep | yep | yep | yep | yep | yep |
2426
2431
  | file cut/paste | - | yep | yep | yep | yep | yep | yep | yep |
2432
+ | unpost uploads | - | - | yep | yep | yep | yep | yep | yep |
2427
2433
  | navpane | - | yep | yep | yep | yep | yep | yep | yep |
2428
2434
  | image viewer | - | yep | yep | yep | yep | yep | yep | yep |
2429
2435
  | video player | - | yep | yep | yep | yep | yep | yep | yep |
@@ -1605,7 +1605,7 @@ config file example:
1605
1605
  w: * # anyone can upload here
1606
1606
  rw: ed # only user "ed" can read-write
1607
1607
  flags:
1608
- e2ds: # filesystem indexing is required for many of these:
1608
+ e2ds # filesystem indexing is required for many of these:
1609
1609
  sz: 1k-3m # accept upload only if filesize in this range
1610
1610
  df: 4g # free disk space cannot go lower than this
1611
1611
  vmaxb: 1g # volume can never exceed 1 GiB
@@ -1662,6 +1662,8 @@ this can instead be kept in a single place using the `--hist` argument, or the `
1662
1662
 
1663
1663
  by default, the per-volume `up2k.db` sqlite3-database for `-e2d` and `-e2t` is stored next to the thumbnails according to the `--hist` option, but the global-option `--dbpath` and/or volflag `dbpath` can be used to put the database somewhere else
1664
1664
 
1665
+ if your storage backend is unreliable (NFS or bad HDDs), you can specify one or more "landmarks" to look for before doing anything database-related. A landmark is a file which is always expected to exist inside the volume. This avoids spurious filesystem rescans in the event of an outage. One line per landmark (see example below)
1666
+
1665
1667
  note:
1666
1668
  * putting the hist-folders on an SSD is strongly recommended for performance
1667
1669
  * markdown edits are always stored in a local `.hist` subdirectory
@@ -1679,6 +1681,8 @@ config file example:
1679
1681
  flags:
1680
1682
  hist: - # restore the default (/mnt/nas/pics/.hist/)
1681
1683
  hist: /mnt/nas/cache/pics/ # can be absolute path
1684
+ landmark: me.jpg # /mnt/nas/pics/me.jpg must be readable to enable db
1685
+ landmark: info/a.txt^=ok # and this textfile must start with "ok"
1682
1686
  ```
1683
1687
 
1684
1688
 
@@ -2364,8 +2368,10 @@ TLDR: yes
2364
2368
  | send message | yep | yep | yep | yep | yep | yep | yep | yep |
2365
2369
  | set sort order | - | yep | yep | yep | yep | yep | yep | yep |
2366
2370
  | zip selection | - | yep | yep | yep | yep | yep | yep | yep |
2371
+ | file search | - | yep | yep | yep | yep | yep | yep | yep |
2367
2372
  | file rename | - | yep | yep | yep | yep | yep | yep | yep |
2368
2373
  | file cut/paste | - | yep | yep | yep | yep | yep | yep | yep |
2374
+ | unpost uploads | - | - | yep | yep | yep | yep | yep | yep |
2369
2375
  | navpane | - | yep | yep | yep | yep | yep | yep | yep |
2370
2376
  | image viewer | - | yep | yep | yep | yep | yep | yep | yep |
2371
2377
  | video player | - | yep | yep | yep | yep | yep | yep | yep |
@@ -855,6 +855,43 @@ def get_sects():
855
855
  """
856
856
  ),
857
857
  ],
858
+ [
859
+ "chmod",
860
+ "file/folder permissions",
861
+ dedent(
862
+ """
863
+ global-option \033[33m--chmod-f\033[0m and volflag \033[33mchmod_f\033[0m specifies the unix-permission to use when creating a new file
864
+
865
+ similarly, \033[33m--chmod-d\033[0m and \033[33mchmod_d\033[0m sets the directory/folder perm
866
+
867
+ the value is a three-digit octal number such as 755, 750, 644, etc.
868
+
869
+ first digit = "User"; permission for the unix-user
870
+ second digit = "Group"; permission for the unix-group
871
+ third digit = "Other"; permission for all other users/groups
872
+
873
+ for files:
874
+ 0 = --- = no access
875
+ 1 = --x = can execute the file as a program
876
+ 2 = -w- = can write
877
+ 3 = -wx = can write and execute
878
+ 4 = r-- = can read
879
+ 5 = r-x = can read and execute
880
+ 6 = rw- = can read and write
881
+ 7 = rwx = can read, write, execute
882
+
883
+ for directories/folders:
884
+ 0 = --- = no access
885
+ 1 = --x = can read files in folder but not list contents
886
+ 2 = -w- = n/a
887
+ 3 = -wx = can create files but not list
888
+ 4 = r-- = can list, but not read/write
889
+ 5 = r-x = can list and read files
890
+ 6 = rw- = n/a
891
+ 7 = rwx = can read, write, list
892
+ """
893
+ ),
894
+ ],
858
895
  [
859
896
  "pwhash",
860
897
  "password hashing",
@@ -1005,6 +1042,8 @@ def add_upload(ap):
1005
1042
  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")
1006
1043
  ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems)")
1007
1044
  ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)")
1045
+ ap2.add_argument("--chmod-f", metavar="UGO", type=u, default="", help="unix file permissions to use when creating files; default is probably 644 (OS-decided), see --help-chmod. Examples: [\033[32m644\033[0m] = owner-RW + all-R, [\033[32m755\033[0m] = owner-RWX + all-RX, [\033[32m777\033[0m] = full-yolo (volflag=chmod_f)")
1046
+ ap2.add_argument("--chmod-d", metavar="UGO", type=u, default="755", help="unix file permissions to use when creating directories; see --help-chmod. Examples: [\033[32m755\033[0m] = owner-RW + all-R, [\033[32m777\033[0m] = full-yolo (volflag=chmod_d)")
1008
1047
  ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)")
1009
1048
  ap2.add_argument("--safe-dedup", metavar="N", type=int, default=50, help="how careful to be when deduplicating files; [\033[32m1\033[0m] = just verify the filesize, [\033[32m50\033[0m] = verify file contents have not been altered (volflag=safededup)")
1010
1049
  ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")
@@ -1041,7 +1080,7 @@ def add_network(ap):
1041
1080
  ap2.add_argument("--rp-loc", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here; example: [\033[32m/foo/bar\033[0m]")
1042
1081
  if ANYWIN:
1043
1082
  ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances")
1044
- else:
1083
+ elif not MACOS:
1045
1084
  ap2.add_argument("--freebind", action="store_true", help="allow listening on IPs which do not yet exist, for example if the network interfaces haven't finished going up. Only makes sense for IPs other than '0.0.0.0', '127.0.0.1', '::', and '::1'. May require running as root (unless net.ipv6.ip_nonlocal_bind)")
1046
1085
  ap2.add_argument("--wr-h-eps", metavar="PATH", type=u, default="", help="write list of listening-on ip:port to textfile at \033[33mPATH\033[0m when http-servers have started")
1047
1086
  ap2.add_argument("--wr-h-aon", metavar="PATH", type=u, default="", help="write list of accessible-on ip:port to textfile at \033[33mPATH\033[0m when http-servers have started")
@@ -1503,6 +1542,7 @@ def add_ui(ap, retry):
1503
1542
  ap2.add_argument("--nsort", action="store_true", help="default-enable natural sort of filenames with leading numbers (volflag=nsort)")
1504
1543
  ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
1505
1544
  ap2.add_argument("--see-dots", action="store_true", help="default-enable seeing dotfiles; only takes effect if user has the necessary permissions")
1545
+ ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)")
1506
1546
  ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
1507
1547
  ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
1508
1548
  ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 18, 2)
3
+ VERSION = (1, 18, 4)
4
4
  CODENAME = "logtail"
5
- BUILD_DT = (2025, 7, 7)
5
+ BUILD_DT = (2025, 7, 25)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
@@ -113,6 +113,8 @@ class Lim(object):
113
113
 
114
114
  self.reg = None # up2k registry
115
115
 
116
+ self.chmod_d = 0o755
117
+
116
118
  self.nups = {} # num tracker
117
119
  self.bups = {} # byte tracker list
118
120
  self.bupc = {} # byte tracker cache
@@ -273,7 +275,7 @@ class Lim(object):
273
275
  if not dirs:
274
276
  # no branches yet; make one
275
277
  sub = os.path.join(path, "0")
276
- bos.mkdir(sub)
278
+ bos.mkdir(sub, self.chmod_d)
277
279
  else:
278
280
  # try newest branch only
279
281
  sub = os.path.join(path, str(dirs[-1]))
@@ -288,7 +290,7 @@ class Lim(object):
288
290
 
289
291
  # make a branch
290
292
  sub = os.path.join(path, str(dirs[-1] + 1))
291
- bos.mkdir(sub)
293
+ bos.mkdir(sub, self.chmod_d)
292
294
  ret = self.dive(sub, lvs - 1)
293
295
  if ret is None:
294
296
  raise Pebkac(500, "rotation bug")
@@ -365,6 +367,7 @@ class VFS(object):
365
367
  self.shr_src = None # source vfs+rem of a share
366
368
  self.shr_files = set() # filenames to include from shr_src
367
369
  self.shr_owner = "" # uname
370
+ self.shr_all_aps = []
368
371
  self.aread = {}
369
372
  self.awrite = {}
370
373
  self.amove = {}
@@ -376,20 +379,20 @@ class VFS(object):
376
379
  self.adot = {}
377
380
  self.js_ls = {}
378
381
  self.js_htm = ""
382
+ self.all_vols = {} # flattened recursive
383
+ self.all_nodes = {} # also jumpvols/shares
379
384
 
380
385
  if realpath:
381
386
  rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
382
387
  vp = vpath + ("/" if vpath else "")
383
388
  self.histpath = os.path.join(realpath, ".hist") # db / thumbcache
384
389
  self.dbpath = self.histpath
385
- self.all_vols = {vpath: self} # flattened recursive
386
- self.all_nodes = {vpath: self} # also jumpvols/shares
387
- self.all_aps = [(rp, self)]
390
+ self.all_vols[vpath] = self
391
+ self.all_nodes[vpath] = self
392
+ self.all_aps = [(rp, [self])]
388
393
  self.all_vps = [(vp, self)]
389
394
  else:
390
395
  self.histpath = self.dbpath = ""
391
- self.all_vols = {}
392
- self.all_nodes = {}
393
396
  self.all_aps = []
394
397
  self.all_vps = []
395
398
 
@@ -417,7 +420,11 @@ class VFS(object):
417
420
  rp = self.realpath
418
421
  rp += "" if rp.endswith(os.sep) else os.sep
419
422
  vp = self.vpath + ("/" if self.vpath else "")
420
- aps.append((rp, self))
423
+ hit = next((x[1] for x in aps if x[0] == rp), None)
424
+ if hit:
425
+ hit.append(self)
426
+ else:
427
+ aps.append((rp, [self]))
421
428
  vps.append((vp, self))
422
429
 
423
430
  for v in self.nodes.values():
@@ -841,9 +848,11 @@ class VFS(object):
841
848
  return None
842
849
 
843
850
  if "xvol" in self.flags:
844
- for vap, vn in self.root.all_aps:
851
+ all_aps = self.shr_all_aps or self.root.all_aps
852
+
853
+ for vap, vns in all_aps:
845
854
  if aps.startswith(vap):
846
- return vn
855
+ return self if self in vns else vns[0]
847
856
 
848
857
  if self.log:
849
858
  self.log("vfs", "xvol: %r" % (ap,), 3)
@@ -852,6 +861,53 @@ class VFS(object):
852
861
 
853
862
  return self
854
863
 
864
+ def check_landmarks(self) :
865
+ if self.dbv:
866
+ return True
867
+
868
+ vps = self.flags.get("landmark") or []
869
+ if not vps:
870
+ return True
871
+
872
+ failed = ""
873
+ for vp in vps:
874
+ if "^=" in vp:
875
+ vp, zs = vp.split("^=", 1)
876
+ expect = zs.encode("utf-8")
877
+ else:
878
+ expect = b""
879
+
880
+ if self.log:
881
+ t = "checking [/%s] landmark [%s]"
882
+ self.log("vfs", t % (self.vpath, vp), 6)
883
+
884
+ ap = "?"
885
+ try:
886
+ ap = self.canonical(vp)
887
+ with open(ap, "rb") as f:
888
+ buf = f.read(4096)
889
+ if not buf.startswith(expect):
890
+ t = "file [%s] does not start with the expected bytes %s"
891
+ failed = t % (ap, expect)
892
+ break
893
+ except Exception as ex:
894
+ t = "%r while trying to read [%s] => [%s]"
895
+ failed = t % (ex, vp, ap)
896
+ break
897
+
898
+ if not failed:
899
+ return True
900
+
901
+ if self.log:
902
+ t = "WARNING: landmark verification failed; %s; will now disable up2k database for volume [/%s]"
903
+ self.log("vfs", t % (failed, self.vpath), 3)
904
+
905
+ for rm in "e2d e2t e2v".split():
906
+ self.flags = {k: v for k, v in self.flags.items() if not k.startswith(rm)}
907
+ self.flags["d2d"] = True
908
+ self.flags["d2t"] = True
909
+ return False
910
+
855
911
 
856
912
  if WINDOWS:
857
913
  re_vol = re.compile(r"^([a-zA-Z]:[\\/][^:]*|[^:]*):([^:]*):(.*)$")
@@ -915,6 +971,9 @@ class AuthSrv(object):
915
971
 
916
972
  yield prev, True
917
973
 
974
+ def vf0(self):
975
+ return {"d2d": True, "tcolor": self.args.tcolor}
976
+
918
977
  def idp_checkin(
919
978
  self, broker , uname , gname
920
979
  ) :
@@ -1481,7 +1540,7 @@ class AuthSrv(object):
1481
1540
  flags[name] = True
1482
1541
  return
1483
1542
 
1484
- zs = "ext_th mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
1543
+ zs = "ext_th landmark mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
1485
1544
  if name not in zs.split():
1486
1545
  if value is True:
1487
1546
  t = "└─add volflag [{}] = {} ({})"
@@ -1620,13 +1679,12 @@ class AuthSrv(object):
1620
1679
  t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw"
1621
1680
  self.log(t, 1)
1622
1681
  axs = AXS()
1623
- vfs = VFS(self.log_func, absreal("."), "", "", axs, {})
1682
+ vfs = VFS(self.log_func, absreal("."), "", "", axs, self.vf0())
1624
1683
  if not axs.uread:
1625
1684
  self.badcfg1 = True
1626
1685
  elif "" not in mount:
1627
1686
  # there's volumes but no root; make root inaccessible
1628
- zsd = {"d2d": True, "tcolor": self.args.tcolor}
1629
- vfs = VFS(self.log_func, "", "", "", AXS(), zsd)
1687
+ vfs = VFS(self.log_func, "", "", "", AXS(), self.vf0())
1630
1688
 
1631
1689
  maxdepth = 0
1632
1690
  for dst in sorted(mount.keys(), key=lambda x: (x.count("/"), len(x))):
@@ -1674,8 +1732,7 @@ class AuthSrv(object):
1674
1732
  shrs = enshare[1:]
1675
1733
  if enshare:
1676
1734
 
1677
- zsd = {"d2d": True, "tcolor": self.args.tcolor}
1678
- shv = VFS(self.log_func, "", shr, shr, AXS(), zsd)
1735
+ shv = VFS(self.log_func, "", shr, shr, AXS(), self.vf0())
1679
1736
 
1680
1737
  db_path = self.args.shr_db
1681
1738
  db = sqlite3.connect(db_path)
@@ -2057,6 +2114,7 @@ class AuthSrv(object):
2057
2114
 
2058
2115
  all_mte = {}
2059
2116
  errors = False
2117
+ free_umask = False
2060
2118
  for vol in vfs.all_nodes.values():
2061
2119
  if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
2062
2120
  vol.flags["e2ds"] = True
@@ -2113,6 +2171,27 @@ class AuthSrv(object):
2113
2171
  t = 'volume "/%s" has invalid %stry [%s]'
2114
2172
  raise Exception(t % (vol.vpath, k, vol.flags.get(k + "try")))
2115
2173
 
2174
+ for k in ("chmod_d", "chmod_f"):
2175
+ is_d = k == "chmod_d"
2176
+ zs = vol.flags.get(k, "")
2177
+ if not zs and is_d:
2178
+ zs = "755"
2179
+ if not zs:
2180
+ vol.flags.pop(k, None)
2181
+ continue
2182
+ if not re.match("^[0-7]{3}$", zs):
2183
+ t = "config-option '%s' must be a three-digit octal value such as [755] or [644] but the value was [%s]"
2184
+ t = t % (k, zs)
2185
+ self.log(t, 1)
2186
+ raise Exception(t)
2187
+ zi = int(zs, 8)
2188
+ vol.flags[k] = zi
2189
+ if (is_d and zi != 0o755) or not is_d:
2190
+ free_umask = True
2191
+
2192
+ if vol.lim:
2193
+ vol.lim.chmod_d = vol.flags["chmod_d"]
2194
+
2116
2195
  if vol.flags.get("og"):
2117
2196
  self.args.uqe = True
2118
2197
 
@@ -2196,6 +2275,8 @@ class AuthSrv(object):
2196
2275
  t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s"
2197
2276
  self.log(t % (vol.vpath, etv), 3)
2198
2277
 
2278
+ vol.check_landmarks()
2279
+
2199
2280
  # d2d drops all database features for a volume
2200
2281
  for grp, rm in [["d2d", "e2d"], ["d2t", "e2t"], ["d2d", "e2v"]]:
2201
2282
  if not vol.flags.get(grp, False):
@@ -2342,6 +2423,10 @@ class AuthSrv(object):
2342
2423
  if errors:
2343
2424
  sys.exit(1)
2344
2425
 
2426
+ setattr(self.args, "free_umask", free_umask)
2427
+ if free_umask:
2428
+ os.umask(0)
2429
+
2345
2430
  vfs.bubble_flags()
2346
2431
 
2347
2432
  have_e2d = False
@@ -2544,6 +2629,28 @@ class AuthSrv(object):
2544
2629
  shn.shr_src = (s_vfs, s_rem)
2545
2630
  shn.realpath = s_vfs.canonical(s_rem)
2546
2631
 
2632
+ # root.all_aps doesn't include any shares, so make a copy where the
2633
+ # share appears in all abspaths it can provide (for example for chk_ap)
2634
+ ap = shn.realpath
2635
+ if not ap.endswith(os.sep):
2636
+ ap += os.sep
2637
+ shn.shr_all_aps = [(x, y[:]) for x, y in vfs.all_aps]
2638
+ exact = False
2639
+ for ap2, vns in shn.shr_all_aps:
2640
+ if ap == ap2:
2641
+ exact = True
2642
+ if ap2.startswith(ap):
2643
+ try:
2644
+ vp2 = vjoin(s_rem, ap2[len(ap) :])
2645
+ vn2, _ = s_vfs.get(vp2, "*", False, False)
2646
+ if vn2 == s_vfs or vn2.dbv == s_vfs:
2647
+ vns.append(shn)
2648
+ except:
2649
+ pass
2650
+ if not exact:
2651
+ shn.shr_all_aps.append((ap, [shn]))
2652
+ shn.shr_all_aps.sort(key=lambda x: len(x[0]), reverse=True)
2653
+
2547
2654
  if self.args.shr_v:
2548
2655
  t = "mapped %s share [%s] by [%s] => [%s] => [%s]"
2549
2656
  self.log(t % (s_pr, s_k, s_un, s_vp, shn.realpath))
@@ -2558,7 +2665,7 @@ class AuthSrv(object):
2558
2665
  continue # also fine
2559
2666
  for zs in svn.nodes.keys():
2560
2667
  # hide subvolume
2561
- vn.nodes[zs] = VFS(self.log_func, "", "", "", AXS(), {})
2668
+ vn.nodes[zs] = VFS(self.log_func, "", "", "", AXS(), self.vf0())
2562
2669
 
2563
2670
  cur2.close()
2564
2671
  cur.close()
@@ -2603,6 +2710,7 @@ class AuthSrv(object):
2603
2710
  "def_hcols": list(vf.get("mth") or []),
2604
2711
  "unlist0": vf.get("unlist") or "",
2605
2712
  "see_dots": self.args.see_dots,
2713
+ "dqdel": self.args.qdel,
2606
2714
  "dgrid": "grid" in vf,
2607
2715
  "dgsel": "gsel" in vf,
2608
2716
  "dnsort": "nsort" in vf,
@@ -22,14 +22,26 @@ def listdir(p = ".") :
22
22
 
23
23
 
24
24
  def makedirs(name , mode = 0o755, exist_ok = True) :
25
+ # os.makedirs does 777 for all but leaf; this does mode on all
26
+ todo = []
25
27
  bname = fsenc(name)
26
- try:
27
- os.makedirs(bname, mode)
28
- return True
29
- except:
30
- if not exist_ok or not os.path.isdir(bname):
31
- raise
28
+ while bname:
29
+ if os.path.isdir(bname):
30
+ break
31
+ todo.append(bname)
32
+ bname = os.path.dirname(bname)
33
+ if not todo:
34
+ if not exist_ok:
35
+ os.mkdir(bname) # to throw
32
36
  return False
37
+ for zb in todo[::-1]:
38
+ try:
39
+ os.mkdir(zb, mode)
40
+ except:
41
+ if os.path.isdir(zb):
42
+ continue
43
+ raise
44
+ return True
33
45
 
34
46
 
35
47
  def mkdir(p , mode = 0o755) :
@@ -78,6 +78,8 @@ def vf_vmap() :
78
78
  }
79
79
  for k in (
80
80
  "bup_ck",
81
+ "chmod_d",
82
+ "chmod_f",
81
83
  "dbd",
82
84
  "forget_ip",
83
85
  "hsortn",
@@ -169,6 +171,8 @@ flagcats = {
169
171
  "safededup": "verify on-disk data before using it for dedup",
170
172
  "noclone": "take dupe data from clients, even if available on HDD",
171
173
  "nodupe": "rejects existing files (instead of linking/cloning them)",
174
+ "chmod_d=755": "unix-permission for new dirs/folders",
175
+ "chmod_f=644": "unix-permission for new files",
172
176
  "sparse": "force use of sparse files, mainly for s3-backed storage",
173
177
  "nosparse": "deny use of sparse files, mainly for slow storage",
174
178
  "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
@@ -218,6 +222,7 @@ flagcats = {
218
222
  "d2d": "disables all database stuff, overrides -e2*",
219
223
  "hist=/tmp/cdb": "puts thumbnails and indexes at that location",
220
224
  "dbpath=/tmp/cdb": "puts indexes at that location",
225
+ "landmark=foo": "disable db if file foo doesn't exist",
221
226
  "scan=60": "scan for new files every 60sec, same as --re-maxage",
222
227
  "nohash=\\.iso$": "skips hashing file contents if path matches *.iso",
223
228
  "noidx=\\.iso$": "fully ignores the contents at paths matching *.iso",
@@ -225,7 +225,7 @@ class FtpFs(AbstractedFS):
225
225
  r = "r" in mode
226
226
  w = "w" in mode or "a" in mode or "+" in mode
227
227
 
228
- ap = self.rv2a(filename, r, w)[0]
228
+ ap, vfs, _ = self.rv2a(filename, r, w)
229
229
  self.validpath(ap)
230
230
  if w:
231
231
  try:
@@ -257,7 +257,11 @@ class FtpFs(AbstractedFS):
257
257
 
258
258
  wunlink(self.log, ap, VF_CAREFUL)
259
259
 
260
- return open(fsenc(ap), mode, self.args.iobuf)
260
+ ret = open(fsenc(ap), mode, self.args.iobuf)
261
+ if w and "chmod_f" in vfs.flags:
262
+ os.fchmod(ret.fileno(), vfs.flags["chmod_f"])
263
+
264
+ return ret
261
265
 
262
266
  def chdir(self, path ) :
263
267
  nwd = join(self.cwd, path)
@@ -288,8 +292,9 @@ class FtpFs(AbstractedFS):
288
292
  ) = avfs.can_access("", self.h.uname)
289
293
 
290
294
  def mkdir(self, path ) :
291
- ap = self.rv2a(path, w=True)[0]
292
- bos.makedirs(ap) # filezilla expects this
295
+ ap, vfs, _ = self.rv2a(path, w=True)
296
+ chmod = vfs.flags["chmod_d"]
297
+ bos.makedirs(ap, chmod) # filezilla expects this
293
298
 
294
299
  def listdir(self, path ) :
295
300
  vpath = join(self.cwd, path)