synclayer 0.1.0__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 (49) hide show
  1. synclayer-0.1.0/CHANGELOG.md +14 -0
  2. synclayer-0.1.0/LICENSE +21 -0
  3. synclayer-0.1.0/MANIFEST.in +9 -0
  4. synclayer-0.1.0/PKG-INFO +418 -0
  5. synclayer-0.1.0/README.md +369 -0
  6. synclayer-0.1.0/pyproject.toml +85 -0
  7. synclayer-0.1.0/setup.cfg +4 -0
  8. synclayer-0.1.0/synclayer/__init__.py +13 -0
  9. synclayer-0.1.0/synclayer/__main__.py +4 -0
  10. synclayer-0.1.0/synclayer/_version.py +1 -0
  11. synclayer-0.1.0/synclayer/autostart.py +175 -0
  12. synclayer-0.1.0/synclayer/cli.py +913 -0
  13. synclayer-0.1.0/synclayer/client/__init__.py +0 -0
  14. synclayer-0.1.0/synclayer/client/api.py +283 -0
  15. synclayer-0.1.0/synclayer/client/config.py +224 -0
  16. synclayer-0.1.0/synclayer/client/sync.py +566 -0
  17. synclayer-0.1.0/synclayer/common/__init__.py +0 -0
  18. synclayer-0.1.0/synclayer/common/scan.py +40 -0
  19. synclayer-0.1.0/synclayer/config.py +317 -0
  20. synclayer-0.1.0/synclayer/deploy.py +370 -0
  21. synclayer-0.1.0/synclayer/desktop.py +281 -0
  22. synclayer-0.1.0/synclayer/devtools.py +209 -0
  23. synclayer-0.1.0/synclayer/gui/__init__.py +23 -0
  24. synclayer-0.1.0/synclayer/gui/app.py +1123 -0
  25. synclayer-0.1.0/synclayer/gui/icon.py +63 -0
  26. synclayer-0.1.0/synclayer/py.typed +0 -0
  27. synclayer-0.1.0/synclayer/remote.py +364 -0
  28. synclayer-0.1.0/synclayer/server/__init__.py +15 -0
  29. synclayer-0.1.0/synclayer/server/app.py +1404 -0
  30. synclayer-0.1.0/synclayer/server/library.py +423 -0
  31. synclayer-0.1.0/synclayer/server/mfa.py +407 -0
  32. synclayer-0.1.0/synclayer/server/quota.py +119 -0
  33. synclayer-0.1.0/synclayer/server/security.py +268 -0
  34. synclayer-0.1.0/synclayer/server/users.py +163 -0
  35. synclayer-0.1.0/synclayer/server/web/app.css +277 -0
  36. synclayer-0.1.0/synclayer/server/web/app.js +841 -0
  37. synclayer-0.1.0/synclayer/server/web/index.html +307 -0
  38. synclayer-0.1.0/synclayer/server/web/share.html +30 -0
  39. synclayer-0.1.0/synclayer/server/web/share.js +57 -0
  40. synclayer-0.1.0/synclayer/theme.py +127 -0
  41. synclayer-0.1.0/synclayer/tui.py +1318 -0
  42. synclayer-0.1.0/synclayer/vault.py +364 -0
  43. synclayer-0.1.0/synclayer/winservice.py +483 -0
  44. synclayer-0.1.0/synclayer.egg-info/PKG-INFO +418 -0
  45. synclayer-0.1.0/synclayer.egg-info/SOURCES.txt +47 -0
  46. synclayer-0.1.0/synclayer.egg-info/dependency_links.txt +1 -0
  47. synclayer-0.1.0/synclayer.egg-info/entry_points.txt +6 -0
  48. synclayer-0.1.0/synclayer.egg-info/requires.txt +27 -0
  49. synclayer-0.1.0/synclayer.egg-info/top_level.txt +1 -0
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.1.0
6
+ - Added a self-hosted cloud with a FastAPI server, web file manager, admin controls, MFA, and configurable storage quotas.
7
+ - Added multi-user accounts with private drives, folder sharing, and a Shared with me view.
8
+ - Added file search, recent and starred views, trash and restore, version history, protected share links, and resumable uploads.
9
+ - Added cross-platform clients: CLI/TUI, Python API, Windows and Linux desktop sync apps, and an Android WebView app with native upload and download support. Sync supports `.slignore`, bandwidth limits, and selective folder sync.
10
+ - Added multi-server support and a first-run connect screen to the desktop and Android apps, and Android camera backup.
11
+ - Added Docker and service deployment, desktop and Android build tools, and CI/release workflows for PyPI, GitHub Releases, container images, a Windows installer, and a Linux .deb.
12
+ - Added setup, contribution, support, and security documentation.
13
+ - Changed sync so deleting a file on a device keeps the server copy unless deletion sync is turned on.
14
+ - Updated repository ignore rules and platform-specific text-file handling.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bradley Odimmasi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include README.md LICENSE CHANGELOG.md
2
+ recursive-include synclayer/server/web *
3
+ include synclayer/py.typed
4
+ prune tests
5
+ prune scripts
6
+ prune android
7
+ prune packaging
8
+ prune docker
9
+ global-exclude __pycache__ *.py[cod] .env*
@@ -0,0 +1,418 @@
1
+ Metadata-Version: 2.4
2
+ Name: synclayer
3
+ Version: 0.1.0
4
+ Summary: Synclayer - self-hosted cloud file manager with cross-platform sync (server, web, desktop, CLI)
5
+ Author: Bradley Odimmasi
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/KshRaven/synclayer
8
+ Project-URL: Documentation, https://github.com/KshRaven/synclayer#readme
9
+ Project-URL: Source, https://github.com/KshRaven/synclayer
10
+ Project-URL: Issues, https://github.com/KshRaven/synclayer/issues
11
+ Project-URL: Changelog, https://github.com/KshRaven/synclayer/blob/main/CHANGELOG.md
12
+ Keywords: file manager,sync,cloud storage,self-hosted,backup
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: System :: Archiving :: Backup
22
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: fastapi>=0.115
28
+ Requires-Dist: pillow>=10.0
29
+ Requires-Dist: uvicorn[standard]>=0.27
30
+ Requires-Dist: httpx>=0.26
31
+ Requires-Dist: platformdirs>=4.0
32
+ Requires-Dist: python-dotenv>=1.0
33
+ Requires-Dist: websockets>=12
34
+ Requires-Dist: watchdog>=4.0
35
+ Provides-Extra: gui
36
+ Requires-Dist: PySide6>=6.6; extra == "gui"
37
+ Provides-Extra: winservice
38
+ Requires-Dist: pywin32>=306; sys_platform == "win32" and extra == "winservice"
39
+ Provides-Extra: build
40
+ Requires-Dist: synclayer[gui]; extra == "build"
41
+ Requires-Dist: pyinstaller>=6.3; extra == "build"
42
+ Requires-Dist: build>=1.0; extra == "build"
43
+ Requires-Dist: twine>=5.0; extra == "build"
44
+ Provides-Extra: dev
45
+ Requires-Dist: synclayer[build]; extra == "dev"
46
+ Requires-Dist: pytest>=8; extra == "dev"
47
+ Requires-Dist: ruff; extra == "dev"
48
+ Dynamic: license-file
49
+
50
+ # Synclayer
51
+
52
+ A self-hosted cloud for your files. Run the server on a machine you control, then browse, upload and
53
+ sync folders from a web browser, a desktop app, an Android app, the command line or Python code.
54
+
55
+ ## What is included
56
+
57
+ | Part | What it does |
58
+ |---|---|
59
+ | Server | FastAPI app that stores the files and serves the web app and API. Has an admin panel (web, desktop tab and CLI). |
60
+ | Web app | File manager with drag and drop for files and folders, search, recent and starred views, trash, version history and share links. |
61
+ | Desktop app | Windows and Linux app with tray icon, notifications and launch at login. macOS works on a best-effort basis. |
62
+ | Terminal app | `synclayer` (or `sl`). With no arguments it opens a full-screen menu; with arguments it behaves like a normal CLI (`synclayer --help`). |
63
+ | Android app | WebView client for the server's web app with native upload and download handling. No background folder sync. |
64
+ | Python package | `import synclayer` to pull, push and sync project files from your own code. |
65
+
66
+ Other features: multiple users with private drives and per-user quotas, folder sharing between users,
67
+ password-protected share links, resumable uploads, `.slignore` files to exclude paths from sync, and
68
+ optional admin multi-factor authentication.
69
+
70
+ ## Try it locally
71
+
72
+ Requires Python 3.10 or newer.
73
+
74
+ ```bash
75
+ python3 -m venv .venv && source .venv/bin/activate # Windows: py -3 -m venv .venv, then .venv\Scripts\activate
76
+ pip install -e .
77
+ cp .env.example .env # Windows: Copy-Item .env.example .env
78
+ synclayer server serve
79
+ ```
80
+
81
+ Open `http://localhost:8787` and sign in as `admin`. If `SL_ADMIN_PASSWORD` is empty, a random password is
82
+ printed once on first start. Set your own later with `synclayer server passwd`. To listen on this machine
83
+ only, set `SL_HOST=127.0.0.1` in `.env`; the example file listens on all interfaces.
84
+
85
+ Check that it is running:
86
+
87
+ ```bash
88
+ curl --fail http://127.0.0.1:8787/api/health
89
+ ```
90
+
91
+ To test from the command line, create a device token on the server and use it from a client:
92
+
93
+ ```bash
94
+ synclayer server token add sample-client
95
+ synclayer login --server http://127.0.0.1:8787 --token "dst_TOKEN_FROM_SERVER"
96
+ synclayer put ./sample.txt
97
+ synclayer ls
98
+ synclayer get sample.txt ./sample-downloaded.txt
99
+ ```
100
+
101
+ The token is shown once. Store it somewhere private and revoke it if the device is lost.
102
+
103
+ There is also an interactive manager that covers setup, running, building, updating, Docker, the boot
104
+ service and PyPI publishing: `bash scripts/manage/manage.sh` (Linux, macOS) or `scripts\manage\manage.bat` (Windows).
105
+
106
+ ### Install options
107
+
108
+ | Command | You get | Typical use |
109
+ |---|---|---|
110
+ | `pip install synclayer` | server, CLI, Python package (no Qt) | servers, scripts |
111
+ | `pip install "synclayer[gui]"` | plus the desktop app (`synclayer-gui`) | desktops |
112
+ | `pip install "synclayer[winservice]"` | plus the Windows Service | Windows servers |
113
+ | `pip install -e ".[dev]"` / `".[build]"` | tests and lint / packaging tools | working on Synclayer |
114
+
115
+ From a checkout of this repository, use `pip install -e` in place of `pip install`.
116
+
117
+ ## Hosting on your own server
118
+
119
+ The server targets Ubuntu/Debian (any systemd distribution works). Windows runs it as a real Windows
120
+ Service. macOS is supported as a LaunchAgent, best effort.
121
+
122
+ You need Python 3.10+, a domain whose `A` record points at the server (for HTTPS), and ports 80 and 443 open
123
+ (`sudo ufw allow 'Nginx Full'`). Behind a router, forward TCP 80 and 443. Only publish an `AAAA` record if
124
+ IPv6 and its firewall rules are set up too; Let's Encrypt validates over port 80.
125
+
126
+ ### Linux: one command
127
+
128
+ From a checkout on the server:
129
+
130
+ ```bash
131
+ sudo bash scripts/install-service.sh install --nginx files.example.com --certbot you@example.com
132
+ ```
133
+
134
+ This creates a `synclayer` system user, installs the app into `/opt/synclayer`, writes
135
+ `/etc/synclayer/synclayer.env`, installs a sandboxed systemd service that starts at boot and restarts on
136
+ failure, configures nginx for your domain and requests a Let's Encrypt certificate. Leave out `--nginx` and
137
+ `--certbot` to do it in steps. Without `--nginx` the server listens on `127.0.0.1` only; set `SL_HOST=0.0.0.0`
138
+ in the env file to expose port 8787 directly.
139
+
140
+ Then find the one-time admin password, or set your own:
141
+
142
+ ```bash
143
+ sudo journalctl -u synclayer-server --no-pager | grep -A4 'first start'
144
+ sudo synclayer server passwd
145
+ ```
146
+
147
+ | Task | Command |
148
+ |---|---|
149
+ | Status, restart, logs | `sudo bash scripts/install-service.sh status` / `restart` / `logs` |
150
+ | Update from this checkout (or PyPI) | `sudo bash scripts/install-service.sh update` |
151
+ | Remove the service (files are kept) | `sudo bash scripts/install-service.sh uninstall` |
152
+
153
+ Data locations: files in `/var/lib/synclayer/files`, settings in `/var/lib/synclayer/config`, environment in
154
+ `/etc/synclayer/synclayer.env`.
155
+
156
+ ### Linux: step by step
157
+
158
+ ```bash
159
+ pip install synclayer
160
+ sudo synclayer service install --domain files.example.com
161
+ sudo synclayer nginx install --domain files.example.com --install-packages --certbot --email you@example.com
162
+ sudo synclayer service restart
163
+ synclayer service status
164
+ ```
165
+
166
+ `synclayer nginx show --domain D` prints the nginx config without installing it, for setups you manage
167
+ yourself. The generated site keeps WebSocket updates working, streams uploads and downloads, and removes
168
+ nginx's body-size limit; the server enforces upload size and quota itself. Without root, use
169
+ `synclayer service install --user` for a systemd user unit (`sudo loginctl enable-linger $USER` makes it start
170
+ without a login).
171
+
172
+ ### Windows
173
+
174
+ ```powershell
175
+ pip install "synclayer[winservice]"
176
+ synclayer service install # from an Administrator prompt
177
+ synclayer service status | stop | start | restart | uninstall
178
+ ```
179
+
180
+ The service uses `SL_PORT` (8787 by default); use `--port` if something else owns it, and update clients and
181
+ any proxy to match. It starts automatically (delayed), restarts on failure and runs as LocalSystem. Data and
182
+ logs are in `%ProgramData%\Synclayer`, and its `config\` folder (signing key, tokens) is restricted to
183
+ Administrators, so run `synclayer server passwd` from an Administrator prompt. Settings come from a `.env` in
184
+ the folder you run `install` from (or `--env-file`), or from the admin panel.
185
+
186
+ If registration fails with a `pythonservice.exe` or DLL error, run
187
+ `python -m pywin32_postinstall -install` once as Administrator and try again. To allow other machines, set
188
+ `SL_HOST=0.0.0.0` and open the port:
189
+ `netsh advfirewall firewall add rule name="Synclayer" dir=in action=allow protocol=TCP localport=8787`.
190
+ The packaged `.exe` cannot host the service; use `synclayer autostart enable --server` (Task Scheduler)
191
+ instead. For HTTPS, put any reverse proxy in front; the config from `synclayer nginx show` works.
192
+
193
+ ### Docker
194
+
195
+ ```bash
196
+ docker compose up -d --build # http://localhost:8787
197
+ ```
198
+
199
+ For HTTPS with nginx and Let's Encrypt, set `SL_DOMAIN=your.domain` and `SL_TRUST_PROXY=true` in `.env`, point
200
+ DNS at the server, open ports 80 and 443, then:
201
+
202
+ ```bash
203
+ docker compose --profile proxy up -d --build # nginx on :80, serves the certificate challenge
204
+ docker compose --profile certbot run --rm certbot certonly --webroot -w /var/www/certbot \
205
+ -d your.domain --email you@example.com --agree-tos --no-eff-email
206
+ docker compose --profile proxy down && docker compose --profile https up -d
207
+ ```
208
+
209
+ Renew about monthly with
210
+ `docker compose --profile certbot run --rm certbot renew && docker compose exec nginx nginx -s reload`.
211
+ On a LAN, or when TLS is handled elsewhere (Cloudflare, a load balancer), stay on `--profile proxy`.
212
+
213
+ ## Configuration
214
+
215
+ Settings are `SL_*` variables (see `.env.example`). Precedence, lowest to highest: defaults, process
216
+ environment, `.env`, `settings.json`. The server re-reads `.env` and `settings.json` within about a second,
217
+ so most settings apply without a restart. Host, port, proxy mode and secret key need one.
218
+
219
+ Change settings from the web Admin panel, the desktop Admin tab, `synclayer admin set KEY VALUE` (from any
220
+ computer) or `synclayer server set KEY VALUE` (on the server). `synclayer server show` prints the effective values.
221
+
222
+ ### Storage and limits
223
+
224
+ Files live in a per-OS data folder by default. Set `SL_STORAGE_ROOT` to move them, and
225
+ `SL_MOUNTS="name=/abs/path;other=/abs/path"` to expose existing server folders for syncing.
226
+
227
+ | Setting | Effect |
228
+ |---|---|
229
+ | `quota_mb` | Cap for the main storage folder. Uploads past it fail with HTTP 507. `0` means unlimited. |
230
+ | `min_free_mb` | Refuse writes that would leave less than this much free disk. |
231
+ | `max_upload_mb` | Largest single file. |
232
+ | `blocked_extensions` | File types that cannot be stored, for example `.exe,.php`. |
233
+ | `user_writes` | `false` makes everyone except the admin read-only. |
234
+
235
+ ```bash
236
+ synclayer server set quota_mb 400
237
+ synclayer server usage # usage, quota, free disk, largest folders (`synclayer admin usage` from another computer)
238
+ ```
239
+
240
+ The quota covers the main storage folder only; `SL_MOUNTS` folders are not counted. Usage is scanned and
241
+ cached for about 15 seconds, so two uploads started at the same moment can overshoot by their own size.
242
+
243
+ ### Users and sharing
244
+
245
+ The admin creates users from the Admin panel. Each user gets a private drive and can have their own quota.
246
+ A user can share a folder with another user as read-only or read-write, and create share links (optionally
247
+ password protected). Share links and starred items belong to the user who made them. Users cannot reach the
248
+ admin API. Disabling a user blocks sign-in; deleting one lets you keep or purge their data.
249
+
250
+ Devices (the desktop app, CLI, scripts) sign in with revocable tokens: `synclayer server token add NAME`.
251
+ Tokens can be read-only (`--read-only`).
252
+
253
+ ### Multi-factor authentication
254
+
255
+ The server can require a second factor on admin password logins: authenticator-app codes (TOTP), SMS (Twilio or
256
+ a webhook), e-mail (SMTP) and one-time recovery codes. Configure delivery with the `sms_twilio_*`,
257
+ `sms_webhook_*` or `smtp_*` settings and manage factors through `/api/admin/mfa/...`. Sign-in codes by SMS or
258
+ e-mail use `/api/auth/mfa/send`.
259
+
260
+ The bundled web, Android, desktop, CLI and Python password logins do not yet prompt for the second factor, so
261
+ do not enable MFA for an account that signs in through them. Device-token authentication is unaffected.
262
+
263
+ ### Security
264
+
265
+ Passwords are hashed, sessions are signed, login attempts are rate limited and paths cannot escape their
266
+ root. Serve over HTTPS on the internet, use a strong admin password and keep `SL_SECRET_KEY` private.
267
+ See [SECURITY.md](SECURITY.md) for reporting vulnerabilities.
268
+
269
+ ### Backups and troubleshooting
270
+
271
+ Back up both the file data and the server configuration (settings, device tokens, signing key, MFA state).
272
+ Losing the configuration invalidates sessions and tokens. Locations: `/var/lib/synclayer` and
273
+ `/etc/synclayer/synclayer.env` on Linux, `%ProgramData%\Synclayer` on Windows, and the `synclayer-data` volume
274
+ (`/data/config`, `/data/files`) in Docker. The storage root may be elsewhere if you changed it. Stop the
275
+ service or use a filesystem snapshot while copying, and test a restore. `docker compose down -v` deletes the volumes.
276
+
277
+ If something fails, check `synclayer service status` and `synclayer service logs` (Docker: `docker compose ps`
278
+ and `docker compose logs`), then `/api/health`, DNS, firewall and ports 80/443. If an upload is rejected, check
279
+ the limits above and free disk space. If a setting seems ignored, remember that `settings.json` overrides `.env`.
280
+
281
+ ## Using the apps
282
+
283
+ ```bash
284
+ synclayer login --server https://your.domain # or: --token dst_...
285
+ synclayer ls | put FILE_OR_DIR [REMOTE] | get REMOTE [LOCAL] | mkdir | mv | rm
286
+ synclayer sync add work ~/Work --path projects/alpha # pair a local folder with a server folder
287
+ synclayer sync run # one pass; `synclayer daemon` keeps syncing
288
+ synclayer gui # desktop app; `synclayer tray --minimized` for the tray only
289
+ synclayer autostart enable # start the tray app at login (--server for the server)
290
+ ```
291
+
292
+ Sync is two-way by default; `--mode upload` makes a backup that never changes local files and `--mode download`
293
+ mirrors the server. If both sides changed a file, the newest copy wins and the other is saved as
294
+ `name.conflict-<time>.ext`. Deleting a file on a device does not delete it on the server unless you add
295
+ `--propagate-deletes` to that sync folder. The default local folder is `~/Synclayer`; each sync profile can use
296
+ another folder and another server path. Put patterns in a `.slignore` file to leave paths out of sync.
297
+
298
+ The desktop and Android apps start with no server and ask for an address on first launch. They can keep several
299
+ servers and switch between them, and the desktop app has a picker for the server folder of each sync folder.
300
+
301
+ The web and desktop apps both have **Account** (who you are, which server, sign in or out, device token) and
302
+ **Settings** (confirm before delete, date format, parallel uploads, notifications, launch at login). Admins also
303
+ get a shortcut to the server-wide Admin panel. CLI equivalents: `synclayer login | logout | status`.
304
+
305
+ ### Desktop app
306
+
307
+ ```bash
308
+ pip install "synclayer[gui]"
309
+ synclayer-gui
310
+ ```
311
+
312
+ The first run adds an application-menu entry and icon (Linux: a `.desktop` file under `~/.local/share`;
313
+ Windows: a Start Menu shortcut you can pin) and keeps the app in the system tray when the window is closed.
314
+ Manage the launcher with `synclayer desktop install | uninstall | status`.
315
+
316
+ On Ubuntu, Qt needs `sudo apt install libxcb-cursor0 libegl1`, and GNOME shows tray icons only with the
317
+ AppIndicator extension (`sudo apt install gnome-shell-extension-appindicator`).
318
+
319
+ macOS (11+, best effort): the same commands work. The first run creates `~/Applications/Synclayer.app`,
320
+ `synclayer autostart enable` adds a LaunchAgent, and `synclayer service install` runs the server as a
321
+ LaunchAgent that starts at login (logs in `~/Library/Logs/Synclayer`). Release builds include an unsigned
322
+ Apple Silicon app: right-click it and choose Open the first time, or run
323
+ `xattr -dr com.apple.quarantine Synclayer.app`. For a server that must run before anyone logs in, use Linux.
324
+
325
+ ### Android app
326
+
327
+ A WebView shell around the web app: choose a server address, use the file manager and admin panel, pick files
328
+ to upload and download through Android's DownloadManager. Optional camera backup uploads new photos and videos to
329
+ `Camera Uploads` on the active server in the background (Wi-Fi only by default, with an option to include existing
330
+ media). Background sync of arbitrary folders is not included (see [TODO.md](TODO.md)).
331
+
332
+ Build an APK with JDK 17, the Android SDK and Gradle, or let `synclayer dev android` download them for you:
333
+
334
+ ```bash
335
+ bash android/build-apk.sh [https://files.example.com] # optional default server address
336
+ ```
337
+
338
+ The APK is written to `dist/synclayer-<version>-android.apk`. Without a keystore it is signed with the debug
339
+ key, which is fine for sideloading. For your own signing, set `SL_KEYSTORE_FILE`, `SL_KEYSTORE_PASSWORD`,
340
+ `SL_KEY_ALIAS` and `SL_KEY_PASSWORD`.
341
+
342
+ ### Python package
343
+
344
+ ```python
345
+ import synclayer
346
+
347
+ cloud = synclayer.connect("https://your.domain", token="dst_...") # or set SL_SERVER / SL_TOKEN
348
+ cloud.pull("projects/alpha", "./workdir") # fetch your files on another machine
349
+ cloud.push("./workdir/results", "projects/alpha/results")
350
+ cloud.sync("./workdir", "projects/alpha") # two-way
351
+ watcher = cloud.watch("./workdir", "projects/alpha", interval=30) # background sync; watcher.stop()
352
+ cloud.write_text("notes/todo.txt", "hello"); print(cloud.read_text("notes/todo.txt"))
353
+ ```
354
+
355
+ ## Building and releasing
356
+
357
+ Build the desktop and CLI apps on the operating system you are targeting:
358
+
359
+ ```bash
360
+ pip install -e ".[gui,build]"
361
+ python scripts/build.py --package # dist/synclayer, dist/synclayer-gui and an archive
362
+ python scripts/build.py --server https://your.domain # also bake in a default server address
363
+ ```
364
+
365
+ Releases are automated by `scripts/pypimgr`. Add notes under `## Unreleased` in `CHANGELOG.md`, then:
366
+
367
+ ```bash
368
+ python scripts/pypimgr/pypimgr.py check # tests, build and verify; changes nothing
369
+ python scripts/pypimgr/pypimgr.py release patch --push
370
+ ```
371
+
372
+ This bumps `synclayer/_version.py`, stamps the changelog, builds from a clean copy, makes one commit and a
373
+ `vX.Y.Z` tag, and pushes. The tag starts `.github/workflows/release.yml`, which:
374
+
375
+ - builds the Windows and Linux apps, a Windows installer and a Linux `.deb` package (macOS, Linux ARM and Android
376
+ are best effort) and attaches them to a GitHub Release with the changelog notes;
377
+ - publishes to TestPyPI, then PyPI, through Trusted Publishing (no tokens);
378
+ - pushes the server image to `ghcr.io`.
379
+
380
+ One-time setup: add a Trusted Publisher for this repository and workflow on pypi.org and test.pypi.org
381
+ (environments `pypi` and `testpypi`). Optional repository variable `SL_BUILD_SERVER` bakes a default server
382
+ address into released apps. Android signing uses the secrets `ANDROID_KEYSTORE_BASE64`,
383
+ `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS` and `ANDROID_KEY_PASSWORD`; without them the APK is
384
+ debug-signed. Running the workflow by hand builds the apps only, with no release or PyPI upload. Check every
385
+ job and artifact before announcing a release. Windows and macOS builds are unsigned.
386
+
387
+ ## Development
388
+
389
+ ```bash
390
+ pip install -e ".[gui,dev,build]"
391
+ synclayer dev doctor # shows what is installed or missing (extras, JDK, Android SDK, Gradle)
392
+ python -m compileall -q synclayer scripts packaging
393
+ ruff check .
394
+ pytest -q
395
+ ```
396
+
397
+ CI runs compile and import checks, Ruff and the tests on Linux and macOS with Python 3.10 and 3.12. The tests
398
+ cover the server API, authentication, users and sharing, trash and versions, quotas, path safety, device
399
+ monitoring, sync ignore rules and terminal-app helpers. They are not a full deployment or cross-device test.
400
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
401
+
402
+ ### Known limitations
403
+
404
+ No per-device folder permissions, polling instead of a filesystem watcher, no block-level delta transfers, no
405
+ Android background sync, and incomplete MFA support in password-login clients. Deferred work and
406
+ clean-machine verification gaps are in [TODO.md](TODO.md).
407
+
408
+ ### Repository layout
409
+
410
+ ```
411
+ synclayer/ server/ (API, web UI, MFA) client/ (API client, sync) gui/ cli.py remote.py vault.py
412
+ android/ Kotlin WebView app and APK build script
413
+ scripts/ manage/ setup menu, pypimgr/ release tool, build.py, install-service.sh
414
+ packaging/ entry points for built executables, installer scripts
415
+ docker/ nginx templates (Dockerfile and docker-compose.yml are in the root)
416
+ .github/workflows/ CI and release automation
417
+ tests/ server, storage and client tests
418
+ ```