pigeondrop 1.5.0 → 1.7.0
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.
- package/CHANGELOG.md +17 -0
- package/README.md +47 -47
- package/package.json +3 -3
- package/public/index.html +24 -8
- package/server.js +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# What's new
|
|
2
2
|
|
|
3
|
+
## v1.7.0 — 4 November 2026
|
|
4
|
+
|
|
5
|
+
- Changed: **Pigeonhole is now called Pigeondrop.** The page, the installers and the instructions use the new name. Existing installs keep working, and the installers keep updating them where they are.
|
|
6
|
+
- Changed: **The `pigeonhole` command is gone.** Run `pigeondrop` instead.
|
|
7
|
+
|
|
8
|
+
## v1.6.1 — 2 November 2026
|
|
9
|
+
|
|
10
|
+
- Changed: **The page heading reads Pigeonhole.** It is no longer shown in capitals.
|
|
11
|
+
|
|
12
|
+
## v1.6.0 — 2 November 2026
|
|
13
|
+
|
|
14
|
+
- New: **Filter the file list by name.** Type in the box above the list to show only the files and folders whose names contain what you typed.
|
|
15
|
+
|
|
16
|
+
## v1.5.1 — 16 October 2026
|
|
17
|
+
|
|
18
|
+
- Changed: **Pigeonhole's code now lives at github.com/cventour/pigeondrop.** Old links and install commands still work.
|
|
19
|
+
|
|
3
20
|
## v1.5.0 — 16 October 2026
|
|
4
21
|
|
|
5
22
|
- New: **Run it with npx.** `npx pigeondrop` starts Pigeonhole in the folder you are in, with nothing to install. Its files and settings stay in that folder.
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
<img src="icon.svg" width="88" alt="
|
|
1
|
+
<img src="icon.svg" width="88" alt="Pigeondrop icon: a wall of arched pigeonholes, one holding a letter">
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Pigeondrop
|
|
4
4
|
|
|
5
|
-
A file
|
|
5
|
+
A file drop you can stand up in a minute. Put files in, let people take
|
|
6
6
|
them out, and take back whatever they leave you.
|
|
7
7
|
|
|
8
8
|
Useful when a group needs a shared drop: distributing builds, installers or
|
|
@@ -61,7 +61,7 @@ Also WSL, and anything else with bash.
|
|
|
61
61
|
## Install it
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
curl -fsSL https://raw.githubusercontent.com/cventour/
|
|
64
|
+
curl -fsSL https://raw.githubusercontent.com/cventour/pigeondrop/main/install.sh -o install.sh
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
Read it — it is about a hundred lines and does nothing surprising. Then:
|
|
@@ -70,14 +70,14 @@ Read it — it is about a hundred lines and does nothing surprising. Then:
|
|
|
70
70
|
bash install.sh
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
It installs to `~/
|
|
73
|
+
It installs to `~/pigeondrop`, checks your Node version, and starts the
|
|
74
74
|
server. Open http://localhost:3001.
|
|
75
75
|
|
|
76
76
|
Options:
|
|
77
77
|
|
|
78
78
|
| Option | Meaning |
|
|
79
79
|
|---|---|
|
|
80
|
-
| `--dir` | Where to install. Default `~/
|
|
80
|
+
| `--dir` | Where to install. Default `~/pigeondrop`. |
|
|
81
81
|
| `--root` | The directory to serve. Default `<install dir>/files`. |
|
|
82
82
|
| `--port` | Default `3001`. |
|
|
83
83
|
| `--host` | Bind address. Default `127.0.0.1`. |
|
|
@@ -97,8 +97,8 @@ Run the script again later and it updates the copy it installed.
|
|
|
97
97
|
The script saves typing, not much else. The manual version is three lines:
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
|
-
git clone https://github.com/cventour/
|
|
101
|
-
cd
|
|
100
|
+
git clone https://github.com/cventour/pigeondrop.git
|
|
101
|
+
cd pigeondrop
|
|
102
102
|
node server.js
|
|
103
103
|
```
|
|
104
104
|
|
|
@@ -150,25 +150,25 @@ Others open `http://<that-address>:3001`. Read
|
|
|
150
150
|
The shell that started it owns it. To survive a closed terminal:
|
|
151
151
|
|
|
152
152
|
```bash
|
|
153
|
-
nohup env REPO_ROOT=/srv/files node server.js >
|
|
153
|
+
nohup env REPO_ROOT=/srv/files node server.js > pigeondrop.log 2>&1 &
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
**Linux, with systemd.** Save as `/etc/systemd/system/
|
|
156
|
+
**Linux, with systemd.** Save as `/etc/systemd/system/pigeondrop.service`:
|
|
157
157
|
|
|
158
158
|
```ini
|
|
159
159
|
[Unit]
|
|
160
|
-
Description=
|
|
160
|
+
Description=Pigeondrop
|
|
161
161
|
After=network.target
|
|
162
162
|
|
|
163
163
|
[Service]
|
|
164
164
|
Type=simple
|
|
165
|
-
User=
|
|
166
|
-
WorkingDirectory=/opt/
|
|
167
|
-
ExecStart=/usr/bin/node /opt/
|
|
165
|
+
User=pigeondrop
|
|
166
|
+
WorkingDirectory=/opt/pigeondrop
|
|
167
|
+
ExecStart=/usr/bin/node /opt/pigeondrop/server.js
|
|
168
168
|
Environment=REPO_ROOT=/srv/files
|
|
169
169
|
# A place the service may write its settings, which ProtectSystem=strict
|
|
170
|
-
# would otherwise refuse.
|
|
171
|
-
StateDirectory=
|
|
170
|
+
# would otherwise refuse. Pigeondrop finds it by itself: /var/lib/pigeondrop.
|
|
171
|
+
StateDirectory=pigeondrop
|
|
172
172
|
# Quote any value containing a space: systemd splits unquoted ones.
|
|
173
173
|
Environment="REPO_TITLE=Team Drop"
|
|
174
174
|
Restart=on-failure
|
|
@@ -183,15 +183,15 @@ WantedBy=multi-user.target
|
|
|
183
183
|
```
|
|
184
184
|
|
|
185
185
|
```bash
|
|
186
|
-
sudo systemctl enable --now
|
|
186
|
+
sudo systemctl enable --now pigeondrop
|
|
187
187
|
```
|
|
188
188
|
|
|
189
189
|
```bash
|
|
190
|
-
systemctl status
|
|
190
|
+
systemctl status pigeondrop
|
|
191
191
|
```
|
|
192
192
|
|
|
193
193
|
**macOS, with launchd.** Save as
|
|
194
|
-
`~/Library/LaunchAgents/com.
|
|
194
|
+
`~/Library/LaunchAgents/com.pigeondrop.plist`, with your own paths — launchd
|
|
195
195
|
does not expand `~`, and needs the full path to node:
|
|
196
196
|
|
|
197
197
|
```xml
|
|
@@ -199,11 +199,11 @@ does not expand `~`, and needs the full path to node:
|
|
|
199
199
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
200
200
|
<plist version="1.0">
|
|
201
201
|
<dict>
|
|
202
|
-
<key>Label</key> <string>com.
|
|
202
|
+
<key>Label</key> <string>com.pigeondrop</string>
|
|
203
203
|
<key>ProgramArguments</key>
|
|
204
204
|
<array>
|
|
205
205
|
<string>/usr/local/bin/node</string>
|
|
206
|
-
<string>/Users/you/
|
|
206
|
+
<string>/Users/you/pigeondrop/server.js</string>
|
|
207
207
|
</array>
|
|
208
208
|
<key>EnvironmentVariables</key>
|
|
209
209
|
<dict>
|
|
@@ -212,7 +212,7 @@ does not expand `~`, and needs the full path to node:
|
|
|
212
212
|
</dict>
|
|
213
213
|
<key>RunAtLoad</key> <true/>
|
|
214
214
|
<key>KeepAlive</key> <true/>
|
|
215
|
-
<key>StandardErrorPath</key> <string>/tmp/
|
|
215
|
+
<key>StandardErrorPath</key> <string>/tmp/pigeondrop.log</string>
|
|
216
216
|
</dict>
|
|
217
217
|
</plist>
|
|
218
218
|
```
|
|
@@ -220,17 +220,17 @@ does not expand `~`, and needs the full path to node:
|
|
|
220
220
|
`which node` gives the path to use. Then load it:
|
|
221
221
|
|
|
222
222
|
```bash
|
|
223
|
-
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.
|
|
223
|
+
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.pigeondrop.plist
|
|
224
224
|
```
|
|
225
225
|
|
|
226
226
|
```bash
|
|
227
|
-
launchctl print gui/$(id -u)/com.
|
|
227
|
+
launchctl print gui/$(id -u)/com.pigeondrop
|
|
228
228
|
```
|
|
229
229
|
|
|
230
230
|
To stop it for good:
|
|
231
231
|
|
|
232
232
|
```bash
|
|
233
|
-
launchctl bootout gui/$(id -u)/com.
|
|
233
|
+
launchctl bootout gui/$(id -u)/com.pigeondrop
|
|
234
234
|
```
|
|
235
235
|
|
|
236
236
|
---
|
|
@@ -243,7 +243,7 @@ PowerShell 7.
|
|
|
243
243
|
## Install it
|
|
244
244
|
|
|
245
245
|
```powershell
|
|
246
|
-
irm https://raw.githubusercontent.com/cventour/
|
|
246
|
+
irm https://raw.githubusercontent.com/cventour/pigeondrop/main/install.ps1 -OutFile install.ps1
|
|
247
247
|
```
|
|
248
248
|
|
|
249
249
|
Read it — it is about a hundred lines and does nothing surprising. Then:
|
|
@@ -255,14 +255,14 @@ powershell -ExecutionPolicy Bypass -File .\install.ps1
|
|
|
255
255
|
Windows blocks downloaded scripts by default, which is why that is longer than
|
|
256
256
|
`.\install.ps1`. It applies to this one run and changes nothing on the machine.
|
|
257
257
|
|
|
258
|
-
It installs to `$HOME\
|
|
258
|
+
It installs to `$HOME\pigeondrop`, checks your Node version, and starts the
|
|
259
259
|
server. Open http://localhost:3001.
|
|
260
260
|
|
|
261
261
|
Options:
|
|
262
262
|
|
|
263
263
|
| Option | Meaning |
|
|
264
264
|
|---|---|
|
|
265
|
-
| `-Dir` | Where to install. Default `$HOME\
|
|
265
|
+
| `-Dir` | Where to install. Default `$HOME\pigeondrop`. |
|
|
266
266
|
| `-Root` | The directory to serve. Default `<install dir>\files`. |
|
|
267
267
|
| `-Port` | Default `3001`. |
|
|
268
268
|
| `-Bind` | Bind address. Default `127.0.0.1`. |
|
|
@@ -285,8 +285,8 @@ Run the script again later and it updates the copy it installed.
|
|
|
285
285
|
The script saves typing, not much else. The manual version is three lines:
|
|
286
286
|
|
|
287
287
|
```powershell
|
|
288
|
-
git clone https://github.com/cventour/
|
|
289
|
-
cd
|
|
288
|
+
git clone https://github.com/cventour/pigeondrop.git
|
|
289
|
+
cd pigeondrop
|
|
290
290
|
node server.js
|
|
291
291
|
```
|
|
292
292
|
|
|
@@ -294,9 +294,9 @@ Windows does not ship with git, so on a fresh machine that first line fails.
|
|
|
294
294
|
Take the zip instead — this needs nothing that is not already there:
|
|
295
295
|
|
|
296
296
|
```powershell
|
|
297
|
-
irm https://github.com/cventour/
|
|
298
|
-
Expand-Archive
|
|
299
|
-
cd
|
|
297
|
+
irm https://github.com/cventour/pigeondrop/archive/refs/heads/main.zip -OutFile pigeondrop.zip
|
|
298
|
+
Expand-Archive pigeondrop.zip -DestinationPath .
|
|
299
|
+
cd pigeondrop-main
|
|
300
300
|
node server.js
|
|
301
301
|
```
|
|
302
302
|
|
|
@@ -340,7 +340,7 @@ Windows Firewall will block the port even once it is bound, and the prompt is
|
|
|
340
340
|
easy to miss. Allow it from an **administrator** PowerShell:
|
|
341
341
|
|
|
342
342
|
```powershell
|
|
343
|
-
New-NetFirewallRule -DisplayName "
|
|
343
|
+
New-NetFirewallRule -DisplayName "Pigeondrop" -Direction Inbound -Protocol TCP -LocalPort 3001 -Action Allow
|
|
344
344
|
```
|
|
345
345
|
|
|
346
346
|
Others open `http://<that-address>:3001`. Read
|
|
@@ -349,7 +349,7 @@ Others open `http://<that-address>:3001`. Read
|
|
|
349
349
|
To close it again afterwards:
|
|
350
350
|
|
|
351
351
|
```powershell
|
|
352
|
-
Remove-NetFirewallRule -DisplayName "
|
|
352
|
+
Remove-NetFirewallRule -DisplayName "Pigeondrop"
|
|
353
353
|
```
|
|
354
354
|
|
|
355
355
|
## Keep it running
|
|
@@ -358,7 +358,7 @@ The window that started it owns it. To put it in the background for this
|
|
|
358
358
|
session:
|
|
359
359
|
|
|
360
360
|
```powershell
|
|
361
|
-
$p = Start-Process node -ArgumentList "server.js" -WorkingDirectory "$HOME\
|
|
361
|
+
$p = Start-Process node -ArgumentList "server.js" -WorkingDirectory "$HOME\pigeondrop" -WindowStyle Hidden -PassThru
|
|
362
362
|
```
|
|
363
363
|
|
|
364
364
|
`-PassThru` hands back the process, so you can stop it again:
|
|
@@ -372,7 +372,7 @@ PowerShell. Install it somewhere outside your profile first, since the task
|
|
|
372
372
|
runs as SYSTEM:
|
|
373
373
|
|
|
374
374
|
```powershell
|
|
375
|
-
powershell -ExecutionPolicy Bypass -File .\install.ps1 -Dir C:\
|
|
375
|
+
powershell -ExecutionPolicy Bypass -File .\install.ps1 -Dir C:\pigeondrop -NoStart
|
|
376
376
|
```
|
|
377
377
|
|
|
378
378
|
Set every setting you need machine-wide, because SYSTEM does not see your
|
|
@@ -384,9 +384,9 @@ account's variables:
|
|
|
384
384
|
```
|
|
385
385
|
|
|
386
386
|
```powershell
|
|
387
|
-
$action = New-ScheduledTaskAction -Execute (Get-Command node).Source -Argument "server.js" -WorkingDirectory "C:\
|
|
387
|
+
$action = New-ScheduledTaskAction -Execute (Get-Command node).Source -Argument "server.js" -WorkingDirectory "C:\pigeondrop"
|
|
388
388
|
$trigger = New-ScheduledTaskTrigger -AtStartup
|
|
389
|
-
Register-ScheduledTask -TaskName
|
|
389
|
+
Register-ScheduledTask -TaskName Pigeondrop -Action $action -Trigger $trigger -User SYSTEM -RunLevel Highest
|
|
390
390
|
```
|
|
391
391
|
|
|
392
392
|
`(Get-Command node).Source` resolves the full path to node.exe, because SYSTEM
|
|
@@ -395,11 +395,11 @@ does not necessarily share your `PATH`.
|
|
|
395
395
|
Start it now rather than waiting for a reboot:
|
|
396
396
|
|
|
397
397
|
```powershell
|
|
398
|
-
Start-ScheduledTask -TaskName
|
|
398
|
+
Start-ScheduledTask -TaskName Pigeondrop
|
|
399
399
|
```
|
|
400
400
|
|
|
401
401
|
```powershell
|
|
402
|
-
Get-ScheduledTask -TaskName
|
|
402
|
+
Get-ScheduledTask -TaskName Pigeondrop | Get-ScheduledTaskInfo
|
|
403
403
|
```
|
|
404
404
|
|
|
405
405
|
A `LastTaskResult` of `267009` is not an error — it is `0x41301`, "the task is
|
|
@@ -408,11 +408,11 @@ currently running", which is what you want to see.
|
|
|
408
408
|
Stop it, or remove it entirely:
|
|
409
409
|
|
|
410
410
|
```powershell
|
|
411
|
-
Stop-ScheduledTask -TaskName
|
|
411
|
+
Stop-ScheduledTask -TaskName Pigeondrop
|
|
412
412
|
```
|
|
413
413
|
|
|
414
414
|
```powershell
|
|
415
|
-
Unregister-ScheduledTask -TaskName
|
|
415
|
+
Unregister-ScheduledTask -TaskName Pigeondrop -Confirm:$false
|
|
416
416
|
```
|
|
417
417
|
|
|
418
418
|
---
|
|
@@ -426,7 +426,7 @@ All optional, all environment variables, identical on every platform.
|
|
|
426
426
|
| `REPO_ROOT` | `./files`, in the working directory | The directory it manages. Created if missing. |
|
|
427
427
|
| `HOST` | `127.0.0.1` | Bind address. Loopback by default, on purpose. |
|
|
428
428
|
| `PORT` | `3001` | |
|
|
429
|
-
| `REPO_TITLE` | `
|
|
429
|
+
| `REPO_TITLE` | `Pigeondrop` | Page title and heading |
|
|
430
430
|
| `REPO_SUBTITLE` | `Drop files in. Take files out.` | Line under the heading |
|
|
431
431
|
| `SETTINGS_FILE` | `$STATE_DIRECTORY/settings.json` under systemd, else `settings.json` beside `server.js` (in the working directory when run through npx or a global install) | Where changes made on the Settings page are kept. Mode 600; not in the managed directory. It must be somewhere the service can write. |
|
|
432
432
|
|
|
@@ -442,7 +442,7 @@ How you set them is the only thing that differs:
|
|
|
442
442
|
|
|
443
443
|
## Scan uploads with ICAP
|
|
444
444
|
|
|
445
|
-
**Settings** in the toolbar points
|
|
445
|
+
**Settings** in the toolbar points Pigeondrop at an ICAP server (MetaDefender
|
|
446
446
|
ICAP Server, c-icap with ClamAV, anything that speaks ICAP `REQMOD`). Each
|
|
447
447
|
upload is sent there once it has arrived, and appears in the listing only if
|
|
448
448
|
the scanner passes it. A blocked file is deleted and the upload card shows the
|
|
@@ -463,7 +463,7 @@ with no licence. It uses the values in the form, so a server can be checked
|
|
|
463
463
|
before saving.
|
|
464
464
|
|
|
465
465
|
If the certificate is self-signed or issued by a private CA, either start
|
|
466
|
-
|
|
466
|
+
Pigeondrop with `NODE_EXTRA_CA_CERTS` set to that CA's PEM file, or turn off
|
|
467
467
|
**Verify the certificate**. Use the name the certificate was issued for as
|
|
468
468
|
Server.
|
|
469
469
|
|
|
@@ -479,7 +479,7 @@ means, are not.
|
|
|
479
479
|
## There is no authentication
|
|
480
480
|
|
|
481
481
|
Read that again before you expose it. Anyone who can reach it can upload,
|
|
482
|
-
rename and delete.
|
|
482
|
+
rename and delete. Pigeondrop assumes it sits on a network you trust — a home
|
|
483
483
|
or office LAN, a VPN, a lab segment.
|
|
484
484
|
|
|
485
485
|
That is a scope decision, not an oversight. Authentication done badly is worse
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pigeondrop",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "A small self-hosted file manager with no dependencies. Drag-and-drop upload with progress, browse, rename, delete, download files or folders as zip.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
7
|
-
"bin": { "pigeondrop": "./server.js"
|
|
7
|
+
"bin": { "pigeondrop": "./server.js" },
|
|
8
8
|
"scripts": { "start": "node server.js" },
|
|
9
9
|
"engines": { "node": ">=20" },
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"keywords": ["file-manager", "upload", "self-hosted", "zero-dependency", "http"],
|
|
12
|
-
"repository": { "type": "git", "url": "git+https://github.com/cventour/
|
|
12
|
+
"repository": { "type": "git", "url": "git+https://github.com/cventour/pigeondrop.git" },
|
|
13
13
|
"files": ["server.js", "public", "icon.svg", "README.md", "LICENSE", "CHANGELOG.md"]
|
|
14
14
|
}
|
package/public/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>
|
|
6
|
+
<title>Pigeondrop</title>
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'> <rect x='2' y='2' width='60' height='60' rx='13' fill='%232d3339'/> <g fill='%230f1113'> <path d='M8 28V17a7 7 0 0 1 14 0v11z'/> <path d='M25 28V17a7 7 0 0 1 14 0v11z'/> <path d='M8 51V40a7 7 0 0 1 14 0v11z'/> <path d='M25 51V40a7 7 0 0 1 14 0v11z'/> <path d='M42 51V40a7 7 0 0 1 14 0v11z'/> </g> <path d='M42 28V17a7 7 0 0 1 14 0v11z' fill='%23ff6a1f'/> <path d='M44.5 19.5h9v6.5h-9z' fill='%231b1e21'/> <path d='M44.5 19.5l4.5 3.5 4.5-3.5' fill='none' stroke='%23ff6a1f' stroke-width='1.3'/> <path d='M6 30h52M6 53h52' stroke='%23c8ced4' stroke-width='2.5' stroke-linecap='round'/> </svg>">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
@@ -45,7 +45,7 @@ body{margin:0;background:var(--panel);color:var(--ink);font-family:var(--sans);
|
|
|
45
45
|
|
|
46
46
|
.wrap{max-width:1080px;margin:0 auto;padding:0 var(--gut)}
|
|
47
47
|
header{padding:clamp(28px,5vw,48px) 0 20px}
|
|
48
|
-
h1{font-family:var(--narrow);font-weight:700;margin:0;
|
|
48
|
+
h1{font-family:var(--narrow);font-weight:700;margin:0;
|
|
49
49
|
font-size:clamp(1.6rem,4vw,2.5rem);line-height:1;letter-spacing:-.02em}
|
|
50
50
|
.brand{display:flex;align-items:center;gap:14px}
|
|
51
51
|
.brand svg{width:clamp(36px,6vw,48px);height:auto;flex:none}
|
|
@@ -103,6 +103,10 @@ button.act.primary:hover{background:var(--signal);color:#0d0f11;border-color:var
|
|
|
103
103
|
.q-retry:hover{background:var(--signal);color:#0d0f11;border-color:var(--signal)}
|
|
104
104
|
|
|
105
105
|
/* --- listing --- */
|
|
106
|
+
.search{display:block;width:100%;padding:10px 12px;margin:0 0 -1px;
|
|
107
|
+
font-family:var(--mono);font-size:.85rem;color:var(--ink);
|
|
108
|
+
background:var(--panel);border:1px solid var(--rule)}
|
|
109
|
+
.search:focus{outline:none;border-color:var(--signal)}
|
|
106
110
|
.tablewrap{border:1px solid var(--rule);background:var(--panel-lo);overflow-x:auto}
|
|
107
111
|
table{width:100%;border-collapse:collapse;min-width:520px}
|
|
108
112
|
th{text-align:left;font-family:var(--mono);font-weight:500;font-size:.75rem;
|
|
@@ -240,7 +244,7 @@ footer a{color:var(--steel-dim)}
|
|
|
240
244
|
<header>
|
|
241
245
|
<div class="brand">
|
|
242
246
|
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"> <rect x="2" y="2" width="60" height="60" rx="13" fill="#2d3339"/> <g fill="#0f1113"> <path d="M8 28V17a7 7 0 0 1 14 0v11z"/> <path d="M25 28V17a7 7 0 0 1 14 0v11z"/> <path d="M8 51V40a7 7 0 0 1 14 0v11z"/> <path d="M25 51V40a7 7 0 0 1 14 0v11z"/> <path d="M42 51V40a7 7 0 0 1 14 0v11z"/> </g> <path d="M42 28V17a7 7 0 0 1 14 0v11z" fill="#ff6a1f"/> <path d="M44.5 19.5h9v6.5h-9z" fill="#1b1e21"/> <path d="M44.5 19.5l4.5 3.5 4.5-3.5" fill="none" stroke="#ff6a1f" stroke-width="1.3"/> <path d="M6 30h52M6 53h52" stroke="#c8ced4" stroke-width="2.5" stroke-linecap="round"/> </svg>
|
|
243
|
-
<h1 id="title">
|
|
247
|
+
<h1 id="title">Pigeondrop</h1>
|
|
244
248
|
</div>
|
|
245
249
|
<p class="sub" id="subtitle">Drop files in. Take files out.</p>
|
|
246
250
|
</header>
|
|
@@ -262,6 +266,7 @@ footer a{color:var(--steel-dim)}
|
|
|
262
266
|
<div class="queue" id="queue"></div>
|
|
263
267
|
<p class="msg" id="msg" role="status"></p>
|
|
264
268
|
|
|
269
|
+
<input type="search" class="search" id="search" placeholder="Filter by name" aria-label="Filter files by name" autocomplete="off" spellcheck="false">
|
|
265
270
|
<div class="tablewrap">
|
|
266
271
|
<table>
|
|
267
272
|
<thead><tr>
|
|
@@ -278,7 +283,7 @@ footer a{color:var(--steel-dim)}
|
|
|
278
283
|
<span id="rootname"></span>
|
|
279
284
|
<span id="scanstate"></span>
|
|
280
285
|
<span>no authentication — trusted networks only</span>
|
|
281
|
-
<span><a href="https://github.com/cventour/
|
|
286
|
+
<span><a href="https://github.com/cventour/pigeondrop">pigeondrop</a></span>
|
|
282
287
|
</footer>
|
|
283
288
|
</div>
|
|
284
289
|
|
|
@@ -462,7 +467,7 @@ function drawCrumbs() {
|
|
|
462
467
|
$('drop-target').textContent = cwd;
|
|
463
468
|
}
|
|
464
469
|
|
|
465
|
-
async function go(dir) { cwd = dir || '/'; selected.clear(); await refresh(); }
|
|
470
|
+
async function go(dir) { cwd = dir || '/'; selected.clear(); $('search').value = ''; await refresh(); }
|
|
466
471
|
|
|
467
472
|
// --- selection and download ------------------------------------------------
|
|
468
473
|
// Paths of the ticked rows in this folder. A single file downloads as itself;
|
|
@@ -566,7 +571,8 @@ async function asJson(r) {
|
|
|
566
571
|
// person scanning for a folder expect. Folders have no size, so under Size
|
|
567
572
|
// they fall back to name order. Remembered per browser, not per folder.
|
|
568
573
|
let sort = { key: 'name', dir: 1 };
|
|
569
|
-
|
|
574
|
+
// Falls back to the key used before the rename to Pigeondrop.
|
|
575
|
+
try { Object.assign(sort, JSON.parse(localStorage.getItem('pigeondrop.sort') || localStorage.getItem('pigeonhole.sort')) || {}); } catch {}
|
|
570
576
|
// A first click on Size or Modified wants the biggest or newest first.
|
|
571
577
|
const FIRST_DIR = { name: 1, size: -1, mtime: -1 };
|
|
572
578
|
let items = [];
|
|
@@ -590,11 +596,13 @@ for (const th of document.querySelectorAll('th[data-k]')) {
|
|
|
590
596
|
th.querySelector('button').onclick = () => {
|
|
591
597
|
const k = th.dataset.k;
|
|
592
598
|
sort = k === sort.key ? { key: k, dir: -sort.dir } : { key: k, dir: FIRST_DIR[k] };
|
|
593
|
-
try { localStorage.setItem('
|
|
599
|
+
try { localStorage.setItem('pigeondrop.sort', JSON.stringify(sort)); } catch {}
|
|
594
600
|
render();
|
|
595
601
|
};
|
|
596
602
|
}
|
|
597
603
|
|
|
604
|
+
$('search').oninput = () => render();
|
|
605
|
+
|
|
598
606
|
async function refresh() {
|
|
599
607
|
drawCrumbs();
|
|
600
608
|
let data;
|
|
@@ -630,7 +638,15 @@ function render() {
|
|
|
630
638
|
return;
|
|
631
639
|
}
|
|
632
640
|
|
|
633
|
-
|
|
641
|
+
const q = $('search').value.trim().toLowerCase();
|
|
642
|
+
const list = q ? items.filter(it => it.name.toLowerCase().includes(q)) : items;
|
|
643
|
+
if (q && !list.length) {
|
|
644
|
+
const tr = document.createElement('tr');
|
|
645
|
+
tr.innerHTML = '<td colspan="5" class="empty">No names match.</td>';
|
|
646
|
+
rows.append(tr);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
for (const it of sorted(list)) {
|
|
634
650
|
const full = (cwd === '/' ? '' : cwd) + '/' + it.name;
|
|
635
651
|
const tr = document.createElement('tr');
|
|
636
652
|
tr.innerHTML = `
|
package/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
2
|
+
// Pigeondrop — a small self-hosted file manager.
|
|
3
3
|
//
|
|
4
4
|
// Node standard library only. Uploads arrive as a raw PUT body rather than
|
|
5
5
|
// multipart/form-data, which removes the only reason this would need a
|
|
@@ -40,7 +40,7 @@ const SETTINGS_FILE = path.resolve(process.env.SETTINGS_FILE
|
|
|
40
40
|
|| path.join(process.env.STATE_DIRECTORY?.split(':')[0] || CODE_DIR, 'settings.json'));
|
|
41
41
|
|
|
42
42
|
// Branding is configuration, not code: one binary, any deployment.
|
|
43
|
-
const TITLE = process.env.REPO_TITLE || '
|
|
43
|
+
const TITLE = process.env.REPO_TITLE || 'Pigeondrop';
|
|
44
44
|
const SUBTITLE = process.env.REPO_SUBTITLE || 'Drop files in. Take files out.';
|
|
45
45
|
|
|
46
46
|
// --- path safety -----------------------------------------------------------
|
|
@@ -284,7 +284,7 @@ async function icapScan(source, name, cfg) {
|
|
|
284
284
|
await once(sock, icapReady(cfg), { signal: ac.signal });
|
|
285
285
|
const put = async (b) => { if (!sock.write(b)) await once(sock, 'drain', { signal: ac.signal }); };
|
|
286
286
|
|
|
287
|
-
const http = `PUT /${encodeURIComponent(name)} HTTP/1.1\r\nHost:
|
|
287
|
+
const http = `PUT /${encodeURIComponent(name)} HTTP/1.1\r\nHost: pigeondrop\r\n` +
|
|
288
288
|
`Content-Disposition: attachment; filename="${name.replace(/[^\x20-\x7E]|["\\]/g, '_')}"\r\n` +
|
|
289
289
|
`Content-Type: application/octet-stream\r\nContent-Length: ${source.size}\r\n\r\n`;
|
|
290
290
|
await put(`REQMOD icap://${cfg.host}:${cfg.port}/${cfg.service} ICAP/1.0\r\n` +
|
|
@@ -337,9 +337,9 @@ async function icapTest(cfg) {
|
|
|
337
337
|
});
|
|
338
338
|
if (options.error) return { ok: false, error: options.error };
|
|
339
339
|
|
|
340
|
-
const sample = Buffer.from('
|
|
340
|
+
const sample = Buffer.from('pigeondrop connection test\n');
|
|
341
341
|
const scan = await icapScan({ size: sample.length, stream: () => Readable.from([sample]) },
|
|
342
|
-
'
|
|
342
|
+
'pigeondrop-test.txt', { ...cfg, timeout: 20 });
|
|
343
343
|
const ms = Date.now() - t0;
|
|
344
344
|
if (scan.verdict === 'clean') return { ok: true, server: options.server, methods: options.methods, ms };
|
|
345
345
|
return {
|