small-software 0.6.0__tar.gz → 0.8.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.
- {small_software-0.6.0 → small_software-0.8.0}/PKG-INFO +142 -31
- {small_software-0.6.0 → small_software-0.8.0}/README.md +141 -30
- {small_software-0.6.0 → small_software-0.8.0}/pyproject.toml +1 -1
- small_software-0.8.0/small_cli/__init__.py +1 -0
- small_software-0.8.0/small_cli/access.py +202 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/archive.py +6 -6
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/cli.py +10 -9
- small_software-0.8.0/small_cli/commands/__init__.py +9 -0
- small_software-0.8.0/small_cli/commands/access.py +352 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/dashboard.py +1 -1
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/db.py +8 -8
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/deploy.py +18 -16
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/destroy.py +2 -2
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/domain.py +1 -1
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/env.py +8 -8
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/init.py +5 -5
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/link.py +1 -1
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/logs.py +1 -1
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/mail.py +8 -8
- small_software-0.8.0/small_cli/commands/mcp.py +23 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/notify.py +5 -5
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/commands/sleep.py +2 -2
- small_software-0.8.0/small_cli/commands/sso.py +268 -0
- small_software-0.8.0/small_cli/commands/volume.py +89 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/common.py +4 -4
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/config.py +6 -6
- small_software-0.8.0/small_cli/dashboard.py +1112 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/i18n.py +10 -9
- small_software-0.8.0/small_cli/mcp.py +389 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/railway.py +66 -25
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/scaffold.py +11 -11
- small_software-0.8.0/small_cli/templates/fastapi/magic_link.py +1397 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/templates/fastapi/main.py +3 -3
- small_software-0.8.0/small_cli/templates/node-http/magic-link.js +988 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/templates/python-http/app.py +3 -2
- small_software-0.8.0/small_cli/templates/python-http/magic_link.py +1397 -0
- small_software-0.8.0/small_cli/templates/streamlit/app.py +314 -0
- small_software-0.8.0/small_cli/templates/streamlit/magic_link.py +1397 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_software.egg-info/PKG-INFO +142 -31
- {small_software-0.6.0 → small_software-0.8.0}/small_software.egg-info/SOURCES.txt +10 -1
- small_software-0.8.0/tests/test_dashboard_share.py +355 -0
- small_software-0.8.0/tests/test_mcp.py +229 -0
- {small_software-0.6.0 → small_software-0.8.0}/tests/test_small.py +129 -124
- small_software-0.8.0/tests/test_sso_volume.py +392 -0
- small_software-0.8.0/tests/test_streamlit_email.py +562 -0
- small_software-0.6.0/small_cli/__init__.py +0 -1
- small_software-0.6.0/small_cli/access.py +0 -114
- small_software-0.6.0/small_cli/commands/__init__.py +0 -8
- small_software-0.6.0/small_cli/commands/access.py +0 -167
- small_software-0.6.0/small_cli/dashboard.py +0 -648
- small_software-0.6.0/small_cli/templates/fastapi/magic_link.py +0 -675
- small_software-0.6.0/small_cli/templates/node-http/magic-link.js +0 -509
- small_software-0.6.0/small_cli/templates/python-http/magic_link.py +0 -675
- small_software-0.6.0/small_cli/templates/streamlit/app.py +0 -181
- {small_software-0.6.0 → small_software-0.8.0}/LICENSE +0 -0
- {small_software-0.6.0 → small_software-0.8.0}/setup.cfg +0 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/__main__.py +0 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/templates/fastapi/requirements.txt +0 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/templates/node-http/package.json +0 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/templates/node-http/server.js +0 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_cli/templates/streamlit/requirements.txt +0 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_software.egg-info/dependency_links.txt +0 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_software.egg-info/entry_points.txt +0 -0
- {small_software-0.6.0 → small_software-0.8.0}/small_software.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: small-software
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A cloud for small software: deploy team apps to Railway in one command, private by default, shared by work email
|
|
5
5
|
Author: RuslanKazyradzi13
|
|
6
6
|
License-Expression: MIT
|
|
@@ -30,20 +30,23 @@ Dynamic: license-file
|
|
|
30
30
|
|
|
31
31
|
[Русская версия](https://github.com/RuslanKazyradzi13/small-software/blob/main/small-cli/README.ru.md)
|
|
32
32
|
|
|
33
|
-
`small` deploys small team apps to Railway in one command and protects them with sign-in by work
|
|
34
|
-
personal invite links.
|
|
33
|
+
`small` deploys small team apps to Railway in one command and protects them with sign-in by work account (Google,
|
|
34
|
+
Microsoft), work email or personal invite links.
|
|
35
35
|
|
|
36
36
|
```
|
|
37
37
|
small init my-service # skeleton: invite-link protected server, small.json, .gitignore
|
|
38
38
|
cd my-service
|
|
39
39
|
small deploy # → https://my-service-3f9a.up.railway.app
|
|
40
40
|
small open # open with the invite link
|
|
41
|
-
small access add ivan@company.com #
|
|
42
|
-
small
|
|
41
|
+
small access add ivan@company.com # who may sign in, like sharing a Google Doc
|
|
42
|
+
small sso google # "Sign in with Google" (or small sso microsoft)
|
|
43
|
+
small mail resend # or sign-in links by email
|
|
44
|
+
small access log # who signed in, when and how
|
|
43
45
|
small access add bob # or a personal link for bob (small access revoke bob to revoke it)
|
|
44
46
|
small notify telegram # access requests to Telegram
|
|
45
47
|
small env set DEBUG=1 # environment variable in Railway and small.json
|
|
46
48
|
small db add postgres # database, DATABASE_URL in the app
|
|
49
|
+
small volume add # persistent disk: files and sign-in state survive deploys
|
|
47
50
|
small domain add app.example.com # custom domain
|
|
48
51
|
small sleep on # sleep when idle (saves money)
|
|
49
52
|
small logs -f # app logs
|
|
@@ -70,8 +73,25 @@ From source, in a clone of the repository:
|
|
|
70
73
|
pip install ./small-cli
|
|
71
74
|
```
|
|
72
75
|
|
|
73
|
-
**Windows
|
|
74
|
-
|
|
76
|
+
**Windows (recommended): one command in PowerShell.**
|
|
77
|
+
|
|
78
|
+
```powershell
|
|
79
|
+
irm https://raw.githubusercontent.com/RuslanKazyradzi13/small-software/main/install.ps1 | iex
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
It installs small into its own Python environment in `%LOCALAPPDATA%\Programs\small` and adds a `small`
|
|
83
|
+
command that Smart App Control does not block (there is no unsigned `small.exe`). Needs Python 3.9+, no admin
|
|
84
|
+
rights; run it again to upgrade. Uninstall:
|
|
85
|
+
|
|
86
|
+
```powershell
|
|
87
|
+
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/RuslanKazyradzi13/small-software/main/install.ps1))) -Uninstall
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Values with `&` or `|` (URLs, for example) need PowerShell's stop-parsing token:
|
|
91
|
+
`small --% env set "URL=https://x?a=1&b=2"`.
|
|
92
|
+
|
|
93
|
+
**Windows with pip.** `pip install` creates `small.exe`, which Smart App Control / Application Control may
|
|
94
|
+
block because it is unsigned. If that happens, either:
|
|
75
95
|
|
|
76
96
|
- run small through Python, which works everywhere: `python -m small_cli deploy` (any command works this way);
|
|
77
97
|
- or add the `small-cli` folder of a clone to `PATH`, and `small.cmd` will serve the `small` command. Run this in
|
|
@@ -154,12 +174,15 @@ The sample apps are in English: both the page text and the code comments.
|
|
|
154
174
|
`small deploy` passes `ACCESS_TOKEN` from `small.json` to Railway. The app opens at
|
|
155
175
|
`https://<domain>/?token=<ACCESS_TOKEN>`; without the token visitors get a 403 page with a **Request access**
|
|
156
176
|
button. The server listens on `$HOST:$PORT`, by default `0.0.0.0` and the `PORT` set by Railway.
|
|
157
|
-
- **The `streamlit` template.** Streamlit
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
177
|
+
- **The `streamlit` template.** Streamlit can't be wrapped in middleware, so `require_access()` in `app.py` uses
|
|
178
|
+
`MagicLinkGate` from `magic_link.py`: the same variables (`ACCESS_EMAILS`, `ACCESS_TOKEN`, `SESSION_SECRET`,
|
|
179
|
+
`ACCESS_REQUEST_URL`...), one-time links, limits and logs as the other templates, and the access screen follows the
|
|
180
|
+
visitor's browser language. Differences:
|
|
181
|
+
- Sign-in by email: the link leads to `https://<domain>/?t=...` and shows a **Sign in** button; only the click
|
|
182
|
+
signs in. The 30-day session is a signed cookie written by a tiny same-origin frame, so unlike in the other
|
|
183
|
+
templates it is **not HttpOnly**: don't render untrusted HTML in a protected app. **Sign out** is in the sidebar.
|
|
184
|
+
- Personal links keep the token in the address bar (`?token=`), which is how they survive a page reload.
|
|
185
|
+
- Streamlit is a single-page app, so the "no access" screen is served with HTTP 200, not 403.
|
|
163
186
|
Railway runs `python app.py`, and `app.py` restarts itself through `streamlit run` on `$HOST:$PORT`.
|
|
164
187
|
Run it locally the same way: `python app.py`.
|
|
165
188
|
- **Name.** By default, the folder name plus a random suffix, for example `my-service-3f9a`. An explicit name in
|
|
@@ -204,6 +227,12 @@ Buttons on the card of a deployed app:
|
|
|
204
227
|
The token only goes to the browser's address bar on navigation; it is not in the dashboard page data.
|
|
205
228
|
- **Logs** opens a window with the last 200 lines of the app or build log (tabs **App** / **Build**).
|
|
206
229
|
Requires `RAILWAY_API_TOKEN`; without it the button is disabled.
|
|
230
|
+
- **Share** opens who has access: work emails, whole domains and personal-link names (never the links
|
|
231
|
+
themselves). Type `ivan@company.com`, `@company.com` or a name and click **Add**, or click **Revoke** next to
|
|
232
|
+
anyone. It does exactly what `small access add` / `revoke` does: updates small.json and Railway and restarts
|
|
233
|
+
the app. A new personal link is shown only once, with a Copy button, and works like a password. Requires
|
|
234
|
+
`RAILWAY_API_TOKEN`. The page and its requests are protected: a per-run key, `Host`/`Origin` checks, no
|
|
235
|
+
framing, JSON only.
|
|
207
236
|
|
|
208
237
|
- **Port** 3000 by default; if it is busy, any free port is used. The URL is printed to stdout.
|
|
209
238
|
- **The server listens on `127.0.0.1` only** and answers 403 to requests with a foreign `Host` (DNS rebinding
|
|
@@ -247,6 +276,46 @@ $10 per GB of memory, $0.15 per GB of volume. The result reads "≈ $X/mo at cur
|
|
|
247
276
|
- **This is an estimate, not a bill.** Egress ($0.05/GB), the plan's free allowance ($5 on Hobby, $20 on Pro) and
|
|
248
277
|
the subscription are not included. The exact amount is in Railway → Usage.
|
|
249
278
|
|
|
279
|
+
## `small mcp`: AI agents (Claude Code, Cursor...)
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
small mcp
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Runs an [MCP](https://modelcontextprotocol.io) server on stdin/stdout, so an AI coding agent can create, deploy and
|
|
286
|
+
share apps itself: *"Deploy this folder and give ivan@company.com access."*
|
|
287
|
+
|
|
288
|
+
**Connect it:**
|
|
289
|
+
- **Claude Code:** `claude mcp add small -- small mcp` (Windows: `claude mcp add small -- cmd /c small mcp`).
|
|
290
|
+
- **Cursor** (`~/.cursor/mcp.json`), **Claude Desktop**, **Windsurf** and other clients:
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{"mcpServers": {"small": {"command": "small", "args": ["mcp"]}}}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
On Windows use `"command": "cmd", "args": ["/c", "small", "mcp"]`, or `"command": "python", "args": ["-m",
|
|
297
|
+
"small_cli", "mcp"]` if small was installed with pip.
|
|
298
|
+
- `RAILWAY_API_TOKEN` must be in the environment the agent starts from. After setting it, restart the agent.
|
|
299
|
+
|
|
300
|
+
**Tools:**
|
|
301
|
+
|
|
302
|
+
| Tool | What it does |
|
|
303
|
+
|---|---|
|
|
304
|
+
| `init_app` | `small init`: a ready app from a template |
|
|
305
|
+
| `deploy_app` | `small deploy`: returns the URL; progress is streamed while it builds |
|
|
306
|
+
| `share_app` | `small access add`: an email, `@domain` or a personal-link name |
|
|
307
|
+
| `revoke_access` | `small access revoke` |
|
|
308
|
+
| `list_access` | `small access list` |
|
|
309
|
+
| `list_apps` | apps under a folder with URL and latest deploy status |
|
|
310
|
+
| `app_logs` | `small logs`: app or build log |
|
|
311
|
+
| `set_env` | `small env set` / `unset` |
|
|
312
|
+
|
|
313
|
+
- **No delete tool:** `small destroy` stays a terminal command.
|
|
314
|
+
- **Prefer emails.** A personal link (a name instead of an email) is returned to the agent, so it passes through the
|
|
315
|
+
chat like any other text; with email sign-in nothing secret does.
|
|
316
|
+
- The tools run the same code as the commands and answer in small's language (`SMALL_LANG`). Your MCP client
|
|
317
|
+
normally asks you before each tool call.
|
|
318
|
+
|
|
250
319
|
## `small deploy`
|
|
251
320
|
|
|
252
321
|
```
|
|
@@ -275,18 +344,22 @@ These commands work in the folder of a deployed app, that is, where `.small.json
|
|
|
275
344
|
small link [folder] [--as NAME] # print the invite link
|
|
276
345
|
small open [folder] [--as NAME] # the same + open it in the browser
|
|
277
346
|
small access [list | add EMAIL|@DOMAIN|NAME | revoke EMAIL|NAME] # who can sign in
|
|
347
|
+
small access log [-n N] [--json] # who signed in, when and how
|
|
348
|
+
small access signout EMAIL|@DOMAIN|--all # end their sessions on all devices
|
|
349
|
+
small sso [google [--client-id ID] | microsoft [--client-id ID] [--tenant T] | off [PROVIDER]]
|
|
278
350
|
small mail [resend | smtp [--from FROM] [--to TO] | log | off] # emails for sign-in by email
|
|
279
351
|
small notify [telegram [--chat-id ID] | off] # access requests to Telegram
|
|
280
352
|
small env [list [--values] | set K=V ... | unset K ...] [--no-deploy]
|
|
281
353
|
small db [list | add postgres | remove postgres [--yes]]
|
|
354
|
+
small volume [add [--mount /data]] # persistent disk; no argument shows the status
|
|
282
355
|
small domain [list | add DOMAIN [--port N] | remove DOMAIN]
|
|
283
356
|
small sleep [on | off] # sleep mode; no argument shows the status
|
|
284
357
|
small logs [folder] [--build] [-n N] [-f] # log of the latest deploy
|
|
285
358
|
small destroy [folder] [--yes] # remove from Railway
|
|
286
359
|
```
|
|
287
360
|
|
|
288
|
-
Commands that change the app's variables (`access add` / `revoke
|
|
289
|
-
`db add` / `remove`) and `sleep on` / `off` restart the app once. Add `--no-deploy` to skip the restart; the change
|
|
361
|
+
Commands that change the app's variables (`access add` / `revoke` / `signout`, `sso`, `mail`, `notify`,
|
|
362
|
+
`env set` / `unset`, `db add` / `remove`), `volume add` and `sleep on` / `off` restart the app once. Add `--no-deploy` to skip the restart; the change
|
|
290
363
|
then takes effect on the next deploy.
|
|
291
364
|
|
|
292
365
|
**`small link` / `small open`** build `https://<domain>/?token=<token>`.
|
|
@@ -299,15 +372,22 @@ then takes effect on the next deploy.
|
|
|
299
372
|
|
|
300
373
|
**`small access`** controls who can open the app. There are two ways, and you can combine them.
|
|
301
374
|
|
|
302
|
-
*By
|
|
303
|
-
- **`add ivan@company.com`** adds the address to `ACCESS_EMAILS`. Ivan opens the app
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
375
|
+
*By address*, like sharing a Google Doc. This is the recommended way:
|
|
376
|
+
- **`add ivan@company.com`** adds the address to `ACCESS_EMAILS`. Ivan opens the app and proves the address is his:
|
|
377
|
+
with **Sign in with Google / Microsoft** (`small sso`), or by entering it and getting a sign-in link by email
|
|
378
|
+
(`small mail`) that works for 15 minutes and only once. The browser remembers the sign-in for 30 days. Passing a
|
|
379
|
+
link on gives nobody anything: only the owner of the mailbox or account gets in.
|
|
307
380
|
- **`add @company.com`** lets in everyone with an address in that domain.
|
|
308
|
-
- **`revoke ivan@company.com`** closes sign-in right after the restart, even in a browser that is already open.
|
|
309
|
-
|
|
310
|
-
|
|
381
|
+
- **`revoke ivan@company.com`** closes sign-in right after the restart, even in a browser that is already open. The
|
|
382
|
+
time also goes to `SESSIONS_REVOKED`, so if you add the address back later, the old sessions stay invalid.
|
|
383
|
+
- **`signout ivan@company.com`** (or `@company.com`, or `--all`) ends the sessions on every device without taking
|
|
384
|
+
access away: for a lost laptop. People can do it themselves with **Sign out on all devices** if the app has a
|
|
385
|
+
volume (`small volume add`).
|
|
386
|
+
- **`log`** prints who signed in, when, how (Google, Microsoft, email link, invite link) and from which IP, plus
|
|
387
|
+
refused sign-ins and access requests, across deploys: `magic_link` writes them as JSON log lines, and Railway
|
|
388
|
+
keeps them as long as your plan keeps logs. `--json` gives one object per line.
|
|
389
|
+
- The first `add` creates `SESSION_SECRET`, the key that signs links and sessions. One way to sign in must be set up:
|
|
390
|
+
`small sso` or `small mail`.
|
|
311
391
|
- `small` passes the address for links in emails to the app itself, in `PUBLIC_URL` (in Railway only): after the
|
|
312
392
|
domain is renamed to `<name>.up.railway.app`, Railway keeps the old address in `RAILWAY_PUBLIC_DOMAIN`, and the
|
|
313
393
|
links would lead to a 404. If you want links on your own domain, set `PUBLIC_URL` in `small.json`; `small` does
|
|
@@ -315,8 +395,7 @@ then takes effect on the next deploy.
|
|
|
315
395
|
- Someone who is not on the list and enters their email gets the same "Check your email" response, and the owner
|
|
316
396
|
receives an access request (in the log, and in Telegram if connected) with a ready-made
|
|
317
397
|
`small access add their@address` command.
|
|
318
|
-
- Works in
|
|
319
|
-
now.
|
|
398
|
+
- Works in all templates: `python-http`, `fastapi`, `node-http` and `streamlit` (see the `streamlit` notes above).
|
|
320
399
|
|
|
321
400
|
*By personal invite link*:
|
|
322
401
|
- **`add bob`** creates a token, adds it to `ACCESS_TOKEN` as `bob:token`, restarts the app and prints the link.
|
|
@@ -325,10 +404,26 @@ then takes effect on the next deploy.
|
|
|
325
404
|
their access.
|
|
326
405
|
|
|
327
406
|
- **`list`** shows who has access: addresses and names (tokens are hidden).
|
|
328
|
-
- **Sign-in log.**
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
407
|
+
- **Sign-in log.** `small access log` (above). In the raw log (`small logs`) each sign-in is one JSON line, for
|
|
408
|
+
example `{"message": "[magic-link] signed in with Google: ivan@company.com, ip 203.0.113.7", "magic_link":
|
|
409
|
+
"sign_in", "method": "google", ...}`; Railway's log explorer filters them with `@magic_link:sign_in`.
|
|
410
|
+
|
|
411
|
+
**`small sso`** sets up sign-in with a work account. Who may sign in is still the access list.
|
|
412
|
+
- **`google`**: "Sign in with Google" for Google Workspace accounts and Gmail addresses. `small` prints what to do
|
|
413
|
+
in Google Cloud (about 5 minutes: an OAuth client of type Web application) and the exact redirect URI to register,
|
|
414
|
+
then asks for the client ID and secret (the secret from `GOOGLE_CLIENT_SECRET` or with hidden input).
|
|
415
|
+
- **`microsoft`**: "Sign in with Microsoft" for the members of one Microsoft Entra organization. `small` prints the
|
|
416
|
+
steps (an app registration, single tenant, a client secret) and the redirect URI; `--tenant` takes the Directory
|
|
417
|
+
(tenant) ID or your domain (`company.com`), which `small` resolves to the ID.
|
|
418
|
+
- **Checked before saving.** `small` shows the provider the client ID and secret; a wrong pair is refused and
|
|
419
|
+
nothing is saved.
|
|
420
|
+
- **Which accounts count.** Google: verified Gmail addresses, or accounts of a Google Workspace organization; a
|
|
421
|
+
personal Google account registered with a company address is refused. Microsoft: members of your tenant only, not
|
|
422
|
+
guests and not other organizations. Details: [SECURITY.md](https://github.com/RuslanKazyradzi13/small-software/blob/main/SECURITY.md#threat-model).
|
|
423
|
+
- **Redirect URI**: `https://<app>/_access/sso/callback`, or `https://<app>/` for `streamlit`. With your own domain,
|
|
424
|
+
set `PUBLIC_URL` in `small.json` first, and register that address.
|
|
425
|
+
- If email sign-in isn't set up, the sign-in page shows only the provider buttons; with both, it shows both.
|
|
426
|
+
- **`off [google|microsoft]`** turns one or both off; with no subcommand you get the status and the redirect URI.
|
|
332
427
|
|
|
333
428
|
**`small mail`** sets how the app sends sign-in emails.
|
|
334
429
|
- **`resend`** sends through [Resend](https://resend.com) over HTTPS. `small` takes the key from `RESEND_API_KEY`
|
|
@@ -379,6 +474,16 @@ then takes effect on the next deploy.
|
|
|
379
474
|
status.
|
|
380
475
|
- **`remove`** removes the domain.
|
|
381
476
|
|
|
477
|
+
**`small volume`** gives the app a persistent disk (a Railway volume).
|
|
478
|
+
- **`add`** creates one at `/data` (`--mount` for another path, outside `/app`) and restarts the app. The app can
|
|
479
|
+
keep files there; the path is in `RAILWAY_VOLUME_MOUNT_PATH`. With no subcommand you get the path and how much is
|
|
480
|
+
used.
|
|
481
|
+
- **Sign-in state.** `magic_link` keeps it in `.magic-link.db` (SQLite) on the volume: used sign-in links stay used
|
|
482
|
+
after a restart, rate limits are shared by worker processes, and **Sign out on all devices** appears. Without a
|
|
483
|
+
volume this state lives in memory and a restart forgets it.
|
|
484
|
+
- **Trade-off.** With a volume each deploy has a few seconds of downtime: two copies of the app can't share the disk.
|
|
485
|
+
A service has one volume.
|
|
486
|
+
|
|
382
487
|
**`small sleep`** controls Railway's sleep mode (serverless).
|
|
383
488
|
- **`on`**: with no incoming requests the app goes to sleep and uses no CPU or memory; the first request wakes it
|
|
384
489
|
up in a few seconds. Handy for apps that are opened a couple of times a day.
|
|
@@ -501,8 +606,9 @@ The tests start a local mock of the Railway API (GraphQL + the upload endpoint)
|
|
|
501
606
|
deploy, repeat deploy, failed build, crash, taken domain, several workspaces, wrong token, ignore rules.
|
|
502
607
|
|
|
503
608
|
Template tests on real frameworks are skipped if the framework is not installed; the rest pass.
|
|
504
|
-
-
|
|
505
|
-
|
|
609
|
+
- `streamlit` tests: the access gate through `streamlit.testing.v1.AppTest` (personal tokens, sign-in by email:
|
|
610
|
+
form, one-time link, session cookie, revocation, sign out; sign-in with Google against a local provider, refusals,
|
|
611
|
+
sign out on all devices) and the server through `python app.py`.
|
|
506
612
|
- 2 `fastapi` tests: `TestClient` and `uvicorn main:app` the way Railway runs it, plus a check that the token is
|
|
507
613
|
hidden in the access log.
|
|
508
614
|
|
|
@@ -517,6 +623,11 @@ Dashboard buttons: the "Open via invite link" redirect and its protection agains
|
|
|
517
623
|
token, with Railway and with Railway unavailable. Sleep mode and the cost estimate in the dashboard, Railway errors
|
|
518
624
|
while computing the cost.
|
|
519
625
|
|
|
626
|
+
`small sso`, `volume`, `access log` and `access signout` are tested against mocks of Railway, Google and Microsoft:
|
|
627
|
+
the credential check, a wrong secret that saves nothing, resolving a domain to a tenant, the Streamlit and custom
|
|
628
|
+
domain redirect URIs, old `magic_link` code, the volume and its mount path, the access log table, `--json` and the MCP
|
|
629
|
+
tool, sign-outs by address, domain and `--all`, forgetting old sign-outs.
|
|
630
|
+
|
|
520
631
|
`small sleep`, `db`, `domain` and `notify` are tested against mocks of Railway and the Telegram Bot API:
|
|
521
632
|
- turning sleep on and off, turning it on again without requests, `--no-deploy`;
|
|
522
633
|
- deploying the Postgres template with default values, the `DATABASE_URL` reference, a workflow error, removing
|
|
@@ -2,20 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
[Русская версия](https://github.com/RuslanKazyradzi13/small-software/blob/main/small-cli/README.ru.md)
|
|
4
4
|
|
|
5
|
-
`small` deploys small team apps to Railway in one command and protects them with sign-in by work
|
|
6
|
-
personal invite links.
|
|
5
|
+
`small` deploys small team apps to Railway in one command and protects them with sign-in by work account (Google,
|
|
6
|
+
Microsoft), work email or personal invite links.
|
|
7
7
|
|
|
8
8
|
```
|
|
9
9
|
small init my-service # skeleton: invite-link protected server, small.json, .gitignore
|
|
10
10
|
cd my-service
|
|
11
11
|
small deploy # → https://my-service-3f9a.up.railway.app
|
|
12
12
|
small open # open with the invite link
|
|
13
|
-
small access add ivan@company.com #
|
|
14
|
-
small
|
|
13
|
+
small access add ivan@company.com # who may sign in, like sharing a Google Doc
|
|
14
|
+
small sso google # "Sign in with Google" (or small sso microsoft)
|
|
15
|
+
small mail resend # or sign-in links by email
|
|
16
|
+
small access log # who signed in, when and how
|
|
15
17
|
small access add bob # or a personal link for bob (small access revoke bob to revoke it)
|
|
16
18
|
small notify telegram # access requests to Telegram
|
|
17
19
|
small env set DEBUG=1 # environment variable in Railway and small.json
|
|
18
20
|
small db add postgres # database, DATABASE_URL in the app
|
|
21
|
+
small volume add # persistent disk: files and sign-in state survive deploys
|
|
19
22
|
small domain add app.example.com # custom domain
|
|
20
23
|
small sleep on # sleep when idle (saves money)
|
|
21
24
|
small logs -f # app logs
|
|
@@ -42,8 +45,25 @@ From source, in a clone of the repository:
|
|
|
42
45
|
pip install ./small-cli
|
|
43
46
|
```
|
|
44
47
|
|
|
45
|
-
**Windows
|
|
46
|
-
|
|
48
|
+
**Windows (recommended): one command in PowerShell.**
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
irm https://raw.githubusercontent.com/RuslanKazyradzi13/small-software/main/install.ps1 | iex
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
It installs small into its own Python environment in `%LOCALAPPDATA%\Programs\small` and adds a `small`
|
|
55
|
+
command that Smart App Control does not block (there is no unsigned `small.exe`). Needs Python 3.9+, no admin
|
|
56
|
+
rights; run it again to upgrade. Uninstall:
|
|
57
|
+
|
|
58
|
+
```powershell
|
|
59
|
+
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/RuslanKazyradzi13/small-software/main/install.ps1))) -Uninstall
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Values with `&` or `|` (URLs, for example) need PowerShell's stop-parsing token:
|
|
63
|
+
`small --% env set "URL=https://x?a=1&b=2"`.
|
|
64
|
+
|
|
65
|
+
**Windows with pip.** `pip install` creates `small.exe`, which Smart App Control / Application Control may
|
|
66
|
+
block because it is unsigned. If that happens, either:
|
|
47
67
|
|
|
48
68
|
- run small through Python, which works everywhere: `python -m small_cli deploy` (any command works this way);
|
|
49
69
|
- or add the `small-cli` folder of a clone to `PATH`, and `small.cmd` will serve the `small` command. Run this in
|
|
@@ -126,12 +146,15 @@ The sample apps are in English: both the page text and the code comments.
|
|
|
126
146
|
`small deploy` passes `ACCESS_TOKEN` from `small.json` to Railway. The app opens at
|
|
127
147
|
`https://<domain>/?token=<ACCESS_TOKEN>`; without the token visitors get a 403 page with a **Request access**
|
|
128
148
|
button. The server listens on `$HOST:$PORT`, by default `0.0.0.0` and the `PORT` set by Railway.
|
|
129
|
-
- **The `streamlit` template.** Streamlit
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
149
|
+
- **The `streamlit` template.** Streamlit can't be wrapped in middleware, so `require_access()` in `app.py` uses
|
|
150
|
+
`MagicLinkGate` from `magic_link.py`: the same variables (`ACCESS_EMAILS`, `ACCESS_TOKEN`, `SESSION_SECRET`,
|
|
151
|
+
`ACCESS_REQUEST_URL`...), one-time links, limits and logs as the other templates, and the access screen follows the
|
|
152
|
+
visitor's browser language. Differences:
|
|
153
|
+
- Sign-in by email: the link leads to `https://<domain>/?t=...` and shows a **Sign in** button; only the click
|
|
154
|
+
signs in. The 30-day session is a signed cookie written by a tiny same-origin frame, so unlike in the other
|
|
155
|
+
templates it is **not HttpOnly**: don't render untrusted HTML in a protected app. **Sign out** is in the sidebar.
|
|
156
|
+
- Personal links keep the token in the address bar (`?token=`), which is how they survive a page reload.
|
|
157
|
+
- Streamlit is a single-page app, so the "no access" screen is served with HTTP 200, not 403.
|
|
135
158
|
Railway runs `python app.py`, and `app.py` restarts itself through `streamlit run` on `$HOST:$PORT`.
|
|
136
159
|
Run it locally the same way: `python app.py`.
|
|
137
160
|
- **Name.** By default, the folder name plus a random suffix, for example `my-service-3f9a`. An explicit name in
|
|
@@ -176,6 +199,12 @@ Buttons on the card of a deployed app:
|
|
|
176
199
|
The token only goes to the browser's address bar on navigation; it is not in the dashboard page data.
|
|
177
200
|
- **Logs** opens a window with the last 200 lines of the app or build log (tabs **App** / **Build**).
|
|
178
201
|
Requires `RAILWAY_API_TOKEN`; without it the button is disabled.
|
|
202
|
+
- **Share** opens who has access: work emails, whole domains and personal-link names (never the links
|
|
203
|
+
themselves). Type `ivan@company.com`, `@company.com` or a name and click **Add**, or click **Revoke** next to
|
|
204
|
+
anyone. It does exactly what `small access add` / `revoke` does: updates small.json and Railway and restarts
|
|
205
|
+
the app. A new personal link is shown only once, with a Copy button, and works like a password. Requires
|
|
206
|
+
`RAILWAY_API_TOKEN`. The page and its requests are protected: a per-run key, `Host`/`Origin` checks, no
|
|
207
|
+
framing, JSON only.
|
|
179
208
|
|
|
180
209
|
- **Port** 3000 by default; if it is busy, any free port is used. The URL is printed to stdout.
|
|
181
210
|
- **The server listens on `127.0.0.1` only** and answers 403 to requests with a foreign `Host` (DNS rebinding
|
|
@@ -219,6 +248,46 @@ $10 per GB of memory, $0.15 per GB of volume. The result reads "≈ $X/mo at cur
|
|
|
219
248
|
- **This is an estimate, not a bill.** Egress ($0.05/GB), the plan's free allowance ($5 on Hobby, $20 on Pro) and
|
|
220
249
|
the subscription are not included. The exact amount is in Railway → Usage.
|
|
221
250
|
|
|
251
|
+
## `small mcp`: AI agents (Claude Code, Cursor...)
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
small mcp
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Runs an [MCP](https://modelcontextprotocol.io) server on stdin/stdout, so an AI coding agent can create, deploy and
|
|
258
|
+
share apps itself: *"Deploy this folder and give ivan@company.com access."*
|
|
259
|
+
|
|
260
|
+
**Connect it:**
|
|
261
|
+
- **Claude Code:** `claude mcp add small -- small mcp` (Windows: `claude mcp add small -- cmd /c small mcp`).
|
|
262
|
+
- **Cursor** (`~/.cursor/mcp.json`), **Claude Desktop**, **Windsurf** and other clients:
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{"mcpServers": {"small": {"command": "small", "args": ["mcp"]}}}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
On Windows use `"command": "cmd", "args": ["/c", "small", "mcp"]`, or `"command": "python", "args": ["-m",
|
|
269
|
+
"small_cli", "mcp"]` if small was installed with pip.
|
|
270
|
+
- `RAILWAY_API_TOKEN` must be in the environment the agent starts from. After setting it, restart the agent.
|
|
271
|
+
|
|
272
|
+
**Tools:**
|
|
273
|
+
|
|
274
|
+
| Tool | What it does |
|
|
275
|
+
|---|---|
|
|
276
|
+
| `init_app` | `small init`: a ready app from a template |
|
|
277
|
+
| `deploy_app` | `small deploy`: returns the URL; progress is streamed while it builds |
|
|
278
|
+
| `share_app` | `small access add`: an email, `@domain` or a personal-link name |
|
|
279
|
+
| `revoke_access` | `small access revoke` |
|
|
280
|
+
| `list_access` | `small access list` |
|
|
281
|
+
| `list_apps` | apps under a folder with URL and latest deploy status |
|
|
282
|
+
| `app_logs` | `small logs`: app or build log |
|
|
283
|
+
| `set_env` | `small env set` / `unset` |
|
|
284
|
+
|
|
285
|
+
- **No delete tool:** `small destroy` stays a terminal command.
|
|
286
|
+
- **Prefer emails.** A personal link (a name instead of an email) is returned to the agent, so it passes through the
|
|
287
|
+
chat like any other text; with email sign-in nothing secret does.
|
|
288
|
+
- The tools run the same code as the commands and answer in small's language (`SMALL_LANG`). Your MCP client
|
|
289
|
+
normally asks you before each tool call.
|
|
290
|
+
|
|
222
291
|
## `small deploy`
|
|
223
292
|
|
|
224
293
|
```
|
|
@@ -247,18 +316,22 @@ These commands work in the folder of a deployed app, that is, where `.small.json
|
|
|
247
316
|
small link [folder] [--as NAME] # print the invite link
|
|
248
317
|
small open [folder] [--as NAME] # the same + open it in the browser
|
|
249
318
|
small access [list | add EMAIL|@DOMAIN|NAME | revoke EMAIL|NAME] # who can sign in
|
|
319
|
+
small access log [-n N] [--json] # who signed in, when and how
|
|
320
|
+
small access signout EMAIL|@DOMAIN|--all # end their sessions on all devices
|
|
321
|
+
small sso [google [--client-id ID] | microsoft [--client-id ID] [--tenant T] | off [PROVIDER]]
|
|
250
322
|
small mail [resend | smtp [--from FROM] [--to TO] | log | off] # emails for sign-in by email
|
|
251
323
|
small notify [telegram [--chat-id ID] | off] # access requests to Telegram
|
|
252
324
|
small env [list [--values] | set K=V ... | unset K ...] [--no-deploy]
|
|
253
325
|
small db [list | add postgres | remove postgres [--yes]]
|
|
326
|
+
small volume [add [--mount /data]] # persistent disk; no argument shows the status
|
|
254
327
|
small domain [list | add DOMAIN [--port N] | remove DOMAIN]
|
|
255
328
|
small sleep [on | off] # sleep mode; no argument shows the status
|
|
256
329
|
small logs [folder] [--build] [-n N] [-f] # log of the latest deploy
|
|
257
330
|
small destroy [folder] [--yes] # remove from Railway
|
|
258
331
|
```
|
|
259
332
|
|
|
260
|
-
Commands that change the app's variables (`access add` / `revoke
|
|
261
|
-
`db add` / `remove`) and `sleep on` / `off` restart the app once. Add `--no-deploy` to skip the restart; the change
|
|
333
|
+
Commands that change the app's variables (`access add` / `revoke` / `signout`, `sso`, `mail`, `notify`,
|
|
334
|
+
`env set` / `unset`, `db add` / `remove`), `volume add` and `sleep on` / `off` restart the app once. Add `--no-deploy` to skip the restart; the change
|
|
262
335
|
then takes effect on the next deploy.
|
|
263
336
|
|
|
264
337
|
**`small link` / `small open`** build `https://<domain>/?token=<token>`.
|
|
@@ -271,15 +344,22 @@ then takes effect on the next deploy.
|
|
|
271
344
|
|
|
272
345
|
**`small access`** controls who can open the app. There are two ways, and you can combine them.
|
|
273
346
|
|
|
274
|
-
*By
|
|
275
|
-
- **`add ivan@company.com`** adds the address to `ACCESS_EMAILS`. Ivan opens the app
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
347
|
+
*By address*, like sharing a Google Doc. This is the recommended way:
|
|
348
|
+
- **`add ivan@company.com`** adds the address to `ACCESS_EMAILS`. Ivan opens the app and proves the address is his:
|
|
349
|
+
with **Sign in with Google / Microsoft** (`small sso`), or by entering it and getting a sign-in link by email
|
|
350
|
+
(`small mail`) that works for 15 minutes and only once. The browser remembers the sign-in for 30 days. Passing a
|
|
351
|
+
link on gives nobody anything: only the owner of the mailbox or account gets in.
|
|
279
352
|
- **`add @company.com`** lets in everyone with an address in that domain.
|
|
280
|
-
- **`revoke ivan@company.com`** closes sign-in right after the restart, even in a browser that is already open.
|
|
281
|
-
|
|
282
|
-
|
|
353
|
+
- **`revoke ivan@company.com`** closes sign-in right after the restart, even in a browser that is already open. The
|
|
354
|
+
time also goes to `SESSIONS_REVOKED`, so if you add the address back later, the old sessions stay invalid.
|
|
355
|
+
- **`signout ivan@company.com`** (or `@company.com`, or `--all`) ends the sessions on every device without taking
|
|
356
|
+
access away: for a lost laptop. People can do it themselves with **Sign out on all devices** if the app has a
|
|
357
|
+
volume (`small volume add`).
|
|
358
|
+
- **`log`** prints who signed in, when, how (Google, Microsoft, email link, invite link) and from which IP, plus
|
|
359
|
+
refused sign-ins and access requests, across deploys: `magic_link` writes them as JSON log lines, and Railway
|
|
360
|
+
keeps them as long as your plan keeps logs. `--json` gives one object per line.
|
|
361
|
+
- The first `add` creates `SESSION_SECRET`, the key that signs links and sessions. One way to sign in must be set up:
|
|
362
|
+
`small sso` or `small mail`.
|
|
283
363
|
- `small` passes the address for links in emails to the app itself, in `PUBLIC_URL` (in Railway only): after the
|
|
284
364
|
domain is renamed to `<name>.up.railway.app`, Railway keeps the old address in `RAILWAY_PUBLIC_DOMAIN`, and the
|
|
285
365
|
links would lead to a 404. If you want links on your own domain, set `PUBLIC_URL` in `small.json`; `small` does
|
|
@@ -287,8 +367,7 @@ then takes effect on the next deploy.
|
|
|
287
367
|
- Someone who is not on the list and enters their email gets the same "Check your email" response, and the owner
|
|
288
368
|
receives an access request (in the log, and in Telegram if connected) with a ready-made
|
|
289
369
|
`small access add their@address` command.
|
|
290
|
-
- Works in
|
|
291
|
-
now.
|
|
370
|
+
- Works in all templates: `python-http`, `fastapi`, `node-http` and `streamlit` (see the `streamlit` notes above).
|
|
292
371
|
|
|
293
372
|
*By personal invite link*:
|
|
294
373
|
- **`add bob`** creates a token, adds it to `ACCESS_TOKEN` as `bob:token`, restarts the app and prints the link.
|
|
@@ -297,10 +376,26 @@ then takes effect on the next deploy.
|
|
|
297
376
|
their access.
|
|
298
377
|
|
|
299
378
|
- **`list`** shows who has access: addresses and names (tokens are hidden).
|
|
300
|
-
- **Sign-in log.**
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
379
|
+
- **Sign-in log.** `small access log` (above). In the raw log (`small logs`) each sign-in is one JSON line, for
|
|
380
|
+
example `{"message": "[magic-link] signed in with Google: ivan@company.com, ip 203.0.113.7", "magic_link":
|
|
381
|
+
"sign_in", "method": "google", ...}`; Railway's log explorer filters them with `@magic_link:sign_in`.
|
|
382
|
+
|
|
383
|
+
**`small sso`** sets up sign-in with a work account. Who may sign in is still the access list.
|
|
384
|
+
- **`google`**: "Sign in with Google" for Google Workspace accounts and Gmail addresses. `small` prints what to do
|
|
385
|
+
in Google Cloud (about 5 minutes: an OAuth client of type Web application) and the exact redirect URI to register,
|
|
386
|
+
then asks for the client ID and secret (the secret from `GOOGLE_CLIENT_SECRET` or with hidden input).
|
|
387
|
+
- **`microsoft`**: "Sign in with Microsoft" for the members of one Microsoft Entra organization. `small` prints the
|
|
388
|
+
steps (an app registration, single tenant, a client secret) and the redirect URI; `--tenant` takes the Directory
|
|
389
|
+
(tenant) ID or your domain (`company.com`), which `small` resolves to the ID.
|
|
390
|
+
- **Checked before saving.** `small` shows the provider the client ID and secret; a wrong pair is refused and
|
|
391
|
+
nothing is saved.
|
|
392
|
+
- **Which accounts count.** Google: verified Gmail addresses, or accounts of a Google Workspace organization; a
|
|
393
|
+
personal Google account registered with a company address is refused. Microsoft: members of your tenant only, not
|
|
394
|
+
guests and not other organizations. Details: [SECURITY.md](https://github.com/RuslanKazyradzi13/small-software/blob/main/SECURITY.md#threat-model).
|
|
395
|
+
- **Redirect URI**: `https://<app>/_access/sso/callback`, or `https://<app>/` for `streamlit`. With your own domain,
|
|
396
|
+
set `PUBLIC_URL` in `small.json` first, and register that address.
|
|
397
|
+
- If email sign-in isn't set up, the sign-in page shows only the provider buttons; with both, it shows both.
|
|
398
|
+
- **`off [google|microsoft]`** turns one or both off; with no subcommand you get the status and the redirect URI.
|
|
304
399
|
|
|
305
400
|
**`small mail`** sets how the app sends sign-in emails.
|
|
306
401
|
- **`resend`** sends through [Resend](https://resend.com) over HTTPS. `small` takes the key from `RESEND_API_KEY`
|
|
@@ -351,6 +446,16 @@ then takes effect on the next deploy.
|
|
|
351
446
|
status.
|
|
352
447
|
- **`remove`** removes the domain.
|
|
353
448
|
|
|
449
|
+
**`small volume`** gives the app a persistent disk (a Railway volume).
|
|
450
|
+
- **`add`** creates one at `/data` (`--mount` for another path, outside `/app`) and restarts the app. The app can
|
|
451
|
+
keep files there; the path is in `RAILWAY_VOLUME_MOUNT_PATH`. With no subcommand you get the path and how much is
|
|
452
|
+
used.
|
|
453
|
+
- **Sign-in state.** `magic_link` keeps it in `.magic-link.db` (SQLite) on the volume: used sign-in links stay used
|
|
454
|
+
after a restart, rate limits are shared by worker processes, and **Sign out on all devices** appears. Without a
|
|
455
|
+
volume this state lives in memory and a restart forgets it.
|
|
456
|
+
- **Trade-off.** With a volume each deploy has a few seconds of downtime: two copies of the app can't share the disk.
|
|
457
|
+
A service has one volume.
|
|
458
|
+
|
|
354
459
|
**`small sleep`** controls Railway's sleep mode (serverless).
|
|
355
460
|
- **`on`**: with no incoming requests the app goes to sleep and uses no CPU or memory; the first request wakes it
|
|
356
461
|
up in a few seconds. Handy for apps that are opened a couple of times a day.
|
|
@@ -473,8 +578,9 @@ The tests start a local mock of the Railway API (GraphQL + the upload endpoint)
|
|
|
473
578
|
deploy, repeat deploy, failed build, crash, taken domain, several workspaces, wrong token, ignore rules.
|
|
474
579
|
|
|
475
580
|
Template tests on real frameworks are skipped if the framework is not installed; the rest pass.
|
|
476
|
-
-
|
|
477
|
-
|
|
581
|
+
- `streamlit` tests: the access gate through `streamlit.testing.v1.AppTest` (personal tokens, sign-in by email:
|
|
582
|
+
form, one-time link, session cookie, revocation, sign out; sign-in with Google against a local provider, refusals,
|
|
583
|
+
sign out on all devices) and the server through `python app.py`.
|
|
478
584
|
- 2 `fastapi` tests: `TestClient` and `uvicorn main:app` the way Railway runs it, plus a check that the token is
|
|
479
585
|
hidden in the access log.
|
|
480
586
|
|
|
@@ -489,6 +595,11 @@ Dashboard buttons: the "Open via invite link" redirect and its protection agains
|
|
|
489
595
|
token, with Railway and with Railway unavailable. Sleep mode and the cost estimate in the dashboard, Railway errors
|
|
490
596
|
while computing the cost.
|
|
491
597
|
|
|
598
|
+
`small sso`, `volume`, `access log` and `access signout` are tested against mocks of Railway, Google and Microsoft:
|
|
599
|
+
the credential check, a wrong secret that saves nothing, resolving a domain to a tenant, the Streamlit and custom
|
|
600
|
+
domain redirect URIs, old `magic_link` code, the volume and its mount path, the access log table, `--json` and the MCP
|
|
601
|
+
tool, sign-outs by address, domain and `--all`, forgetting old sign-outs.
|
|
602
|
+
|
|
492
603
|
`small sleep`, `db`, `domain` and `notify` are tested against mocks of Railway and the Telegram Bot API:
|
|
493
604
|
- turning sleep on and off, turning it on again without requests, `--no-deploy`;
|
|
494
605
|
- deploying the Postgres template with default values, the `DATABASE_URL` reference, a workflow error, removing
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "small-software"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
8
8
|
description = "A cloud for small software: deploy team apps to Railway in one command, private by default, shared by work email"
|
|
9
9
|
license = "MIT"
|
|
10
10
|
license-files = ["LICENSE"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.0"
|