copyparty 1.19.3__tar.gz → 1.19.5__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 (122) hide show
  1. {copyparty-1.19.3 → copyparty-1.19.5}/PKG-INFO +85 -7
  2. {copyparty-1.19.3 → copyparty-1.19.5}/README.md +84 -6
  3. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/__main__.py +4 -2
  4. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/__version__.py +2 -2
  5. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/authsrv.py +12 -0
  6. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/ftpd.py +4 -4
  7. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/httpcli.py +3 -3
  8. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/qrcodegen.py +19 -0
  9. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/svchub.py +9 -2
  10. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/tcpsrv.py +33 -1
  11. copyparty-1.19.5/copyparty/web/browser.css.gz +0 -0
  12. copyparty-1.19.5/copyparty/web/browser.js.gz +0 -0
  13. copyparty-1.19.5/copyparty/web/md.js.gz +0 -0
  14. copyparty-1.19.5/copyparty/web/up2k.js.gz +0 -0
  15. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty.egg-info/PKG-INFO +85 -7
  16. copyparty-1.19.3/copyparty/web/browser.css.gz +0 -0
  17. copyparty-1.19.3/copyparty/web/browser.js.gz +0 -0
  18. copyparty-1.19.3/copyparty/web/md.js.gz +0 -0
  19. copyparty-1.19.3/copyparty/web/up2k.js.gz +0 -0
  20. {copyparty-1.19.3 → copyparty-1.19.5}/LICENSE +0 -0
  21. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/__init__.py +0 -0
  22. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/bos/__init__.py +0 -0
  23. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/bos/bos.py +0 -0
  24. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/bos/path.py +0 -0
  25. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/broker_mp.py +0 -0
  26. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/broker_mpw.py +0 -0
  27. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/broker_thr.py +0 -0
  28. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/broker_util.py +0 -0
  29. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/cert.py +0 -0
  30. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/cfg.py +0 -0
  31. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/dxml.py +0 -0
  32. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/fsutil.py +0 -0
  33. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/httpconn.py +0 -0
  34. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/httpsrv.py +0 -0
  35. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/ico.py +0 -0
  36. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/mdns.py +0 -0
  37. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/metrics.py +0 -0
  38. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/mtag.py +0 -0
  39. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/multicast.py +0 -0
  40. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/pwhash.py +0 -0
  41. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/res/COPYING.txt +0 -0
  42. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/res/__init__.py +0 -0
  43. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/res/insecure.pem +0 -0
  44. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/smbd.py +0 -0
  45. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/ssdp.py +0 -0
  46. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/star.py +0 -0
  47. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/__init__.py +0 -0
  48. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/dnslib/__init__.py +0 -0
  49. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/dnslib/bimap.py +0 -0
  50. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/dnslib/bit.py +0 -0
  51. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/dnslib/buffer.py +0 -0
  52. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/dnslib/dns.py +0 -0
  53. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/dnslib/label.py +0 -0
  54. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/dnslib/lex.py +0 -0
  55. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/dnslib/ranges.py +0 -0
  56. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/ifaddr/__init__.py +0 -0
  57. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/ifaddr/_posix.py +0 -0
  58. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/ifaddr/_shared.py +0 -0
  59. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/ifaddr/_win32.py +0 -0
  60. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/stolen/surrogateescape.py +0 -0
  61. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/sutil.py +0 -0
  62. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/szip.py +0 -0
  63. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/tftpd.py +0 -0
  64. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/th_cli.py +0 -0
  65. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/th_srv.py +0 -0
  66. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/u2idx.py +0 -0
  67. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/up2k.py +0 -0
  68. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/util.py +0 -0
  69. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/a/__init__.py +0 -0
  70. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/a/partyfuse.py +0 -0
  71. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/a/u2c.py +0 -0
  72. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/a/webdav-cfg.bat +0 -0
  73. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/baguettebox.js.gz +0 -0
  74. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/browser.html +0 -0
  75. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/browser2.html +0 -0
  76. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/cf.html +0 -0
  77. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/dbg-audio.js.gz +0 -0
  78. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/__init__.py +0 -0
  79. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/busy.mp3.gz +0 -0
  80. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/easymde.css.gz +0 -0
  81. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/easymde.js.gz +0 -0
  82. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/fuse.py +0 -0
  83. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/marked.js.gz +0 -0
  84. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/mini-fa.css.gz +0 -0
  85. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/mini-fa.woff +0 -0
  86. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/prism.css.gz +0 -0
  87. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/prism.js.gz +0 -0
  88. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/prismd.css.gz +0 -0
  89. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/scp.woff2 +0 -0
  90. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  91. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  92. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/idp.html +0 -0
  93. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/md.css.gz +0 -0
  94. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/md.html +0 -0
  95. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/md2.css.gz +0 -0
  96. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/md2.js.gz +0 -0
  97. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/mde.css.gz +0 -0
  98. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/mde.html +0 -0
  99. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/mde.js.gz +0 -0
  100. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/msg.css.gz +0 -0
  101. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/msg.html +0 -0
  102. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/rups.css.gz +0 -0
  103. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/rups.html +0 -0
  104. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/rups.js.gz +0 -0
  105. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/shares.css.gz +0 -0
  106. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/shares.html +0 -0
  107. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/shares.js.gz +0 -0
  108. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/splash.css.gz +0 -0
  109. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/splash.html +0 -0
  110. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/splash.js.gz +0 -0
  111. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/svcs.html +0 -0
  112. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/svcs.js.gz +0 -0
  113. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/ui.css.gz +0 -0
  114. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/util.js.gz +0 -0
  115. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty/web/w.hash.js.gz +0 -0
  116. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty.egg-info/SOURCES.txt +0 -0
  117. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty.egg-info/dependency_links.txt +0 -0
  118. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty.egg-info/entry_points.txt +0 -0
  119. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty.egg-info/requires.txt +0 -0
  120. {copyparty-1.19.3 → copyparty-1.19.5}/copyparty.egg-info/top_level.txt +0 -0
  121. {copyparty-1.19.3 → copyparty-1.19.5}/pyproject.toml +0 -0
  122. {copyparty-1.19.3 → copyparty-1.19.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.19.3
3
+ Version: 1.19.5
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
@@ -177,6 +177,7 @@ made in Norway 🇳🇴
177
177
  * [packages](#packages) - the party might be closer than you think
178
178
  * [arch package](#arch-package) - `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
179
179
  * [fedora package](#fedora-package) - does not exist yet
180
+ * [homebrew formulae](#homebrew-formulae) - `brew install copyparty ffmpeg`
180
181
  * [nix package](#nix-package) - `nix profile install github:9001/copyparty`
181
182
  * [nixos module](#nixos-module)
182
183
  * [browser support](#browser-support) - TLDR: yes
@@ -206,6 +207,7 @@ made in Norway 🇳🇴
206
207
  * [copyparty.exe](#copypartyexe) - download [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) (win8+) or [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) (win7+)
207
208
  * [zipapp](#zipapp) - another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz)
208
209
  * [install on android](#install-on-android)
210
+ * [install on iOS](#install-on-iOS)
209
211
  * [reporting bugs](#reporting-bugs) - ideas for context to include, and where to submit them
210
212
  * [devnotes](#devnotes) - for build instructions etc, see [./docs/devnotes.md](./docs/devnotes.md)
211
213
 
@@ -220,6 +222,7 @@ just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/
220
222
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
221
223
  * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
222
224
  * or if you are on android, [install copyparty in termux](#install-on-android)
225
+ * or maybe an iPhone or iPad? [install in a-Shell on iOS](#install-on-iOS)
223
226
  * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
224
227
  * or if you have [uv](https://docs.astral.sh/uv/) installed, run `uv tool run copyparty`
225
228
  * or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
@@ -306,7 +309,7 @@ also see [comparison to similar software](./docs/versus.md)
306
309
  * ☑ [upnp / zeroconf / mdns / ssdp](#zeroconf)
307
310
  * ☑ [event hooks](#event-hooks) / script runner
308
311
  * ☑ [reverse-proxy support](https://github.com/9001/copyparty#reverse-proxy)
309
- * ☑ cross-platform (Windows, Linux, Macos, Android, FreeBSD, arm32/arm64, ppc64le, s390x, risc-v/riscv64)
312
+ * ☑ cross-platform (Windows, Linux, Macos, Android, iOS, FreeBSD, arm32/arm64, ppc64le, s390x, risc-v/riscv64)
310
313
  * upload
311
314
  * ☑ basic: plain multipart, ie6 support
312
315
  * ☑ [up2k](#uploading): js, resumable, multithreaded
@@ -329,7 +332,7 @@ also see [comparison to similar software](./docs/versus.md)
329
332
  * ☑ play video files as audio (converted on server)
330
333
  * ☑ create and play [m3u8 playlists](#playlists)
331
334
  * ☑ image gallery with webm player
332
- * ☑ [textfile browser](#textfile-viewer) with syntax hilighting
335
+ * ☑ [textfile browser](#textfile-viewer) with syntax highlighting
333
336
  * ☑ realtime streaming of growing files (logfiles and such)
334
337
  * ☑ [thumbnails](#thumbnails)
335
338
  * ☑ ...of images using Pillow, pyvips, or FFmpeg
@@ -896,7 +899,7 @@ the up2k UI is the epitome of polished intuitive experiences:
896
899
  * `[🔎]` switch between upload and [file-search](#file-search) mode
897
900
  * ignore `[🔎]` if you add files by dragging them into the browser
898
901
 
899
- and then theres the tabs below it,
902
+ and then there's the tabs below it,
900
903
  * `[ok]` is the files which completed successfully
901
904
  * `[ng]` is the ones that failed / got rejected (already exists, ...)
902
905
  * `[done]` shows a combined list of `[ok]` and `[ng]`, chronological order
@@ -1128,7 +1131,7 @@ plays almost every audio format there is (if the server has FFmpeg installed fo
1128
1131
 
1129
1132
  the following audio formats are usually always playable, even without FFmpeg: `aac|flac|m4a|mp3|ogg|opus|wav`
1130
1133
 
1131
- some hilights:
1134
+ some highlights:
1132
1135
  * OS integration; control playback from your phone's lockscreen ([windows](https://user-images.githubusercontent.com/241032/233213022-298a98ba-721a-4cf1-a3d4-f62634bc53d5.png) // [iOS](https://user-images.githubusercontent.com/241032/142711926-0700be6c-3e31-47b3-9928-53722221f722.png) // [android](https://user-images.githubusercontent.com/241032/233212311-a7368590-08c7-4f9f-a1af-48ccf3f36fad.png))
1133
1136
  * shows the audio waveform in the seekbar
1134
1137
  * not perfectly gapless but can get really close (see settings + eq below); good enough to enjoy gapless albums as intended
@@ -1359,6 +1362,12 @@ print a qr-code [(screenshot)](https://user-images.githubusercontent.com/241032/
1359
1362
  * `--qrl lootbox/?pw=hunter2` appends to the url, linking to the `lootbox` folder with password `hunter2`
1360
1363
  * `--qrz 1` forces 1x zoom instead of autoscaling to fit the terminal size
1361
1364
  * 1x may render incorrectly on some terminals/fonts, but 2x should always work
1365
+ * `--qr-pin 1` makes the qr-code stick to the bottom of the console (never scrolls away)
1366
+ * `--qr-file qr.txt:1:2` writes a small qr-code to `qr.txt`
1367
+ * `--qr-file qr.txt:2:2` writes a big qr-code to `qr.txt`
1368
+ * `--qr-file qr.svg:1:2` writes a vector-graphics qr-code to `qr.svg`
1369
+ * `--qr-file qr.png:8:4:333333:ffcc55` writes an 8x-magnified yellow-on-gray `qr.png`
1370
+ * `--qr-file qr.png:8:4::ffffff` writes an 8x-magnified white-on-transparent `qr.png`
1362
1371
 
1363
1372
  it uses the server hostname if [mdns](#mdns) is enabled, otherwise it'll use your external ip (default route) unless `--qri` specifies a specific ip-prefix or domain
1364
1373
 
@@ -1383,6 +1392,14 @@ some recommended FTP / FTPS clients; `wark` = example password:
1383
1392
  * `lftp -u k,wark -p 3921 127.0.0.1 -e ls`
1384
1393
  * `lftp -u k,wark -p 3990 127.0.0.1 -e 'set ssl:verify-certificate no; ls'`
1385
1394
 
1395
+ config file example, which restricts FTP to only use ports 3921 and 12000-12099 so all of those ports must be opened in your firewall:
1396
+
1397
+ ```yaml
1398
+ [global]
1399
+ ftp: 3921
1400
+ ftp-pr: 12000-12099
1401
+ ```
1402
+
1386
1403
 
1387
1404
  ## webdav server
1388
1405
 
@@ -2225,7 +2242,7 @@ when connecting the reverse-proxy to `127.0.0.1` instead (the basic and/or old-f
2225
2242
 
2226
2243
  in summary, `haproxy > caddy > traefik > nginx > apache > lighttpd`, and use uds when possible (traefik does not support it yet)
2227
2244
 
2228
- * if these results are bullshit because my config exampels are bad, please submit corrections!
2245
+ * if these results are bullshit because my config examples are bad, please submit corrections!
2229
2246
 
2230
2247
 
2231
2248
  ## permanent cloudflare tunnel
@@ -2396,6 +2413,15 @@ after installing, start either the system service or the user service and naviga
2396
2413
  does not exist yet; there are rumours that it is being packaged! keep an eye on this space...
2397
2414
 
2398
2415
 
2416
+ ## homebrew formulae
2417
+
2418
+ `brew install copyparty ffmpeg` -- https://formulae.brew.sh/formula/copyparty
2419
+
2420
+ should work on all macs (both intel and apple silicon) and all relevant macos versions
2421
+
2422
+ the homebrew package is maintained by the homebrew team (thanks!)
2423
+
2424
+
2399
2425
  ## nix package
2400
2426
 
2401
2427
  `nix profile install github:9001/copyparty`
@@ -2409,7 +2435,7 @@ some recommended dependencies are enabled by default; [override the package](htt
2409
2435
 
2410
2436
  ## nixos module
2411
2437
 
2412
- for this setup, you will need a [flake-enabled](https://nixos.wiki/wiki/Flakes) installation of NixOS.
2438
+ for [flake-enabled](https://nixos.wiki/wiki/Flakes) installations of NixOS:
2413
2439
 
2414
2440
  ```nix
2415
2441
  {
@@ -2436,6 +2462,33 @@ for this setup, you will need a [flake-enabled](https://nixos.wiki/wiki/Flakes)
2436
2462
  }
2437
2463
  ```
2438
2464
 
2465
+ if you don't use a flake in your configuration, you can use other dependency management tools like [npins](https://github.com/andir/npins), [niv](https://github.com/nmattia/niv), or even plain [`fetchTarball`](https://nix.dev/manual/nix/stable/language/builtins#builtins-fetchTarball), like so:
2466
+
2467
+ ```nix
2468
+ { pkgs, ... }:
2469
+
2470
+ let
2471
+ # npins example, adjust for your setup. copyparty should be a path to the downloaded repo
2472
+ # for niv, just replace the npins folder import with the sources.nix file
2473
+ copyparty = (import ./npins).copyparty;
2474
+
2475
+ # or with fetchTarball:
2476
+ copyparty = fetchTarball "https://github.com/9001/copyparty/archive/hovudstraum.tar.gz";
2477
+ in
2478
+
2479
+ {
2480
+ # load the copyparty NixOS module
2481
+ imports = [ "${copyparty}/contrib/nixos/modules/copyparty.nix" ];
2482
+
2483
+ # add the copyparty overlay to expose the package to the module
2484
+ nixpkgs.overlays = [ (import "${copyparty}/contrib/package/nix/overlay.nix") ];
2485
+ # (optional) install the package globally
2486
+ environment.systemPackages = [ pkgs.copyparty ];
2487
+ # configure the copyparty module
2488
+ services.copyparty.enable = true;
2489
+ }
2490
+ ```
2491
+
2439
2492
  copyparty on NixOS is configured via `services.copyparty` options, for example:
2440
2493
  ```nix
2441
2494
  services.copyparty = {
@@ -2622,6 +2675,8 @@ sync folders to/from copyparty
2622
2675
 
2623
2676
  NOTE: full bidirectional sync, like what [nextcloud](https://docs.nextcloud.com/server/latest/user_manual/sv/files/desktop_mobile_sync.html) and [syncthing](https://syncthing.net/) does, will never be supported! Only single-direction sync (server-to-client, or client-to-server) is possible with copyparty
2624
2677
 
2678
+ * if you want bidirectional sync, then copyparty and syncthing *should* be entirely safe to combine; they should be able to collaborate on the same folders without causing any trouble for eachother. Many people do this, and there have been no issues so far. But, if you *do* encounter any problems, please [file a copyparty bug](https://github.com/9001/copyparty/issues/new/choose) and I'll try to help -- just keep in mind I've never used syncthing before :-)
2679
+
2625
2680
  the commandline uploader [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy) with `--dr` is the best way to sync a folder to copyparty; verifies checksums and does files in parallel, and deletes unexpected files on the server after upload has finished which makes file-renames really cheap (it'll rename serverside and skip uploading)
2626
2681
 
2627
2682
  alternatively there is [rclone](./docs/rclone.md) which allows for bidirectional sync and is *way* more flexible (stream files straight from sftp/s3/gcs to copyparty, ...), although there is no integrity check and it won't work with files over 100 MiB if copyparty is behind cloudflare
@@ -2668,6 +2723,8 @@ there is no iPhone app, but the following shortcuts are almost as good:
2668
2723
  * can download links and rehost the target file on copyparty (see first comment inside the shortcut)
2669
2724
  * pics become lowres if you share from gallery to shortcut, so better to launch the shortcut and pick stuff from there
2670
2725
 
2726
+ if you want to run the copyparty server on your iPhone or iPad, see [install on iOS](#install-on-iOS)
2727
+
2671
2728
 
2672
2729
  # performance
2673
2730
 
@@ -3001,6 +3058,27 @@ if you want thumbnails (photos+videos) and you're okay with spending another 132
3001
3058
  * or if you want to use `vips` for photo-thumbs instead, `pkg install libvips && python -m pip install --user -U wheel && python -m pip install --user -U pyvips && (cd /data/data/com.termux/files/usr/lib/; ln -s libgobject-2.0.so{,.0}; ln -s libvips.so{,.42})`
3002
3059
 
3003
3060
 
3061
+ # install on iOS
3062
+
3063
+ first install one of the following:
3064
+ * [a-Shell mini](https://apps.apple.com/us/app/a-shell-mini/id1543537943) gives you the essential features
3065
+ * [a-Shell](https://apps.apple.com/us/app/a-shell/id1473805438) also enables audio transcoding and better thubmnails
3066
+
3067
+ and then copypaste the following command into `a-Shell`:
3068
+
3069
+ ```sh
3070
+ curl https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh | sh
3071
+ ```
3072
+
3073
+ what this does:
3074
+ * creates a basic [config file](#accounts-and-volumes) named `cpc` which you can edit with `vim cpc`
3075
+ * adds the command `cpp` to launch copyparty with that config file
3076
+
3077
+ known issues:
3078
+ * cannot run in the background; it needs to be on-screen to accept connections / uploads / downloads
3079
+ * the best way to exit copyparty is to swipe away the app
3080
+
3081
+
3004
3082
  # reporting bugs
3005
3083
 
3006
3084
  ideas for context to include, and where to submit them
@@ -112,6 +112,7 @@ made in Norway 🇳🇴
112
112
  * [packages](#packages) - the party might be closer than you think
113
113
  * [arch package](#arch-package) - `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
114
114
  * [fedora package](#fedora-package) - does not exist yet
115
+ * [homebrew formulae](#homebrew-formulae) - `brew install copyparty ffmpeg`
115
116
  * [nix package](#nix-package) - `nix profile install github:9001/copyparty`
116
117
  * [nixos module](#nixos-module)
117
118
  * [browser support](#browser-support) - TLDR: yes
@@ -141,6 +142,7 @@ made in Norway 🇳🇴
141
142
  * [copyparty.exe](#copypartyexe) - download [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) (win8+) or [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) (win7+)
142
143
  * [zipapp](#zipapp) - another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz)
143
144
  * [install on android](#install-on-android)
145
+ * [install on iOS](#install-on-iOS)
144
146
  * [reporting bugs](#reporting-bugs) - ideas for context to include, and where to submit them
145
147
  * [devnotes](#devnotes) - for build instructions etc, see [./docs/devnotes.md](./docs/devnotes.md)
146
148
 
@@ -155,6 +157,7 @@ just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/
155
157
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
156
158
  * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
157
159
  * or if you are on android, [install copyparty in termux](#install-on-android)
160
+ * or maybe an iPhone or iPad? [install in a-Shell on iOS](#install-on-iOS)
158
161
  * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
159
162
  * or if you have [uv](https://docs.astral.sh/uv/) installed, run `uv tool run copyparty`
160
163
  * or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
@@ -241,7 +244,7 @@ also see [comparison to similar software](./docs/versus.md)
241
244
  * ☑ [upnp / zeroconf / mdns / ssdp](#zeroconf)
242
245
  * ☑ [event hooks](#event-hooks) / script runner
243
246
  * ☑ [reverse-proxy support](https://github.com/9001/copyparty#reverse-proxy)
244
- * ☑ cross-platform (Windows, Linux, Macos, Android, FreeBSD, arm32/arm64, ppc64le, s390x, risc-v/riscv64)
247
+ * ☑ cross-platform (Windows, Linux, Macos, Android, iOS, FreeBSD, arm32/arm64, ppc64le, s390x, risc-v/riscv64)
245
248
  * upload
246
249
  * ☑ basic: plain multipart, ie6 support
247
250
  * ☑ [up2k](#uploading): js, resumable, multithreaded
@@ -264,7 +267,7 @@ also see [comparison to similar software](./docs/versus.md)
264
267
  * ☑ play video files as audio (converted on server)
265
268
  * ☑ create and play [m3u8 playlists](#playlists)
266
269
  * ☑ image gallery with webm player
267
- * ☑ [textfile browser](#textfile-viewer) with syntax hilighting
270
+ * ☑ [textfile browser](#textfile-viewer) with syntax highlighting
268
271
  * ☑ realtime streaming of growing files (logfiles and such)
269
272
  * ☑ [thumbnails](#thumbnails)
270
273
  * ☑ ...of images using Pillow, pyvips, or FFmpeg
@@ -831,7 +834,7 @@ the up2k UI is the epitome of polished intuitive experiences:
831
834
  * `[🔎]` switch between upload and [file-search](#file-search) mode
832
835
  * ignore `[🔎]` if you add files by dragging them into the browser
833
836
 
834
- and then theres the tabs below it,
837
+ and then there's the tabs below it,
835
838
  * `[ok]` is the files which completed successfully
836
839
  * `[ng]` is the ones that failed / got rejected (already exists, ...)
837
840
  * `[done]` shows a combined list of `[ok]` and `[ng]`, chronological order
@@ -1063,7 +1066,7 @@ plays almost every audio format there is (if the server has FFmpeg installed fo
1063
1066
 
1064
1067
  the following audio formats are usually always playable, even without FFmpeg: `aac|flac|m4a|mp3|ogg|opus|wav`
1065
1068
 
1066
- some hilights:
1069
+ some highlights:
1067
1070
  * OS integration; control playback from your phone's lockscreen ([windows](https://user-images.githubusercontent.com/241032/233213022-298a98ba-721a-4cf1-a3d4-f62634bc53d5.png) // [iOS](https://user-images.githubusercontent.com/241032/142711926-0700be6c-3e31-47b3-9928-53722221f722.png) // [android](https://user-images.githubusercontent.com/241032/233212311-a7368590-08c7-4f9f-a1af-48ccf3f36fad.png))
1068
1071
  * shows the audio waveform in the seekbar
1069
1072
  * not perfectly gapless but can get really close (see settings + eq below); good enough to enjoy gapless albums as intended
@@ -1294,6 +1297,12 @@ print a qr-code [(screenshot)](https://user-images.githubusercontent.com/241032/
1294
1297
  * `--qrl lootbox/?pw=hunter2` appends to the url, linking to the `lootbox` folder with password `hunter2`
1295
1298
  * `--qrz 1` forces 1x zoom instead of autoscaling to fit the terminal size
1296
1299
  * 1x may render incorrectly on some terminals/fonts, but 2x should always work
1300
+ * `--qr-pin 1` makes the qr-code stick to the bottom of the console (never scrolls away)
1301
+ * `--qr-file qr.txt:1:2` writes a small qr-code to `qr.txt`
1302
+ * `--qr-file qr.txt:2:2` writes a big qr-code to `qr.txt`
1303
+ * `--qr-file qr.svg:1:2` writes a vector-graphics qr-code to `qr.svg`
1304
+ * `--qr-file qr.png:8:4:333333:ffcc55` writes an 8x-magnified yellow-on-gray `qr.png`
1305
+ * `--qr-file qr.png:8:4::ffffff` writes an 8x-magnified white-on-transparent `qr.png`
1297
1306
 
1298
1307
  it uses the server hostname if [mdns](#mdns) is enabled, otherwise it'll use your external ip (default route) unless `--qri` specifies a specific ip-prefix or domain
1299
1308
 
@@ -1318,6 +1327,14 @@ some recommended FTP / FTPS clients; `wark` = example password:
1318
1327
  * `lftp -u k,wark -p 3921 127.0.0.1 -e ls`
1319
1328
  * `lftp -u k,wark -p 3990 127.0.0.1 -e 'set ssl:verify-certificate no; ls'`
1320
1329
 
1330
+ config file example, which restricts FTP to only use ports 3921 and 12000-12099 so all of those ports must be opened in your firewall:
1331
+
1332
+ ```yaml
1333
+ [global]
1334
+ ftp: 3921
1335
+ ftp-pr: 12000-12099
1336
+ ```
1337
+
1321
1338
 
1322
1339
  ## webdav server
1323
1340
 
@@ -2160,7 +2177,7 @@ when connecting the reverse-proxy to `127.0.0.1` instead (the basic and/or old-f
2160
2177
 
2161
2178
  in summary, `haproxy > caddy > traefik > nginx > apache > lighttpd`, and use uds when possible (traefik does not support it yet)
2162
2179
 
2163
- * if these results are bullshit because my config exampels are bad, please submit corrections!
2180
+ * if these results are bullshit because my config examples are bad, please submit corrections!
2164
2181
 
2165
2182
 
2166
2183
  ## permanent cloudflare tunnel
@@ -2331,6 +2348,15 @@ after installing, start either the system service or the user service and naviga
2331
2348
  does not exist yet; there are rumours that it is being packaged! keep an eye on this space...
2332
2349
 
2333
2350
 
2351
+ ## homebrew formulae
2352
+
2353
+ `brew install copyparty ffmpeg` -- https://formulae.brew.sh/formula/copyparty
2354
+
2355
+ should work on all macs (both intel and apple silicon) and all relevant macos versions
2356
+
2357
+ the homebrew package is maintained by the homebrew team (thanks!)
2358
+
2359
+
2334
2360
  ## nix package
2335
2361
 
2336
2362
  `nix profile install github:9001/copyparty`
@@ -2344,7 +2370,7 @@ some recommended dependencies are enabled by default; [override the package](htt
2344
2370
 
2345
2371
  ## nixos module
2346
2372
 
2347
- for this setup, you will need a [flake-enabled](https://nixos.wiki/wiki/Flakes) installation of NixOS.
2373
+ for [flake-enabled](https://nixos.wiki/wiki/Flakes) installations of NixOS:
2348
2374
 
2349
2375
  ```nix
2350
2376
  {
@@ -2371,6 +2397,33 @@ for this setup, you will need a [flake-enabled](https://nixos.wiki/wiki/Flakes)
2371
2397
  }
2372
2398
  ```
2373
2399
 
2400
+ if you don't use a flake in your configuration, you can use other dependency management tools like [npins](https://github.com/andir/npins), [niv](https://github.com/nmattia/niv), or even plain [`fetchTarball`](https://nix.dev/manual/nix/stable/language/builtins#builtins-fetchTarball), like so:
2401
+
2402
+ ```nix
2403
+ { pkgs, ... }:
2404
+
2405
+ let
2406
+ # npins example, adjust for your setup. copyparty should be a path to the downloaded repo
2407
+ # for niv, just replace the npins folder import with the sources.nix file
2408
+ copyparty = (import ./npins).copyparty;
2409
+
2410
+ # or with fetchTarball:
2411
+ copyparty = fetchTarball "https://github.com/9001/copyparty/archive/hovudstraum.tar.gz";
2412
+ in
2413
+
2414
+ {
2415
+ # load the copyparty NixOS module
2416
+ imports = [ "${copyparty}/contrib/nixos/modules/copyparty.nix" ];
2417
+
2418
+ # add the copyparty overlay to expose the package to the module
2419
+ nixpkgs.overlays = [ (import "${copyparty}/contrib/package/nix/overlay.nix") ];
2420
+ # (optional) install the package globally
2421
+ environment.systemPackages = [ pkgs.copyparty ];
2422
+ # configure the copyparty module
2423
+ services.copyparty.enable = true;
2424
+ }
2425
+ ```
2426
+
2374
2427
  copyparty on NixOS is configured via `services.copyparty` options, for example:
2375
2428
  ```nix
2376
2429
  services.copyparty = {
@@ -2557,6 +2610,8 @@ sync folders to/from copyparty
2557
2610
 
2558
2611
  NOTE: full bidirectional sync, like what [nextcloud](https://docs.nextcloud.com/server/latest/user_manual/sv/files/desktop_mobile_sync.html) and [syncthing](https://syncthing.net/) does, will never be supported! Only single-direction sync (server-to-client, or client-to-server) is possible with copyparty
2559
2612
 
2613
+ * if you want bidirectional sync, then copyparty and syncthing *should* be entirely safe to combine; they should be able to collaborate on the same folders without causing any trouble for eachother. Many people do this, and there have been no issues so far. But, if you *do* encounter any problems, please [file a copyparty bug](https://github.com/9001/copyparty/issues/new/choose) and I'll try to help -- just keep in mind I've never used syncthing before :-)
2614
+
2560
2615
  the commandline uploader [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy) with `--dr` is the best way to sync a folder to copyparty; verifies checksums and does files in parallel, and deletes unexpected files on the server after upload has finished which makes file-renames really cheap (it'll rename serverside and skip uploading)
2561
2616
 
2562
2617
  alternatively there is [rclone](./docs/rclone.md) which allows for bidirectional sync and is *way* more flexible (stream files straight from sftp/s3/gcs to copyparty, ...), although there is no integrity check and it won't work with files over 100 MiB if copyparty is behind cloudflare
@@ -2603,6 +2658,8 @@ there is no iPhone app, but the following shortcuts are almost as good:
2603
2658
  * can download links and rehost the target file on copyparty (see first comment inside the shortcut)
2604
2659
  * pics become lowres if you share from gallery to shortcut, so better to launch the shortcut and pick stuff from there
2605
2660
 
2661
+ if you want to run the copyparty server on your iPhone or iPad, see [install on iOS](#install-on-iOS)
2662
+
2606
2663
 
2607
2664
  # performance
2608
2665
 
@@ -2936,6 +2993,27 @@ if you want thumbnails (photos+videos) and you're okay with spending another 132
2936
2993
  * or if you want to use `vips` for photo-thumbs instead, `pkg install libvips && python -m pip install --user -U wheel && python -m pip install --user -U pyvips && (cd /data/data/com.termux/files/usr/lib/; ln -s libgobject-2.0.so{,.0}; ln -s libvips.so{,.42})`
2937
2994
 
2938
2995
 
2996
+ # install on iOS
2997
+
2998
+ first install one of the following:
2999
+ * [a-Shell mini](https://apps.apple.com/us/app/a-shell-mini/id1543537943) gives you the essential features
3000
+ * [a-Shell](https://apps.apple.com/us/app/a-shell/id1473805438) also enables audio transcoding and better thubmnails
3001
+
3002
+ and then copypaste the following command into `a-Shell`:
3003
+
3004
+ ```sh
3005
+ curl https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh | sh
3006
+ ```
3007
+
3008
+ what this does:
3009
+ * creates a basic [config file](#accounts-and-volumes) named `cpc` which you can edit with `vim cpc`
3010
+ * adds the command `cpp` to launch copyparty with that config file
3011
+
3012
+ known issues:
3013
+ * cannot run in the background; it needs to be on-screen to accept connections / uploads / downloads
3014
+ * the best way to exit copyparty is to swipe away the app
3015
+
3016
+
2939
3017
  # reporting bugs
2940
3018
 
2941
3019
  ideas for context to include, and where to submit them
@@ -431,7 +431,7 @@ def args_from_cfg(cfg_path ) :
431
431
  def expand_cfg(argv) :
432
432
  if CFG_DEF:
433
433
  supp = args_from_cfg(CFG_DEF[0])
434
- argv = supp + argv
434
+ argv = argv[:1] + supp + argv[1:]
435
435
 
436
436
  n = 0
437
437
  while n < len(argv):
@@ -767,7 +767,7 @@ def get_sects():
767
767
  \033[36mc0\033[35m show all process output (default)
768
768
  \033[36mc1\033[35m show only stderr
769
769
  \033[36mc2\033[35m show only stdout
770
- \033[36mc3\033[35m mute all process otput
770
+ \033[36mc3\033[35m mute all process output
771
771
  \033[0m
772
772
  examples:
773
773
 
@@ -1106,6 +1106,8 @@ def add_qr(ap, tty):
1106
1106
  ap2.add_argument("--qrp", metavar="CELLS", type=int, default=4, help="padding (spec says 4 or more, but 1 is usually fine)")
1107
1107
  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)")
1108
1108
  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")
1109
+ ap2.add_argument("--qr-wait", metavar="SEC", type=float, default=0, help="wait \033[33mSEC\033[0m before printing the qr-code to the log")
1110
+ ap2.add_argument("--qr-file", metavar="TXT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m write qr-code to file.\n └─To create txt or svg, \033[33mTXT\033[0m is Filepath:Zoom:Pad, for example [\033[32mqr.txt:1:2\033[0m]\n └─To create png or gif, \033[33mTXT\033[0m is Filepath:Zoom:Pad:Foreground:Background, for example [\033[32mqr.png:8:2:333333:ffcc55\033[0m], or [\033[32mqr.png:8:2::ffcc55\033[0m] for transparent")
1109
1111
 
1110
1112
 
1111
1113
  def add_fs(ap):
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 19, 3)
3
+ VERSION = (1, 19, 5)
4
4
  CODENAME = "usernames"
5
- BUILD_DT = (2025, 8, 17)
5
+ BUILD_DT = (2025, 8, 21)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
@@ -1320,6 +1320,10 @@ class AuthSrv(object):
1320
1320
  zt = split_cfg_ln(ln)
1321
1321
  for zs, za in zt.items():
1322
1322
  zs = zs.lstrip("-")
1323
+ if "=" in zs:
1324
+ t = "WARNING: found an option named [%s] in your [global] config; did you mean to say [%s: %s] instead?"
1325
+ zs1, zs2 = zs.split("=", 1)
1326
+ self.log(t % (zs, zs1, zs2), 3)
1323
1327
  if za is True:
1324
1328
  self._e("└─argument [{}]".format(zs))
1325
1329
  else:
@@ -1329,6 +1333,10 @@ class AuthSrv(object):
1329
1333
  if cat == cata:
1330
1334
  try:
1331
1335
  u, p = [zs.strip() for zs in ln.split(":", 1)]
1336
+ if "=" in u and not p:
1337
+ t = "WARNING: found username [%s] in your [accounts] config; did you mean to say [%s: %s] instead?"
1338
+ zs1, zs2 = u.split("=", 1)
1339
+ self.log(t % (u, zs1, zs2), 3)
1332
1340
  self._l(ln, 5, "account [{}], password [{}]".format(u, p))
1333
1341
  acct[u] = p
1334
1342
  except:
@@ -1399,6 +1407,10 @@ class AuthSrv(object):
1399
1407
  zd = split_cfg_ln(ln)
1400
1408
  fstr = ""
1401
1409
  for sk, sv in zd.items():
1410
+ if "=" in sk:
1411
+ t = "WARNING: found a volflag named [%s] in your config; did you mean to say [%s: %s] instead?"
1412
+ zs1, zs2 = sk.split("=", 1)
1413
+ self.log(t % (sk, zs1, zs2), 3)
1402
1414
  bad = re.sub(r"[a-z0-9_-]", "", sk).lstrip("-")
1403
1415
  if bad:
1404
1416
  err = "bad characters [{}] in volflag name [{}]; "
@@ -64,13 +64,13 @@ class FtpAuth(DummyAuthorizer):
64
64
  if ip.startswith("::ffff:"):
65
65
  ip = ip[7:]
66
66
 
67
- ip = ipnorm(ip)
67
+ ipn = ipnorm(ip)
68
68
  bans = self.hub.bans
69
- if ip in bans:
70
- rt = bans[ip] - time.time()
69
+ if ipn in bans:
70
+ rt = bans[ipn] - time.time()
71
71
  if rt < 0:
72
72
  logging.info("client unbanned")
73
- del bans[ip]
73
+ del bans[ipn]
74
74
  else:
75
75
  raise AuthenticationFailed("banned")
76
76
 
@@ -389,10 +389,10 @@ class HttpCli(object):
389
389
  zsl = [
390
390
  " rproxy: %d if this client's IP-address is [%s]"
391
391
  % (-1 - zd, zs.strip())
392
- for zd, zs in enumerate(zsl)
392
+ for zd, zs in enumerate(zsl[::-1])
393
393
  ]
394
- t = 'could not determine the client\'s IP-address because the global-option --rproxy has not been configured, so the request-header [%s] specified by global-option --xff-hdr cannot be used safely! Please see the "reverse-proxy" section in the readme. The best approach is to configure your reverse-proxy to give copyparty the exact IP-address to assume (perhaps in another header), but you may also try the following:'
395
- t = t % (self.args.xff_hdr,)
394
+ t = 'could not determine the client\'s IP-address because the global-option --rproxy has not been configured, so the request-header [%s] specified by global-option --xff-hdr cannot be used safely! The raw header value was [%s]. Please see the "reverse-proxy" section in the readme. The best approach is to configure your reverse-proxy to give copyparty the exact IP-address to assume (perhaps in another header), but you may also try the following:'
395
+ t = t % (self.args.xff_hdr, zso)
396
396
  self.log("%s\n\n%s\n" % (t, "\n".join(zsl)), 3)
397
397
 
398
398
  pip = self.conn.addr[0]
@@ -195,6 +195,25 @@ class QrCode(object):
195
195
 
196
196
  return "\n".join(rows)
197
197
 
198
+ def to_png(self, zoom, pad, bg, fg, ap) :
199
+ from PIL import Image
200
+
201
+ tab = self.modules
202
+ sz = self.size
203
+ psz = sz + pad * 2
204
+ if bg:
205
+ img = Image.new("RGB", (psz, psz), bg)
206
+ else:
207
+ img = Image.new("RGBA", (psz, psz), (0, 0, 0, 0))
208
+ fg = (fg[0], fg[1], fg[2], 255)
209
+ for y in range(sz):
210
+ for x in range(sz):
211
+ if tab[y][x]:
212
+ img.putpixel((x + pad, y + pad), fg)
213
+ if zoom != 1:
214
+ img = img.resize((sz * zoom, sz * zoom), Image.Resampling.NEAREST)
215
+ img.save(ap)
216
+
198
217
  def _draw_function_patterns(self) :
199
218
  # Draw horizontal and vertical timing patterns
200
219
  for i in range(self.size):
@@ -427,7 +427,7 @@ class SvcHub(object):
427
427
 
428
428
  # create netmaps early to avoid firewall gaps,
429
429
  # but the mutex blocks multiprocessing startup
430
- for zs in "ipu_iu ftp_ipa_nm tftp_ipa_nm".split():
430
+ for zs in "ipu_nm ftp_ipa_nm tftp_ipa_nm".split():
431
431
  try:
432
432
  getattr(args, zs).mutex = threading.Lock()
433
433
  except:
@@ -809,6 +809,10 @@ class SvcHub(object):
809
809
  t = "%s\033[s\033[1;%dr\033[%dH%s%s\033[u" % (t, sh - 1, sh, qr, url)
810
810
  self.pr(t, file=sys.stderr)
811
811
 
812
+ def sleepy_qr(self):
813
+ time.sleep(self.args.qr_wait)
814
+ self.log("qr-code", self.tcpsrv.qr)
815
+
812
816
  def cb_httpsrv_up(self) :
813
817
  self.httpsrv_up += 1
814
818
  if self.httpsrv_up != self.broker.num_workers:
@@ -824,7 +828,10 @@ class SvcHub(object):
824
828
  if self.args.qr_pin:
825
829
  self.sticky_qr()
826
830
  else:
827
- self.log("qr-code", self.tcpsrv.qr)
831
+ if self.args.qr_wait:
832
+ Daemon(self.sleepy_qr, "qr_w8")
833
+ else:
834
+ self.log("qr-code", self.tcpsrv.qr)
828
835
  else:
829
836
  self.log("root", "workers OK\n")
830
837
 
@@ -9,7 +9,7 @@ import time
9
9
 
10
10
  from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
11
11
  from .cert import gencert
12
- from .stolen.qrcodegen import QrCode
12
+ from .stolen.qrcodegen import QrCode, qr2svg
13
13
  from .util import (
14
14
  E_ACCESS,
15
15
  E_ADDR_IN_USE,
@@ -618,6 +618,10 @@ class TcpSrv(object):
618
618
  pad = self.args.qrp
619
619
  zoom = self.args.qrz
620
620
  qrc = QrCode.encode_binary(btxt)
621
+
622
+ for zs in self.args.qr_file or []:
623
+ self._qr2file(qrc, zs)
624
+
621
625
  if zoom == 0:
622
626
  try:
623
627
  tw, th = termsize()
@@ -633,6 +637,8 @@ class TcpSrv(object):
633
637
  halfc = "\033[40;48;5;{0}m{1}\033[47;48;5;{2}m"
634
638
  if not fg:
635
639
  halfc = "\033[0;40m{1}\033[0;47m"
640
+ if nocolor:
641
+ halfc = "\033[0;7m{1}\033[0m"
636
642
 
637
643
  def ansify(m ) :
638
644
  return halfc.format(fg, " " * len(m.group(1)), bg)
@@ -651,3 +657,29 @@ class TcpSrv(object):
651
657
  t = t.replace("\n", "`\n`")
652
658
 
653
659
  return txt + t
660
+
661
+ def _qr2file(self, qrc , txt ):
662
+ if ".txt:" in txt or ".svg:" in txt:
663
+ ap, zs1, zs2 = txt.rsplit(":", 2)
664
+ bg = fg = ""
665
+ else:
666
+ ap, zs1, zs2, bg, fg = txt.rsplit(":", 4)
667
+ zoom = int(zs1)
668
+ pad = int(zs2)
669
+
670
+ if ap.endswith(".txt"):
671
+ if zoom not in (1, 2):
672
+ raise Exception("invalid zoom for qr.txt; must be 1 or 2")
673
+ with open(ap, "wb") as f:
674
+ f.write(qrc.render(zoom, pad).encode("utf-8"))
675
+ elif ap.endswith(".svg"):
676
+ with open(ap, "wb") as f:
677
+ f.write(qr2svg(qrc, pad).encode("utf-8"))
678
+ else:
679
+ qrc.to_png(zoom, pad, self._h2i(bg), self._h2i(fg), ap)
680
+
681
+ def _h2i(self, hs):
682
+ try:
683
+ return tuple(int(hs[i : i + 2], 16) for i in (0, 2, 4))
684
+ except:
685
+ return None
Binary file