pigeondrop 1.6.1 → 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 CHANGED
@@ -1,5 +1,10 @@
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
+
3
8
  ## v1.6.1 — 2 November 2026
4
9
 
5
10
  - Changed: **The page heading reads Pigeonhole.** It is no longer shown in capitals.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- <img src="icon.svg" width="88" alt="Pigeonhole icon: a wall of arched pigeonholes, one holding a letter">
1
+ <img src="icon.svg" width="88" alt="Pigeondrop icon: a wall of arched pigeonholes, one holding a letter">
2
2
 
3
- # Pigeonhole
3
+ # Pigeondrop
4
4
 
5
- A file pigeonhole you can stand up in a minute. Put files in, let people take
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
@@ -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 `~/pigeonhole`, checks your Node version, and starts the
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 `~/pigeonhole`. |
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`. |
@@ -98,7 +98,7 @@ The script saves typing, not much else. The manual version is three lines:
98
98
 
99
99
  ```bash
100
100
  git clone https://github.com/cventour/pigeondrop.git
101
- cd pigeonhole
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 > pigeonhole.log 2>&1 &
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/pigeonhole.service`:
156
+ **Linux, with systemd.** Save as `/etc/systemd/system/pigeondrop.service`:
157
157
 
158
158
  ```ini
159
159
  [Unit]
160
- Description=Pigeonhole
160
+ Description=Pigeondrop
161
161
  After=network.target
162
162
 
163
163
  [Service]
164
164
  Type=simple
165
- User=pigeonhole
166
- WorkingDirectory=/opt/pigeonhole
167
- ExecStart=/usr/bin/node /opt/pigeonhole/server.js
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. Pigeonhole finds it by itself: /var/lib/pigeonhole.
171
- StateDirectory=pigeonhole
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 pigeonhole
186
+ sudo systemctl enable --now pigeondrop
187
187
  ```
188
188
 
189
189
  ```bash
190
- systemctl status pigeonhole
190
+ systemctl status pigeondrop
191
191
  ```
192
192
 
193
193
  **macOS, with launchd.** Save as
194
- `~/Library/LaunchAgents/com.pigeonhole.plist`, with your own paths — launchd
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.pigeonhole</string>
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/pigeonhole/server.js</string>
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/pigeonhole.log</string>
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.pigeonhole.plist
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.pigeonhole
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.pigeonhole
233
+ launchctl bootout gui/$(id -u)/com.pigeondrop
234
234
  ```
235
235
 
236
236
  ---
@@ -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\pigeonhole`, checks your Node version, and starts the
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\pigeonhole`. |
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`. |
@@ -286,7 +286,7 @@ The script saves typing, not much else. The manual version is three lines:
286
286
 
287
287
  ```powershell
288
288
  git clone https://github.com/cventour/pigeondrop.git
289
- cd pigeonhole
289
+ cd pigeondrop
290
290
  node server.js
291
291
  ```
292
292
 
@@ -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 "Pigeonhole" -Direction Inbound -Protocol TCP -LocalPort 3001 -Action Allow
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 "Pigeonhole"
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\pigeonhole" -WindowStyle Hidden -PassThru
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:\pigeonhole -NoStart
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:\pigeonhole"
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 Pigeonhole -Action $action -Trigger $trigger -User SYSTEM -RunLevel Highest
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 Pigeonhole
398
+ Start-ScheduledTask -TaskName Pigeondrop
399
399
  ```
400
400
 
401
401
  ```powershell
402
- Get-ScheduledTask -TaskName Pigeonhole | Get-ScheduledTaskInfo
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 Pigeonhole
411
+ Stop-ScheduledTask -TaskName Pigeondrop
412
412
  ```
413
413
 
414
414
  ```powershell
415
- Unregister-ScheduledTask -TaskName Pigeonhole -Confirm:$false
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` | `Pigeonhole` | Page title and heading |
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 Pigeonhole at an ICAP server (MetaDefender
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
- Pigeonhole with `NODE_EXTRA_CA_CERTS` set to that CA's PEM file, or turn off
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. Pigeonhole assumes it sits on a network you trust — a home
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,10 +1,10 @@
1
1
  {
2
2
  "name": "pigeondrop",
3
- "version": "1.6.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", "pigeonhole": "./server.js" },
7
+ "bin": { "pigeondrop": "./server.js" },
8
8
  "scripts": { "start": "node server.js" },
9
9
  "engines": { "node": ">=20" },
10
10
  "license": "MIT",
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>Pigeonhole</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>
@@ -244,7 +244,7 @@ footer a{color:var(--steel-dim)}
244
244
  <header>
245
245
  <div class="brand">
246
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>
247
- <h1 id="title">Pigeonhole</h1>
247
+ <h1 id="title">Pigeondrop</h1>
248
248
  </div>
249
249
  <p class="sub" id="subtitle">Drop files in. Take files out.</p>
250
250
  </header>
@@ -283,7 +283,7 @@ footer a{color:var(--steel-dim)}
283
283
  <span id="rootname"></span>
284
284
  <span id="scanstate"></span>
285
285
  <span>no authentication — trusted networks only</span>
286
- <span><a href="https://github.com/cventour/pigeondrop">pigeonhole</a></span>
286
+ <span><a href="https://github.com/cventour/pigeondrop">pigeondrop</a></span>
287
287
  </footer>
288
288
  </div>
289
289
 
@@ -571,7 +571,8 @@ async function asJson(r) {
571
571
  // person scanning for a folder expect. Folders have no size, so under Size
572
572
  // they fall back to name order. Remembered per browser, not per folder.
573
573
  let sort = { key: 'name', dir: 1 };
574
- try { Object.assign(sort, JSON.parse(localStorage.getItem('pigeonhole.sort')) || {}); } catch {}
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 {}
575
576
  // A first click on Size or Modified wants the biggest or newest first.
576
577
  const FIRST_DIR = { name: 1, size: -1, mtime: -1 };
577
578
  let items = [];
@@ -595,7 +596,7 @@ for (const th of document.querySelectorAll('th[data-k]')) {
595
596
  th.querySelector('button').onclick = () => {
596
597
  const k = th.dataset.k;
597
598
  sort = k === sort.key ? { key: k, dir: -sort.dir } : { key: k, dir: FIRST_DIR[k] };
598
- try { localStorage.setItem('pigeonhole.sort', JSON.stringify(sort)); } catch {}
599
+ try { localStorage.setItem('pigeondrop.sort', JSON.stringify(sort)); } catch {}
599
600
  render();
600
601
  };
601
602
  }
package/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // Pigeonhole — a small self-hosted file manager.
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 || 'Pigeonhole';
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: pigeonhole\r\n` +
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('pigeonhole connection test\n');
340
+ const sample = Buffer.from('pigeondrop connection test\n');
341
341
  const scan = await icapScan({ size: sample.length, stream: () => Readable.from([sample]) },
342
- 'pigeonhole-test.txt', { ...cfg, timeout: 20 });
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 {