Skip to content

Commit 75bc3e3

Browse files
author
FileShot
committed
ci: draft releases by default; scaffold WinFsp true drive
1 parent dd9db06 commit 75bc3e3

7 files changed

Lines changed: 933 additions & 54 deletions

File tree

.github/workflows/build.yml

Lines changed: 32 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ on:
1010
description: 'Release tag to build/upload (e.g. v1.4.8)'
1111
required: true
1212
type: string
13+
publish_release:
14+
description: 'Publish the GitHub Release (default: create a draft so we can validate before shipping)'
15+
required: false
16+
type: boolean
17+
default: false
1318

1419
permissions:
1520
contents: write
@@ -32,32 +37,16 @@ jobs:
3237
run: npm install
3338

3439
- name: Build Windows
35-
run: npm run build:win
40+
run: npm run build:win -- --publish never
3641
env:
3742
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38-
39-
- name: Upload Windows assets to GitHub Release
40-
uses: softprops/action-gh-release@v2
41-
with:
42-
tag_name: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}
43-
files: |
44-
dist/**/*.exe
45-
dist/**/*.msi
46-
dist/**/*.zip
47-
dist/**/*.yml
48-
dist/**/*.blockmap
49-
fail_on_unmatched_files: false
5043

5144
- name: Upload Windows artifacts
5245
uses: actions/upload-artifact@v4
5346
with:
5447
name: windows-installer
5548
path: |
56-
dist/**/*.exe
57-
dist/**/*.msi
58-
dist/**/*.zip
59-
dist/**/*.yml
60-
dist/**/*.blockmap
49+
dist/**/*
6150
6251
build-macos:
6352
runs-on: macos-latest
@@ -76,31 +65,17 @@ jobs:
7665
run: npm install
7766

7867
- name: Build macOS
79-
run: npm run build:mac
68+
run: npm run build:mac -- --publish never
8069
env:
8170
CSC_IDENTITY_AUTO_DISCOVERY: false
8271
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
83-
84-
- name: Upload macOS assets to GitHub Release
85-
uses: softprops/action-gh-release@v2
86-
with:
87-
tag_name: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}
88-
files: |
89-
dist/**/*.dmg
90-
dist/**/*.zip
91-
dist/**/*.yml
92-
dist/**/*.blockmap
93-
fail_on_unmatched_files: false
9472

9573
- name: Upload macOS artifacts
9674
uses: actions/upload-artifact@v4
9775
with:
9876
name: macos-installer
9977
path: |
100-
dist/**/*.dmg
101-
dist/**/*.zip
102-
dist/**/*.yml
103-
dist/**/*.blockmap
78+
dist/**/*
10479
10580
build-linux:
10681
runs-on: ubuntu-latest
@@ -119,31 +94,34 @@ jobs:
11994
run: npm install
12095

12196
- name: Build Linux
122-
run: npm run build:linux
97+
run: npm run build:linux -- --publish never
12398
env:
12499
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
125-
126-
- name: Upload Linux assets to GitHub Release
127-
uses: softprops/action-gh-release@v2
128-
with:
129-
tag_name: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}
130-
files: |
131-
dist/**/*.AppImage
132-
dist/**/*.deb
133-
dist/**/*.rpm
134-
dist/**/*.zip
135-
dist/**/*.yml
136-
dist/**/*.blockmap
137-
fail_on_unmatched_files: false
138100

139101
- name: Upload Linux artifacts
140102
uses: actions/upload-artifact@v4
141103
with:
142104
name: linux-installer
143105
path: |
144-
dist/**/*.AppImage
145-
dist/**/*.deb
146-
dist/**/*.rpm
147-
dist/**/*.zip
148-
dist/**/*.yml
149-
dist/**/*.blockmap
106+
dist/**/*
107+
108+
create-release:
109+
needs: [build-windows, build-macos, build-linux]
110+
runs-on: ubuntu-latest
111+
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
112+
steps:
113+
- name: Download all artifacts
114+
uses: actions/download-artifact@v4
115+
116+
- name: Create / Update GitHub Release
117+
uses: softprops/action-gh-release@v2
118+
with:
119+
tag_name: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}
120+
draft: ${{ !(github.event_name == 'workflow_dispatch' && inputs.publish_release == true) }}
121+
fail_on_unmatched_files: true
122+
files: |
123+
windows-installer/**
124+
macos-installer/**
125+
linux-installer/**
126+
env:
127+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,8 @@ certificates/
3636
# Temporary files
3737
*.tmp
3838
*.temp
39+
40+
# Local workspace sentinels (not part of the product)
41+
.terminal_sentinel.txt
42+
sentinel.txt
43+
sentinel_cmd.txt

FILESHOT_VIRTUAL_DRIVE_SPEC.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# FileShot Virtual Drive (True Volume) — Spec + Implementation Plan
2+
3+
Status: **Not implemented yet** (current app uses `SUBST` mapping to a local folder; that cannot report custom capacity/free space in Explorer).
4+
5+
This document defines the **hard requirements** and a realistic implementation path for a true FileShot drive that appears in the OS file explorer as a mounted volume with a tier-based quota.
6+
7+
---
8+
9+
## 1) Hard requirements (non-negotiable)
10+
11+
### 1.1 Appears as a real drive / volume
12+
- **Windows:** shows under **“This PC”** as a drive letter (e.g. `F:`).
13+
- **macOS:** shows in Finder as a mounted volume.
14+
- **Linux:** shows as a mounted filesystem (e.g. under `/mnt/fileshot` or user-chosen).
15+
16+
### 1.2 Capacity + free space must reflect FileShot tier quota
17+
- Explorer/Finder must show:
18+
- **Total size** = tier quota (e.g. Free = **50 GB**)
19+
- **Free space** = quota remaining (quota - usage)
20+
- This must **not** mirror the host disk (no “it looks like C:” / “it’s just a folder”).
21+
22+
### 1.3 File operations map to FileShot cloud (ZKE)
23+
- Creating/copying a file into the drive uploads it to FileShot.
24+
- Browsing the drive shows the user’s remote FileShot files/folders.
25+
- Opening/reading a file downloads and decrypts it locally.
26+
- Deleting a file deletes it remotely (with safety settings/confirmation behavior defined below).
27+
28+
### 1.4 Optional password support
29+
- Users must be able to set an **optional password** per upload (or per folder default) from the drive workflow.
30+
- This must remain compatible with FileShot’s **zero-knowledge encryption** model.
31+
32+
---
33+
34+
## 2) Constraints we must respect
35+
36+
### 2.1 Filesystem drivers are OS-specific
37+
To report custom volume size/free space, we need a **real filesystem mount**:
38+
- Windows: **WinFsp** (chosen) or Dokan-style user-mode filesystem
39+
- macOS: macFUSE (or native FS APIs)
40+
- Linux: FUSE
41+
42+
Electron alone cannot do this; it needs a companion native component.
43+
44+
#### Windows choice (decision)
45+
46+
We will implement the Windows “true drive” using **WinFsp**.
47+
48+
Why:
49+
- It’s a mature, widely-used user-mode filesystem stack.
50+
- It supports filesystem statistics reporting (total/free bytes), which we must control to match tier quota.
51+
- It supports a FUSE-compatibility path, which helps keep the long-term cross-platform story coherent.
52+
53+
Non-goal (explicit): `SUBST` can never meet the “Explorer shows tier quota capacity” requirement, because it inherits capacity from the underlying disk.
54+
55+
### 2.2 Encryption model
56+
FileShot’s ZKE model means:
57+
- The server must never learn the plaintext
58+
- Client must encrypt before upload
59+
- Client must decrypt after download
60+
61+
The current desktop app already has ZKE streaming container support in `utils/zke-stream.js`.
62+
63+
### 2.3 Explorer/Finder cannot prompt for “password” mid-copy
64+
File managers don’t provide a standard UI to prompt for extra metadata when copying.
65+
So we need a deterministic UX mechanism (see §5).
66+
67+
---
68+
69+
## 3) Proposed architecture (no compromises)
70+
71+
### 3.1 Split responsibilities
72+
73+
**A) Electron app (UI + auth + settings)**
74+
- Login, store auth token
75+
- Shows status (mounted/unmounted, quota used/remaining)
76+
- Starts/stops the mount service
77+
- Handles configuration (drive letter, mount point, sync rules)
78+
79+
**B) `fileshot-drive` mount service (native executable)**
80+
- Implements filesystem callbacks (list/read/write/delete/etc)
81+
- Reports filesystem statistics (total bytes, free bytes)
82+
- Streams encryption/decryption
83+
- Talks to FileShot API using the user token
84+
85+
Electron communicates with the service over a local IPC channel:
86+
- Windows named pipe / localhost loopback HTTP
87+
- macOS/Linux: Unix domain socket / localhost
88+
89+
### 3.2 Why a separate service is required
90+
- Node/Electron cannot reliably implement a filesystem driver
91+
- A dedicated service can be:
92+
- restarted independently
93+
- kept minimal for security
94+
- packaged per OS
95+
96+
---
97+
98+
## 4) Filesystem semantics
99+
100+
### 4.1 Path layout
101+
Default layout:
102+
- `/` (root)
103+
- `My Files/` (remote user root)
104+
- `Shared With Me/` (optional, later)
105+
- `.fileshot/` (special config folder; see §5)
106+
107+
We can start simpler: `/` == user’s file root.
108+
109+
### 4.2 Upload behavior
110+
When a file is copied into the mounted drive:
111+
- The mount service receives write operations
112+
- It buffers to a local temp file
113+
- On close/flush, it:
114+
1) encrypts to FSZK (streaming)
115+
2) performs pre-upload
116+
3) uploads chunks
117+
4) finalizes
118+
5) exposes a stable remote entry in the drive
119+
120+
### 4.3 Download behavior
121+
When a file is opened:
122+
- Service downloads encrypted blob
123+
- Decrypts on the fly or to temp
124+
- Serves plaintext bytes to the filesystem reader
125+
126+
### 4.4 Delete behavior
127+
Options (must be decided):
128+
- Immediate remote delete
129+
- Or move-to-trash semantics (special folder)
130+
131+
### 4.5 Offline / caching
132+
- Minimal v1: small read cache + write staging only
133+
- Later: full offline pinning
134+
135+
---
136+
137+
## 5) Optional password UX (works in file explorers)
138+
139+
We need a mechanism that does not require interactive prompts.
140+
141+
### Option A (recommended): folder-level config file
142+
- In any folder, user can create/edit:
143+
- `.fileshot/settings.json`
144+
- Example keys:
145+
- `defaultPassphrase` (or password hint; careful)
146+
- `requirePassphrase: true/false`
147+
- `defaultExpirationHours`
148+
- `defaultMaxDownloads`
149+
150+
Security note: storing passphrases in plaintext on disk is risky.
151+
Better: store an identifier that tells Electron to fetch the secret from OS keychain.
152+
153+
### Option B: sidecar metadata files
154+
- For `photo.jpg` allow `photo.jpg.fileshot.json`
155+
- Contains upload settings for that one file
156+
157+
### Option C: extended attributes
158+
- Use xattrs/ADS where supported
159+
- Not cross-platform reliable; keep as later enhancement
160+
161+
---
162+
163+
## 6) Cross-platform plan
164+
165+
### Phase 1: Windows true drive (priority)
166+
- Implement `fileshot-drive.exe` using **WinFsp** (user-mode filesystem).
167+
- Must support:
168+
- directory listing
169+
- file read
170+
- file create/write/close->upload
171+
- delete
172+
- **disk free/total reporting based on tier quota**
173+
174+
### Phase 2: macOS mount
175+
- Implement `fileshot-drive` via macFUSE
176+
177+
### Phase 3: Linux mount
178+
- Implement via FUSE3
179+
180+
---
181+
182+
## 7) Release/build requirements
183+
184+
- GitHub Actions builds installers for:
185+
- Windows (NSIS)
186+
- macOS (DMG/ZIP)
187+
- Linux (AppImage/DEB/RPM)
188+
- The mount service binaries must be included inside the Electron app bundle and signed/notarized later.
189+
190+
---
191+
192+
## 8) Acceptance tests (what “done” means)
193+
194+
### Windows
195+
- In Explorer, `FileShot (F:)` shows:
196+
- Capacity: **50 GB** for Free tier
197+
- Free space decreases as usage increases
198+
- Copying a file into `F:` uploads it to FileShot and the file appears in the user’s account
199+
- Opening a file in `F:` downloads/decrypts and opens correctly
200+
- No “C drive mirror” behavior
201+
202+
### macOS/Linux
203+
- Same semantics, appropriate mount points
204+
205+
---
206+
207+
## 9) Current state in this repo
208+
209+
- There is an existing “FileShot Drive” feature implemented with `SUBST` mapping to `app.getPath('userData')/vault/drive-inbox`.
210+
- This is useful as a drop-folder workflow, but it **cannot** meet the quota/capacity requirement.
211+
212+
Next step is implementing the native mount service and switching the UI to prefer the true mount.

drive/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# FileShot Drive (True Mounted Volume)
2+
3+
This folder will contain the **native mount service** and related docs.
4+
5+
Important distinction:
6+
- The existing feature in `main.js` uses `SUBST` (drive letter -> local folder). That is a *drop-folder UX* and **cannot** control Explorer/Finder “capacity/free space”.
7+
- The **true drive** requires an OS filesystem mount (Windows: WinFsp, macOS: macFUSE, Linux: FUSE).
8+
9+
High-level architecture:
10+
- Electron app: auth UI, settings, tray, status, starts/stops mount service
11+
- `fileshot-drive` service: actual filesystem implementation + tier quota reporting + ZKE upload/download
12+
13+
See `../FILESHOT_VIRTUAL_DRIVE_SPEC.md` for the hard requirements.

0 commit comments

Comments
 (0)