prashna 0.2.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 (35) hide show
  1. prashna-0.2.0/.gitignore +42 -0
  2. prashna-0.2.0/LICENSE +21 -0
  3. prashna-0.2.0/PKG-INFO +117 -0
  4. prashna-0.2.0/README.md +90 -0
  5. prashna-0.2.0/deploy/README.md +176 -0
  6. prashna-0.2.0/distribution/README.md +118 -0
  7. prashna-0.2.0/performance/README.md +119 -0
  8. prashna-0.2.0/prashna/__init__.py +0 -0
  9. prashna-0.2.0/prashna/__main__.py +6 -0
  10. prashna-0.2.0/prashna/auth.py +198 -0
  11. prashna-0.2.0/prashna/backup.py +194 -0
  12. prashna-0.2.0/prashna/cli.py +134 -0
  13. prashna-0.2.0/prashna/db.py +155 -0
  14. prashna-0.2.0/prashna/diagnostics.py +58 -0
  15. prashna-0.2.0/prashna/installers/install.ps1 +22 -0
  16. prashna-0.2.0/prashna/installers/install.sh +24 -0
  17. prashna-0.2.0/prashna/main.py +2800 -0
  18. prashna-0.2.0/prashna/models.py +320 -0
  19. prashna-0.2.0/prashna/oauth.py +115 -0
  20. prashna-0.2.0/prashna/ratelimit.py +90 -0
  21. prashna-0.2.0/prashna/static/assets/common-B1uJCwh4.css +1 -0
  22. prashna-0.2.0/prashna/static/assets/common-C-iimYTB.js +1 -0
  23. prashna-0.2.0/prashna/static/assets/index-DZnWpx3x.js +1 -0
  24. prashna-0.2.0/prashna/static/assets/moderator-dnk9gqKk.js +1 -0
  25. prashna-0.2.0/prashna/static/assets/participant-C9Hyu7Br.js +1 -0
  26. prashna-0.2.0/prashna/static/assets/projector-lCmSbmv0.js +1 -0
  27. prashna-0.2.0/prashna/static/assets/quizzes-BCrX8Dds.js +1 -0
  28. prashna-0.2.0/prashna/static/index.html +133 -0
  29. prashna-0.2.0/prashna/static/moderator.html +133 -0
  30. prashna-0.2.0/prashna/static/participant.html +182 -0
  31. prashna-0.2.0/prashna/static/projector.html +37 -0
  32. prashna-0.2.0/prashna/static/quizzes.html +89 -0
  33. prashna-0.2.0/prashna/static/vendor/qrcode.js +2297 -0
  34. prashna-0.2.0/prashna/ws.py +71 -0
  35. prashna-0.2.0/pyproject.toml +101 -0
@@ -0,0 +1,42 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .DS_Store
5
+
6
+ # SQLite DB and journals — contains user accounts, session data.
7
+ # (-wal / -shm are the WAL-mode sidecar files created at runtime.)
8
+ prashna.db
9
+ prashna.db-journal
10
+ *.db
11
+ *.db-journal
12
+ *.db-wal
13
+ *.db-shm
14
+
15
+ # User-uploaded files
16
+ uploads/*
17
+ !uploads/.gitkeep
18
+
19
+ # Local env / IDE / tooling
20
+ .env
21
+ .env.*
22
+ !.env.example
23
+ .idea/
24
+ .vscode/
25
+ .claude/
26
+ .mcp.json
27
+
28
+ # Frontend dev-lint tooling (no runtime/build artifacts — dev only)
29
+ node_modules/
30
+
31
+ # Lint / test caches & coverage
32
+ .ruff_cache/
33
+ .pytest_cache/
34
+ .coverage
35
+ coverage.xml
36
+ htmlcov/
37
+
38
+ # Vite build output (produced by `npm run build`)
39
+ prashna/static/
40
+
41
+ # Local release archives (published artifacts are attached in GitHub Releases)
42
+ dist/
prashna-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sankaranarayanan Viswanathan
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.
prashna-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.5
2
+ Name: prashna
3
+ Version: 0.2.0
4
+ Summary: Live Q&A and quizzes for any session with an audience
5
+ Project-URL: Homepage, https://kvsankar.github.io/prashna/
6
+ Project-URL: Source, https://github.com/kvsankar/prashna
7
+ Project-URL: Documentation, https://github.com/kvsankar/prashna/tree/main/docs
8
+ Project-URL: Issues, https://github.com/kvsankar/prashna/issues
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: audience,classroom,live,q&a,quiz,training
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Framework :: FastAPI
15
+ Classifier: Intended Audience :: Education
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Education
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: bcrypt>=4.0
20
+ Requires-Dist: fastapi>=0.115
21
+ Requires-Dist: itsdangerous>=2.2
22
+ Requires-Dist: pydantic>=2.7
23
+ Requires-Dist: python-multipart>=0.0.9
24
+ Requires-Dist: sqlalchemy>=2.0
25
+ Requires-Dist: uvicorn[standard]>=0.30
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Prashna
29
+
30
+ ![How prashna brings an admin, moderators, participants and a projector into one live session for Q&A and quizzes, and what it produces afterwards](https://raw.githubusercontent.com/kvsankar/prashna/main/docs/images/prashna-infographic.png)
31
+
32
+ *How prashna runs a session with an audience: an admin sets it up, a moderator runs the room, participants join on their phones and the projector shows the room the same state. Questions are asked and upvoted, versioned quizzes are run question by question, and the session ends with ranked questions, quiz results, run reports, attendee profiles and feedback. The [infographic source](https://github.com/kvsankar/prashna/blob/main/docs/images/prashna-infographic.html) is kept with the image.*
33
+
34
+ A live Q&A and quiz tool for any session with an audience: training,
35
+ lectures, workshops, town halls, meetups. *Prashna* (प्रश्न) is
36
+ Sanskrit for *question*.
37
+
38
+ Two core features:
39
+
40
+ - **Anonymous Q&A.** Participants post questions and upvote each other's.
41
+ The moderator responds in text or out loud. A removed question is hidden
42
+ from participants and kept for the record.
43
+ - **Moderator-run quizzes.** Single- or multiple-choice questions, optional
44
+ images, anonymous answers, results revealed one question at a time, a lobby
45
+ screen on the projector, and optional live counts while people answer.
46
+ Quizzes are versioned: editing a quiz never changes the report of a past
47
+ run.
48
+
49
+ A **projector view** for the screen at the front of the room shows the join
50
+ code, a QR code for the participant link, the top questions, and the current
51
+ quiz question in step with the moderator.
52
+
53
+ ## Install
54
+
55
+ One command, on macOS, Linux, or Windows:
56
+
57
+ ```bash
58
+ curl -LsSf https://kvsankar.github.io/prashna/install.sh | sh # macOS / Linux
59
+ powershell -ExecutionPolicy ByPass -c "irm https://kvsankar.github.io/prashna/install.ps1 | iex" # Windows
60
+ ```
61
+
62
+ Then start it:
63
+
64
+ ```bash
65
+ prashna
66
+ ```
67
+
68
+ If you already have [uv](https://docs.astral.sh/uv/), `uvx prashna` installs
69
+ and starts it in one step. Update with `uv tool upgrade prashna`.
70
+ `prashna --help` lists the options: port, data folder, and backups.
71
+
72
+ ## First run
73
+
74
+ `prashna` serves on <http://127.0.0.1:8000> and opens your browser on a
75
+ one-time page where you choose the admin's username and password. The page
76
+ works once. After that, sign in to create sessions and write quizzes.
77
+ Prashna keeps its data in your app-data folder.
78
+
79
+ ## Roles
80
+
81
+ | Role | Page | Who gets in |
82
+ | --- | --- | --- |
83
+ | Admin | `/` | The person who signed in with the admin password (or an allowed GitHub account). Creates sessions and quizzes. |
84
+ | Moderator | `/m/<code>` | The admin who owns the session, or anyone the admin gives the moderator link to, such as a co-host. Runs the room. |
85
+ | Participant | `/s/<code>` | Anyone with the session code or QR code. No account; an optional profile. |
86
+ | Projector | `/p/<code>` | Anyone. Read-only, for the room's screen. |
87
+ | Quiz library | `/quizzes` | The admin. Writes quizzes and reads run reports. |
88
+
89
+ The admin who owns a session can do everything a moderator can, on the same
90
+ page, plus the session's settings. The moderator link is how the admin hands
91
+ the room to someone without an admin account.
92
+
93
+ ## Documentation
94
+
95
+ - [Behaviour specification](https://github.com/kvsankar/prashna/blob/main/docs/specs.md): every use case and its tests.
96
+ - [UX rules](https://github.com/kvsankar/prashna/blob/main/docs/UX.md): the frontend design system.
97
+ - [Architecture](https://github.com/kvsankar/prashna/blob/main/docs/architecture.md): components, data model, design decisions, and risks.
98
+ - [Development guide](https://github.com/kvsankar/prashna/blob/main/docs/development.md): setup, environment variables, tests, checks, and releases.
99
+ - [Engineering backlog](https://github.com/kvsankar/prashna/blob/main/docs/todo.md).
100
+ - [Performance measurement](https://github.com/kvsankar/prashna/blob/main/performance/README.md).
101
+
102
+ To run Prashna in Docker instead, see the
103
+ [container guide](https://github.com/kvsankar/prashna/blob/main/distribution/README.md).
104
+ The production deployment behind nginx is described in the
105
+ [deployment runbook](https://github.com/kvsankar/prashna/blob/main/deploy/README.md).
106
+
107
+ ## License
108
+
109
+ MIT. See [LICENSE](https://github.com/kvsankar/prashna/blob/main/LICENSE).
110
+
111
+ ## Status
112
+
113
+ Prashna runs in production at `prashna.sankara.net` for a single operator. It
114
+ is released as a container image.
115
+ Product ideas that need a decision are listed in the
116
+ [deferred appendix](https://github.com/kvsankar/prashna/blob/main/docs/specs.md#deferred-decision-needed)
117
+ of the specification.
@@ -0,0 +1,90 @@
1
+ # Prashna
2
+
3
+ ![How prashna brings an admin, moderators, participants and a projector into one live session for Q&A and quizzes, and what it produces afterwards](https://raw.githubusercontent.com/kvsankar/prashna/main/docs/images/prashna-infographic.png)
4
+
5
+ *How prashna runs a session with an audience: an admin sets it up, a moderator runs the room, participants join on their phones and the projector shows the room the same state. Questions are asked and upvoted, versioned quizzes are run question by question, and the session ends with ranked questions, quiz results, run reports, attendee profiles and feedback. The [infographic source](https://github.com/kvsankar/prashna/blob/main/docs/images/prashna-infographic.html) is kept with the image.*
6
+
7
+ A live Q&A and quiz tool for any session with an audience: training,
8
+ lectures, workshops, town halls, meetups. *Prashna* (प्रश्न) is
9
+ Sanskrit for *question*.
10
+
11
+ Two core features:
12
+
13
+ - **Anonymous Q&A.** Participants post questions and upvote each other's.
14
+ The moderator responds in text or out loud. A removed question is hidden
15
+ from participants and kept for the record.
16
+ - **Moderator-run quizzes.** Single- or multiple-choice questions, optional
17
+ images, anonymous answers, results revealed one question at a time, a lobby
18
+ screen on the projector, and optional live counts while people answer.
19
+ Quizzes are versioned: editing a quiz never changes the report of a past
20
+ run.
21
+
22
+ A **projector view** for the screen at the front of the room shows the join
23
+ code, a QR code for the participant link, the top questions, and the current
24
+ quiz question in step with the moderator.
25
+
26
+ ## Install
27
+
28
+ One command, on macOS, Linux, or Windows:
29
+
30
+ ```bash
31
+ curl -LsSf https://kvsankar.github.io/prashna/install.sh | sh # macOS / Linux
32
+ powershell -ExecutionPolicy ByPass -c "irm https://kvsankar.github.io/prashna/install.ps1 | iex" # Windows
33
+ ```
34
+
35
+ Then start it:
36
+
37
+ ```bash
38
+ prashna
39
+ ```
40
+
41
+ If you already have [uv](https://docs.astral.sh/uv/), `uvx prashna` installs
42
+ and starts it in one step. Update with `uv tool upgrade prashna`.
43
+ `prashna --help` lists the options: port, data folder, and backups.
44
+
45
+ ## First run
46
+
47
+ `prashna` serves on <http://127.0.0.1:8000> and opens your browser on a
48
+ one-time page where you choose the admin's username and password. The page
49
+ works once. After that, sign in to create sessions and write quizzes.
50
+ Prashna keeps its data in your app-data folder.
51
+
52
+ ## Roles
53
+
54
+ | Role | Page | Who gets in |
55
+ | --- | --- | --- |
56
+ | Admin | `/` | The person who signed in with the admin password (or an allowed GitHub account). Creates sessions and quizzes. |
57
+ | Moderator | `/m/<code>` | The admin who owns the session, or anyone the admin gives the moderator link to, such as a co-host. Runs the room. |
58
+ | Participant | `/s/<code>` | Anyone with the session code or QR code. No account; an optional profile. |
59
+ | Projector | `/p/<code>` | Anyone. Read-only, for the room's screen. |
60
+ | Quiz library | `/quizzes` | The admin. Writes quizzes and reads run reports. |
61
+
62
+ The admin who owns a session can do everything a moderator can, on the same
63
+ page, plus the session's settings. The moderator link is how the admin hands
64
+ the room to someone without an admin account.
65
+
66
+ ## Documentation
67
+
68
+ - [Behaviour specification](https://github.com/kvsankar/prashna/blob/main/docs/specs.md): every use case and its tests.
69
+ - [UX rules](https://github.com/kvsankar/prashna/blob/main/docs/UX.md): the frontend design system.
70
+ - [Architecture](https://github.com/kvsankar/prashna/blob/main/docs/architecture.md): components, data model, design decisions, and risks.
71
+ - [Development guide](https://github.com/kvsankar/prashna/blob/main/docs/development.md): setup, environment variables, tests, checks, and releases.
72
+ - [Engineering backlog](https://github.com/kvsankar/prashna/blob/main/docs/todo.md).
73
+ - [Performance measurement](https://github.com/kvsankar/prashna/blob/main/performance/README.md).
74
+
75
+ To run Prashna in Docker instead, see the
76
+ [container guide](https://github.com/kvsankar/prashna/blob/main/distribution/README.md).
77
+ The production deployment behind nginx is described in the
78
+ [deployment runbook](https://github.com/kvsankar/prashna/blob/main/deploy/README.md).
79
+
80
+ ## License
81
+
82
+ MIT. See [LICENSE](https://github.com/kvsankar/prashna/blob/main/LICENSE).
83
+
84
+ ## Status
85
+
86
+ Prashna runs in production at `prashna.sankara.net` for a single operator. It
87
+ is released as a container image.
88
+ Product ideas that need a decision are listed in the
89
+ [deferred appendix](https://github.com/kvsankar/prashna/blob/main/docs/specs.md#deferred-decision-needed)
90
+ of the specification.
@@ -0,0 +1,176 @@
1
+ # Deploy
2
+
3
+ This directory holds everything the **Hetzner** box needs to run Prashna at
4
+ `https://prashna.sankara.net`.
5
+
6
+ Laptop and other self-hosted container installs use the root `compose.yaml`
7
+ instead; see [`distribution/README.md`](../distribution/README.md).
8
+
9
+ ## One-time setup
10
+
11
+ 1. **DNS** — create a proxied Cloudflare `A` record whose origin value is:
12
+
13
+ ```
14
+ prashna.sankara.net → <server-ip>
15
+ ```
16
+
17
+ With the orange cloud enabled, public DNS correctly returns Cloudflare
18
+ anycast addresses rather than the server's IP. Verify the record and proxy
19
+ state in Cloudflare before continuing.
20
+
21
+ 2. **GitHub Actions** — the `Build & publish Docker image` workflow pushes to
22
+ `ghcr.io/kvsankar/prashna`. No extra secrets needed; it uses the built-in
23
+ `GITHUB_TOKEN` with `packages:write` permission. Trigger it once by pushing
24
+ to `main` or running it manually so the image exists.
25
+
26
+ 3. **On the server**, install nginx and Certbot if they are not already
27
+ present. `setup.sh` installs Docker when necessary, but it expects the nginx
28
+ and Certbot commands to exist before configuring TLS.
29
+
30
+ 4. **If the GHCR package is private**, sign in to `ghcr.io` on the server
31
+ first, as described in
32
+ [Registry access](../distribution/README.md#registry-access).
33
+
34
+ 5. **Run the bootstrap**:
35
+
36
+ ```bash
37
+ git clone git@github.com:kvsankar/prashna.git ~/repos/prashna
38
+ cd ~/repos/prashna
39
+ CERTBOT_EMAIL=you@example.com ./deploy/setup.sh
40
+ ```
41
+
42
+ This installs Docker when it is missing, lays down `/opt/prashna/` with a
43
+ generated `PRASHNA_SECRET_KEY` in `.env`, pulls the image, installs the
44
+ nginx site, prepares nginx for Cloudflare proxying
45
+ (`prepare-cloudflare-proxy.sh`), requests a TLS certificate with Certbot,
46
+ enables HTTP/2 (`enable-http2.sh`), and starts the container.
47
+
48
+ 6. **Create the first admin.** Find the one-time setup link in the log and
49
+ open it on `https://prashna.sankara.net`:
50
+
51
+ ```bash
52
+ cd /opt/prashna && docker compose logs prashna | grep setup=
53
+ ```
54
+
55
+ With GitHub sign-in configured, an allowlisted GitHub account can create
56
+ the admin instead.
57
+
58
+ ## Updating
59
+
60
+ Either:
61
+
62
+ ```bash
63
+ cd /opt/prashna && docker compose pull && docker compose up -d --wait
64
+ ```
65
+
66
+ Or wire up the optional `deploy.yml` workflow (not included by default) that
67
+ SSHes in and runs that on every push to `main`.
68
+
69
+ ## Cloudflare visitor IP and HTTP/2
70
+
71
+ `setup.sh` runs two idempotent nginx helpers. Each one backs up the site file,
72
+ validates the result with `nginx -t`, and restores the backup if validation
73
+ fails.
74
+
75
+ - `prepare-cloudflare-proxy.sh` downloads Cloudflare's published IP ranges and
76
+ writes them to `/etc/nginx/snippets/prashna-cloudflare-realip.conf` as
77
+ `set_real_ip_from` lines with `real_ip_header CF-Connecting-IP`. It includes
78
+ that file in the site. nginx then sees the visitor's address rather than a
79
+ Cloudflare edge address, and passes it on in `X-Forwarded-For`, which the
80
+ application reads with `PRASHNA_TRUSTED_PROXY_HOPS=1`. Re-run the script to
81
+ refresh the ranges.
82
+ - `enable-http2.sh` adds `http2` to the Certbot-managed TLS listeners without
83
+ touching the certificates.
84
+
85
+ Both read `PRASHNA_DOMAIN` and `PRASHNA_NGINX_SITE_PATH` to target another
86
+ site.
87
+
88
+ ## Cloudflare HTML-shell caching
89
+
90
+ The application marks only its static HTML page shells as edge-cacheable. API
91
+ responses, bad-session redirects, and personalized data remain `no-store`.
92
+ Cloudflare still needs a Cache Rule to make non-static HTML eligible. Preview
93
+ the idempotent ruleset update, then apply it with a token that can edit zone
94
+ rulesets:
95
+
96
+ ```bash
97
+ export CLOUDFLARE_API_TOKEN=... # Zone Rulesets:Edit and Zone:Read
98
+ export CLOUDFLARE_ZONE_ID=... # optional; discovered by zone name
99
+ deploy/configure-cloudflare-cache.sh --dry-run
100
+ deploy/configure-cloudflare-cache.sh --apply
101
+ ```
102
+
103
+ The helper preserves unrelated cache rules and replaces only the stable
104
+ `prashna_html_shells` rule. It covers `/`, `/quizzes`, and the `/s/`, `/m/`,
105
+ and `/p/` page prefixes, and respects the application's browser and edge TTLs.
106
+ Verify a repeated page response with `CF-Cache-Status`; purge the affected URLs
107
+ after a deploy if the five-minute edge TTL is too long for an urgent shell fix.
108
+
109
+ ## Smoke test
110
+
111
+ Run the production-like Docker Compose smoke from a development checkout with:
112
+
113
+ ```bash
114
+ PRASHNA_COMPOSE_SMOKE=1 uv run pytest tests/ops/test_compose_smoke.py -q
115
+ ```
116
+
117
+ The smoke builds the local image, starts Compose with temporary data and upload
118
+ directories, waits for `/api/admin/status`, creates the first admin user,
119
+ uploads an image, restarts the service, and verifies the SQLite file, upload,
120
+ health endpoint, cookie signing, and safe diagnostics survive the restart.
121
+
122
+ After an image is published, CI runs the same state/restart checks against its
123
+ exact digest through the root distribution Compose model and also verifies the
124
+ in-image backup command.
125
+
126
+ The test removes its Compose project at the end. If it is interrupted, clean up
127
+ with `docker compose --project-name <printed-project> down --volumes
128
+ --remove-orphans`.
129
+
130
+ ## Files
131
+
132
+ - `docker-compose.yml`: the service definition, copied to `/opt/prashna/`.
133
+ It requires `PRASHNA_SECRET_KEY` in `.env`.
134
+ - `env.example`: reference for `/opt/prashna/.env`. `setup.sh` writes that
135
+ file with a generated key when it is missing.
136
+ - `nginx-prashna.conf`: the nginx server block, symlinked into
137
+ `sites-enabled/`.
138
+ - `setup.sh`: the idempotent bootstrap script.
139
+ - `prepare-cloudflare-proxy.sh`: Cloudflare visitor-IP configuration for nginx.
140
+ - `enable-http2.sh`: HTTP/2 on the TLS listeners.
141
+ - `configure-cloudflare-cache.sh`: dry-run and apply reconciler for the
142
+ Cloudflare Cache Rule for HTML shells.
143
+
144
+ ## Persistent data
145
+
146
+ - SQLite DB → `/opt/prashna/data/prashna.db`
147
+ - User uploads → `/opt/prashna/uploads/`
148
+
149
+ Back these two up. Everything else is rebuilt from the image.
150
+
151
+ The container writes as UID/GID `1001:1001`, so those two host directories must
152
+ be writable by that identity. If startup logs show SQLite or upload permission
153
+ errors, fix ownership with:
154
+
155
+ ```bash
156
+ sudo chown -R 1001:1001 /opt/prashna/data /opt/prashna/uploads
157
+ ```
158
+
159
+ For local smoke tests only, the compose file accepts `PRASHNA_PORT`,
160
+ `PRASHNA_DATA_DIR`, and `PRASHNA_UPLOADS_HOST_DIR` overrides. Here
161
+ `PRASHNA_DATA_DIR` is the host folder mounted at `/data`; it is a Compose
162
+ variable, unrelated to the `prashna` command's `PRASHNA_DATA_DIR`. Production
163
+ keeps the `/opt/prashna` defaults.
164
+
165
+ ## Logs and retention
166
+
167
+ The app writes operator diagnostics to the container log stream. Inspect them
168
+ with:
169
+
170
+ ```bash
171
+ cd /opt/prashna && docker compose logs -f prashna
172
+ ```
173
+
174
+ Keep at least 7 days of application logs on the host or in your external log
175
+ shipper. Configure that retention in Docker, journald, or the server's logging
176
+ stack; Prashna emits safe diagnostic context but does not own log rotation.
@@ -0,0 +1,118 @@
1
+ # Prashna local distribution
2
+
3
+ This bundle runs the published Prashna container on a laptop or a small
4
+ self-hosted machine. It is separate from the Hetzner production configuration.
5
+
6
+ ## Start
7
+
8
+ 1. Install Docker Desktop or Docker Engine with the Compose plugin.
9
+ 2. Start Prashna and wait for its health check:
10
+
11
+ ```bash
12
+ docker compose up -d --wait
13
+ ```
14
+
15
+ 3. On a fresh install, find the one-time setup link in the log:
16
+
17
+ ```bash
18
+ docker compose logs prashna | grep setup=
19
+ ```
20
+
21
+ 4. Open <http://127.0.0.1:8000/> plus the `/#setup=...` path from the log,
22
+ and choose the admin's username and password. The link works once.
23
+
24
+ No `.env` is needed. The cookie-signing key is generated into the data volume
25
+ on first start. To change the port or bind address, pin a version, or set
26
+ optional values, copy `compose.env.example` to `.env`; the file describes each
27
+ setting.
28
+
29
+ The database and uploads live in the Compose named volumes `prashna-data`
30
+ (mounted at `/data`) and `prashna-uploads` (mounted at `/app/uploads`). They
31
+ survive container replacement.
32
+
33
+ ## Start without Compose
34
+
35
+ The image also runs on its own:
36
+
37
+ ```bash
38
+ docker run -d --name prashna -p 127.0.0.1:8000:8000 \
39
+ -v prashna-data:/data -v prashna-uploads:/app/uploads \
40
+ ghcr.io/kvsankar/prashna
41
+ docker logs prashna 2>&1 | grep setup=
42
+ ```
43
+
44
+ ## Upgrade
45
+
46
+ The release bundle pins `PRASHNA_IMAGE_TAG` to its version. Upgrade only after
47
+ reading the release notes, then change the tag in `.env` and run
48
+ `docker compose pull` and `docker compose up -d --wait`.
49
+
50
+ ## Registry access
51
+
52
+ Anonymous pulls work only while the `prashna` container package is set to
53
+ **Public** in GitHub Packages. That setting belongs to the GitHub account, not
54
+ to the repository workflow. While the package is private, authorized users
55
+ first sign in with a GitHub token that has `read:packages`:
56
+
57
+ ```bash
58
+ echo "$GHCR_TOKEN" | docker login ghcr.io -u YOUR_GITHUB_LOGIN --password-stdin
59
+ ```
60
+
61
+ Do not put the token in `.env`; that file is passed to Compose and is for
62
+ application configuration only.
63
+
64
+ ## Share temporarily with ngrok
65
+
66
+ Set `PRASHNA_TRUSTED_PROXY_HOPS=1` in `.env`, restart with
67
+ `docker compose up -d`, and run:
68
+
69
+ ```bash
70
+ ngrok http 8000
71
+ ```
72
+
73
+ The ngrok URL is public. Stop ngrok when the session is over.
74
+
75
+ ## Backup
76
+
77
+ The database and uploads live in Docker named volumes. This command creates a
78
+ consistent archive inside the container, including SQLite WAL state and all
79
+ uploads:
80
+
81
+ ```bash
82
+ mkdir -p backups
83
+ name="prashna-$(date -u +%Y%m%dT%H%M%SZ).tar.gz"
84
+ docker compose exec -T prashna python -m prashna.backup create "/data/$name"
85
+ docker compose cp "prashna:/data/$name" "backups/$name"
86
+ docker compose exec -T prashna rm "/data/$name"
87
+ ```
88
+
89
+ Keep backup archives outside the Docker volumes.
90
+
91
+ ## Restore
92
+
93
+ Restore replaces the database and uploads. The command creates an additional
94
+ pre-restore archive beside the staged archive before changing anything.
95
+
96
+ ```bash
97
+ archive=prashna-YYYYMMDDTHHMMSSZ.tar.gz
98
+ docker compose cp "backups/$archive" "prashna:/data/$archive"
99
+ docker compose stop prashna
100
+ docker compose run --rm --no-deps prashna \
101
+ python -m prashna.backup restore "/data/$archive" --confirm
102
+ docker compose start prashna
103
+ docker compose exec -T prashna rm "/data/$archive"
104
+ ```
105
+
106
+ After restoring, open <http://127.0.0.1:8000/api/admin/status> and confirm the
107
+ expected session data. Copy the reported `prashna-pre-restore-*.tar.gz` out of
108
+ the container before deleting it.
109
+
110
+ ## Useful commands
111
+
112
+ ```bash
113
+ docker compose ps
114
+ docker compose logs -f prashna
115
+ docker compose restart prashna
116
+ docker compose down # keeps named volumes
117
+ docker compose down --volumes # DELETES database and uploads
118
+ ```
@@ -0,0 +1,119 @@
1
+ # Performance measurement
2
+
3
+ Measure Prashna at three separate layers:
4
+
5
+ 1. `Server-Timing: app;dur=...` isolates FastAPI application time.
6
+ 2. Browser navigation and resource timing captures cold/repeat loads,
7
+ transfers, protocols, paint milestones, and the application-defined
8
+ `prashna-usable` milestone.
9
+ 3. Structured application logs identify slow or state-changing requests
10
+ without storing credentials, participant identity, session URLs, or Q&A
11
+ content.
12
+
13
+ `measure.py` records the browser layer plus the server timing, five slowest
14
+ resources, transfer sizes, protocol, navigation type, and Cloudflare colo.
15
+ Reports are JSON so runs can be compared in CI or retained as release evidence.
16
+
17
+ Build the frontend and install Chromium before measuring a local server:
18
+
19
+ ```bash
20
+ npm run build
21
+ uv run playwright install chromium
22
+ uv run python performance/measure.py \
23
+ --base-url http://127.0.0.1:8000 \
24
+ --session-code ABC123 \
25
+ --runs 5 \
26
+ --output /tmp/prashna-perf.json
27
+ ```
28
+
29
+ For production, save a baseline and compare the same routes after a change:
30
+
31
+ ```bash
32
+ uv run python performance/measure.py \
33
+ --base-url https://prashna.sankara.net \
34
+ --session-code ABC123 \
35
+ --budget performance/budgets.json \
36
+ --output /tmp/prashna-before.json
37
+
38
+ uv run python performance/measure.py \
39
+ --base-url https://prashna.sankara.net \
40
+ --session-code ABC123 \
41
+ --baseline /tmp/prashna-before.json \
42
+ --max-regression-percent 20 \
43
+ --budget performance/budgets.json \
44
+ --output /tmp/prashna-after.json
45
+ ```
46
+
47
+ Use repeated samples from the same machine and network. Do not compare one
48
+ cold browser run with one warm run or infer browser speed from server timing
49
+ alone. Production budgets are intentionally broad enough for normal network
50
+ variation; baseline comparison provides the more sensitive release signal.
51
+ Every measured page must emit the usable milestone; a fast error or incomplete
52
+ boot therefore cannot pass merely because network activity stopped.
53
+ With the default five samples, release gates use browser/transfer medians and
54
+ application-time p95. Browser p95 remains in every report for diagnosis, but
55
+ needs at least 20 samples before it is a useful percentile rather than nearly
56
+ the single slowest request.
57
+
58
+ Optional authenticated measurements read secrets from environment variables
59
+ without writing them to reports:
60
+
61
+ ```bash
62
+ uv run python performance/measure.py \
63
+ --base-url https://prashna.sankara.net \
64
+ --session-code ABC123 \
65
+ --moderator-key-env PRASHNA_PERF_MOD_KEY \
66
+ --route moderator=/m/ABC123
67
+ ```
68
+
69
+ For an admin page, pass both `--admin-username-env` and
70
+ `--admin-password-env`. Never put credentials or moderator keys directly in a
71
+ command, report, or committed file.
72
+
73
+ ## Interpret high variance
74
+
75
+ Do not infer browser performance from application timing or one public request.
76
+ When `Server-Timing` stays low but navigation varies, separate these fault
77
+ domains:
78
+
79
+ - client access network and ISP;
80
+ - client-to-Cloudflare ingress routing;
81
+ - Cloudflare edge and cache behavior;
82
+ - Cloudflare-to-Hetzner origin routing; and
83
+ - nginx, FastAPI, SQLite, and host capacity.
84
+
85
+ Capture several successful and failed samples from the same machine and
86
+ network. Record UTC time, client ISP/ASN, `CF-RAY`, `CF-Cache-Status`, negotiated
87
+ protocol, cURL phase timings, and MTR/traceroute output. Compare against an
88
+ unrelated control host during the same interval.
89
+
90
+ For Cloudflare routing, compare `https://prashna.sankara.net/cdn-cgi/trace`
91
+ with Cloudflare control endpoints from the same connection. Different `colo`
92
+ values are possible because anycast selection is destination-prefix-specific;
93
+ the value is not simply the geographically nearest data center. A different
94
+ colo explains the path selection, but does not by itself prove the cause of
95
+ latency variance. Correlate it with timestamped timings, Ray IDs, packet loss,
96
+ and path traces.
97
+
98
+ There is no zone setting that pins client ingress to a particular Cloudflare
99
+ colo. Argo Smart Routing affects the edge-to-origin path, not client-to-edge
100
+ selection. Keep the hostname proxied unless collected evidence shows the proxy
101
+ itself is the regression. The nginx visitor-IP and HTTP/2 setup and the
102
+ HTML-shell cache rule are described in
103
+ [`deploy/README.md`](../deploy/README.md#cloudflare-visitor-ip-and-http2).
104
+
105
+ ## Release verification
106
+
107
+ For a performance-sensitive release:
108
+
109
+ 1. Run lint and the full automated suite.
110
+ 2. Save a pre-release JSON report when practical.
111
+ 3. Deploy and verify the intended immutable image revision.
112
+ 4. Check container health, `/api/admin/status`, and a direct static page.
113
+ 5. Repeat the browser measurement from the same location and network.
114
+ 6. Confirm HTTP/2 or HTTP/3, immutable compressed assets, page-shell cache
115
+ behavior, API `no-store`, and `Server-Timing`.
116
+
117
+ Five-run gates use browser and transfer medians plus application p95. Retain
118
+ browser p95 for diagnosis, but use at least 20 samples before treating it as a
119
+ meaningful tail percentile.