Skip to content

Commit f98cf7e

Browse files
feat: welcome OpenCoperLock.txt in each new space + feature the Linux wizard
- New non-ZK top-level spaces get a short, deletable OpenCoperLock.txt in their root (bilingual) explaining how spaces work and that files placed at the Drive ROOT via WebDAV/API stay invisible in the web app. Best-effort: a quota/storage hiccup never fails space creation. ZK vaults are skipped (the server can't write into a blind vault). - Linux wizard: verify the URL + token (PROPFIND) before saving, with clear hints per HTTP status; note proxy support (curl/rclone honour HTTP(S)_PROXY). - README: prominent 'Client quick-setup' section with the wizard one-liner and the Windows one-liner.
1 parent c39b567 commit f98cf7e

3 files changed

Lines changed: 94 additions & 4 deletions

File tree

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,34 @@ the source.
2020
- **[Deployment](docs/DEPLOYMENT.md)** — bare-metal (PM2) and Docker, behind nginx.
2121
- **[Contributing](CONTRIBUTING.md)** · **[Security policy](SECURITY.md)**
2222

23+
## 🚀 Client quick-setup (right-click upload & network drive)
24+
25+
Turn any machine into a one-click uploader — right-click a file → send it straight to a
26+
**`ComputerShared`** space in your Drive. All of it lives in [`scripts/`](scripts/) and uses your
27+
personal API token over WebDAV (no new server code).
28+
29+
**Linux — one command (interactive wizard):** installs the right-click integration and/or mounts
30+
your Drive as a folder (rclone). It asks for your instance URL + token once, checks the connection,
31+
and configures everything (it honours `HTTP(S)_PROXY` too):
32+
33+
```bash
34+
curl -fsSL https://raw.githubusercontent.com/softpython2884/OpenCoperLock/main/scripts/opencoperlock-linux-wizard.sh | bash
35+
```
36+
37+
**Windows — one line in PowerShell** (no admin) for the *Send to → OpenCoperLock* + *Drop on
38+
OpenCoperLock* right-click entries:
39+
40+
```powershell
41+
irm https://raw.githubusercontent.com/softpython2884/OpenCoperLock/main/scripts/send-to/windows/install-windows.ps1 | iex
42+
```
43+
44+
Also in `scripts/`: a **WebDAV network-drive** mounter for Windows, and small **right-click-menu
45+
tidy tools** (a GUI manager + a quick declutter for the noisy Windows 11 entries). See
46+
[`scripts/send-to/`](scripts/send-to/) and the **Desktop integrations** section of `/docs`.
47+
48+
> Replace the URL host with your own instance. On the maintainer's instance it's
49+
> `https://copper.forgenet.fr`; the scripts default to that but prompt for yours.
50+
2351
## What it does
2452

2553
- **Drive** — browse, upload (streaming), download and delete files and folders, with a

apps/api/src/routes/folders.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,52 @@
11
import type { FastifyPluginAsync } from 'fastify';
2+
import { Readable } from 'node:stream';
3+
import type { Folder } from '@prisma/client';
24
import { createFolderSchema, randomToken, updateFolderSchema } from '@opencoperlock/shared';
35
import { prisma } from '../db.js';
46
import { parseOr400 } from '../lib/validate.js';
57
import { toPublicFolder } from '../lib/serialize.js';
68
import { hardDeleteFolder, trashFolder } from '../services/trash.js';
9+
import { storeUserFile } from '../services/upload.js';
710
import { audit } from '../services/audit.js';
11+
import type { AppContext } from '../context.js';
12+
13+
/** Bilingual welcome note dropped into the root of each new space. Users may delete it freely. */
14+
function welcomeText(space: Folder): string {
15+
const mode = space.isPublic
16+
? 'Public / Open (plaintext, direct URLs)'
17+
: 'Server-encrypted (AES-256-GCM at rest)';
18+
return [
19+
`OpenCoperLock - "${space.name}"`,
20+
'='.repeat(40),
21+
'',
22+
`[FR] Ceci est un espace (${mode}). Les fichiers que vous deposez ICI apparaissent`,
23+
'dans l\'application web OpenCoperLock. Bon a savoir : un fichier place a la RACINE du',
24+
'Drive via WebDAV ou l\'API ne s\'affiche PAS dans l\'app web - rangez toujours vos',
25+
'fichiers dans un espace comme celui-ci. Vous pouvez supprimer ce fichier sans risque.',
26+
'',
27+
`[EN] This is a space (${mode}). Files you drop HERE show up in the OpenCoperLock web`,
28+
'app. Heads up: a file placed at the Drive ROOT over WebDAV or the API does NOT appear',
29+
'in the web app - always keep your files inside a space like this one. You can delete',
30+
'this file safely.',
31+
'',
32+
].join('\n');
33+
}
34+
35+
/** Best-effort: put an OpenCoperLock.txt in a brand-new space's root. Never fails folder creation. */
36+
async function seedWelcomeFile(ctx: AppContext, ownerId: string, space: Folder): Promise<void> {
37+
try {
38+
await storeUserFile(ctx, {
39+
ownerId,
40+
folderId: space.id,
41+
stream: Readable.from([Buffer.from(welcomeText(space), 'utf8')]),
42+
filename: 'OpenCoperLock.txt',
43+
mimetype: 'text/plain',
44+
public: space.isPublic,
45+
});
46+
} catch {
47+
/* quota exhausted, storage hiccup, etc. - the space is still created. */
48+
}
49+
}
850

951
/** Collect a folder's id plus all descendant ids (breadth-first), scoped to one owner's personal
1052
* Drive (Shared-Space folders carry a spaceId and are handled by the /spaces routes). */
@@ -70,6 +112,9 @@ export const folderRoutes: FastifyPluginAsync = async (app) => {
70112
},
71113
});
72114
await audit(req, 'folder.create', { target: folder.id });
115+
// A brand-new top-level space (not a subfolder, not a blind ZK vault) gets a short readme in
116+
// its root explaining how spaces work. Best-effort and deletable.
117+
if (!body.parentId && !isZk) await seedWelcomeFile(app.ctx, req.user!.id, folder);
73118
return reply.code(201).send({ folder: toPublicFolder(folder) });
74119
} catch {
75120
return reply.code(409).send({ error: 'A folder with that name already exists here' });

scripts/opencoperlock-linux-wizard.sh

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,29 @@ asks() { local p="$1" a; printf '%s' "$p" >&2; IFS= read -rs a <&3 || true; prin
2525

2626
say "=== OpenCoperLock - Linux setup wizard ==="
2727
echo
28+
if [ -n "${HTTPS_PROXY:-${https_proxy:-}}" ]; then say "(using proxy ${HTTPS_PROXY:-$https_proxy})"; fi
2829

2930
default_base="https://copper.forgenet.fr/api/dav"
30-
BASE="$(ask "WebDAV base URL [$default_base]: " "$default_base")"
31-
BASE="${BASE%/}"
32-
TOKEN="$(asks "Paste your OpenCoperLock API token (ocl_...): ")"
33-
[ -n "$TOKEN" ] || { warn "No token provided - aborting."; exit 1; }
31+
32+
# Collect + verify the URL and token. curl goes through HTTP(S)_PROXY automatically if set.
33+
while true; do
34+
BASE="$(ask "WebDAV base URL [$default_base]: " "$default_base")"
35+
BASE="${BASE%/}"
36+
TOKEN="$(asks "Paste your OpenCoperLock API token (ocl_...): ")"
37+
[ -n "$TOKEN" ] || { warn "No token provided."; continue; }
38+
39+
printf 'Checking the connection... ' >&2
40+
code="$(curl -fsS -o /dev/null -w '%{http_code}' -u "me:$TOKEN" -X PROPFIND -H 'Depth: 0' "$BASE/" 2>/dev/null || true)"
41+
if [ "$code" = "207" ]; then ok "OK (server reachable, token valid)."; break; fi
42+
warn "Failed (HTTP ${code:-no response})."
43+
case "$code" in
44+
401) warn " -> the token is wrong or lacks read/write scope." ;;
45+
404|405) warn " -> the URL looks off. It usually ends in /dav or /api/dav." ;;
46+
000|"") warn " -> could not reach the host (DNS/TLS/proxy/offline?)." ;;
47+
esac
48+
retry="$(ask "Try again? [Y/n]: " "Y")"
49+
case "${retry,,}" in n|no|non) warn "Aborting."; exit 1 ;; esac
50+
done
3451

3552
# Save config (readable only by you).
3653
umask 077

0 commit comments

Comments
 (0)