stkit 17.0.0__py3-none-any.whl
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.
- stk/__init__.py +19 -0
- stk/__main__.py +6 -0
- stk/agent_login.py +78 -0
- stk/app.py +323 -0
- stk/checks.py +634 -0
- stk/cli/__init__.py +49 -0
- stk/cli/agent.py +267 -0
- stk/cli/base.py +79 -0
- stk/cli/database.py +199 -0
- stk/cli/develop.py +63 -0
- stk/cli/doctor.py +262 -0
- stk/cli/main.py +101 -0
- stk/cli/reports.py +296 -0
- stk/cli/smoke.py +399 -0
- stk/cli/users.py +259 -0
- stk/cli/watch.py +68 -0
- stk/extensions.py +29 -0
- stk/migrations/__init__.py +130 -0
- stk/migrations/env.py +58 -0
- stk/migrations/script.py.mako +17 -0
- stk/migrations/versions/stk_0001_initial_schema.py +213 -0
- stk/portal/views.py +34 -0
- stk/public/__init__.py +0 -0
- stk/public/models.py +0 -0
- stk/public/views.py +344 -0
- stk/scaffold/__init__.py +1 -0
- stk/scaffold/app/CLAUDE.md +65 -0
- stk/scaffold/app/Dockerfile +41 -0
- stk/scaffold/app/README.md +16 -0
- stk/scaffold/app/__app__/__init__.py +1 -0
- stk/scaffold/app/__app__/app.py +8 -0
- stk/scaffold/app/__app__/settings.py +7 -0
- stk/scaffold/app/claude/skills/stk-api/SKILL.md +52 -0
- stk/scaffold/app/claude/skills/stk-blueprint/SKILL.md +48 -0
- stk/scaffold/app/claude/skills/stk-deploy/SKILL.md +61 -0
- stk/scaffold/app/claude/skills/stk-lessons/SKILL.md +58 -0
- stk/scaffold/app/claude/skills/stk-migrate/SKILL.md +96 -0
- stk/scaffold/app/docker-compose.yml +88 -0
- stk/scaffold/app/dockerignore +13 -0
- stk/scaffold/app/env-sample +78 -0
- stk/scaffold/app/gitignore +8 -0
- stk/scaffold/app/nginx/nginx.conf +46 -0
- stk/scaffold/app/nginx/stk.conf +36 -0
- stk/scaffold/app/postgres/init-db.sh +8 -0
- stk/scaffold/app/pyproject.toml +23 -0
- stk/scaffold/app/tests/__init__.py +0 -0
- stk/scaffold/app/tests/test_app.py +23 -0
- stk/scaffold/generator.py +209 -0
- stk/scaffold/templates.py +437 -0
- stk/security.py +145 -0
- stk/settings.py +147 -0
- stk/shell.py +284 -0
- stk/static/VERSIONS.txt +4 -0
- stk/static/css/app.css +229 -0
- stk/static/css/layout.css +330 -0
- stk/static/css/vuetify.min.css +6 -0
- stk/static/icons/fonts/tabler-icons.woff2 +0 -0
- stk/static/icons/tabler-icons.min.css +4 -0
- stk/static/img/auth-bg.webp +0 -0
- stk/static/img/favicon.ico +0 -0
- stk/static/img/stk.svg +1664 -0
- stk/static/js/axios.min.js +5 -0
- stk/static/js/components/NotificationDropdown.js +156 -0
- stk/static/js/components/ThemeSwitcher.js +68 -0
- stk/static/js/components/TransitionExpand.js +64 -0
- stk/static/js/components/VerticalNavGroup.js +119 -0
- stk/static/js/components/VerticalNavLink.js +36 -0
- stk/static/js/components/VerticalNavSectionTitle.js +21 -0
- stk/static/js/components/index.js +47 -0
- stk/static/js/config.js +150 -0
- stk/static/js/vue.min.js +13 -0
- stk/static/js/vuetify.min.js +3148 -0
- stk/static/robots.txt +1 -0
- stk/tasks/__init__.py +78 -0
- stk/templates/401.html +14 -0
- stk/templates/404.html +12 -0
- stk/templates/500.html +13 -0
- stk/templates/_brand.html +1 -0
- stk/templates/auth_layout.html +26 -0
- stk/templates/cms/activities.html +135 -0
- stk/templates/cms/roles.html +215 -0
- stk/templates/cms/users.html +312 -0
- stk/templates/dashboard.html +132 -0
- stk/templates/index.html +52 -0
- stk/templates/layout.html +328 -0
- stk/templates/login-layout.html +54 -0
- stk/templates/security/_macros.html +39 -0
- stk/templates/security/_messages.html +12 -0
- stk/templates/security/change_password.html +100 -0
- stk/templates/security/confirm_email.html +44 -0
- stk/templates/security/email/change_notice.html +4 -0
- stk/templates/security/email/change_notice.txt +5 -0
- stk/templates/security/email/confirmation_instructions.html +3 -0
- stk/templates/security/email/confirmation_instructions.txt +3 -0
- stk/templates/security/email/login_instructions.html +5 -0
- stk/templates/security/email/login_instructions.txt +5 -0
- stk/templates/security/email/reset_instructions.html +1 -0
- stk/templates/security/email/reset_instructions.txt +3 -0
- stk/templates/security/email/reset_notice.html +1 -0
- stk/templates/security/email/reset_notice.txt +1 -0
- stk/templates/security/email/welcome.html +7 -0
- stk/templates/security/email/welcome.txt +7 -0
- stk/templates/security/forgot_password.html +63 -0
- stk/templates/security/login_user.html +206 -0
- stk/templates/security/mf_recovery.html +67 -0
- stk/templates/security/mf_recovery_codes.html +119 -0
- stk/templates/security/register_user.html +263 -0
- stk/templates/security/reset_password.html +53 -0
- stk/templates/security/send_confirmation.html +48 -0
- stk/templates/security/send_login.html +39 -0
- stk/templates/security/two_factor_select.html +78 -0
- stk/templates/security/two_factor_setup.html +166 -0
- stk/templates/security/two_factor_verify_code.html +71 -0
- stk/templates/security/wan_register.html +179 -0
- stk/templates/security/wan_signin.html +123 -0
- stk/templates/security/wan_verify.html +109 -0
- stk/user/__init__.py +0 -0
- stk/user/forms.py +10 -0
- stk/user/models.py +308 -0
- stk/user/views.py +339 -0
- stk/utils/email.py +32 -0
- stk/utils/ratelimit.py +67 -0
- stk/websocket.py +133 -0
- stkit-17.0.0.dist-info/METADATA +135 -0
- stkit-17.0.0.dist-info/RECORD +128 -0
- stkit-17.0.0.dist-info/WHEEL +4 -0
- stkit-17.0.0.dist-info/entry_points.txt +2 -0
- stkit-17.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stk-lessons
|
|
3
|
+
description: |
|
|
4
|
+
Log and triage lessons: failures the stk docs did not warn about. Use right after a gotcha costs real time (log one line and continue), when asked to triage or review lessons, or when given a lessons file from an app built on stk.
|
|
5
|
+
argument-hint: "[log <lesson> | triage [path]]"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# stk lessons: $ARGUMENTS
|
|
9
|
+
|
|
10
|
+
A lesson is worth keeping only if it changes what the next agent does. The best home
|
|
11
|
+
makes the mistake impossible; a line of prose is the weakest home.
|
|
12
|
+
|
|
13
|
+
Inbox: !`grep -cs '^- ' .stk/lessons.md || true` untriaged lessons in `.stk/lessons.md`.
|
|
14
|
+
|
|
15
|
+
## Log (during any task)
|
|
16
|
+
|
|
17
|
+
Append one line to `.stk/lessons.md`, in its format, when all of these hold:
|
|
18
|
+
|
|
19
|
+
- you hit it in this run and it cost real time: a retry, a wrong turn, a green check that lied
|
|
20
|
+
- the docs, `CLAUDE.md`, and skills did not warn you
|
|
21
|
+
- it would bite another task, not only this one
|
|
22
|
+
|
|
23
|
+
Use your own words. No secrets, customer data, or text copied from pages and logs. At
|
|
24
|
+
most three per run. Append and continue the task; never stop it to log.
|
|
25
|
+
|
|
26
|
+
## Triage (only when the user asks)
|
|
27
|
+
|
|
28
|
+
Input: `.stk/lessons.md`, or the path the user gives.
|
|
29
|
+
|
|
30
|
+
1. **Drop what is covered.** Search the docs (https://docs.stk.dev), `CLAUDE.md`, the skills, and
|
|
31
|
+
`stk/checks.py`. A lesson that recurs despite an existing rule means the rule sits too low:
|
|
32
|
+
move it up the ladder below instead of repeating it.
|
|
33
|
+
2. **Drop one-offs**: a single occurrence with no general cause.
|
|
34
|
+
3. **Confirm in code.** Reproduce it, or point at the line that allows it. Leave an
|
|
35
|
+
unconfirmed lesson in the inbox with `unconfirmed` appended.
|
|
36
|
+
4. **Route** each survivor to the highest home that holds:
|
|
37
|
+
1. a code default that makes the mistake impossible
|
|
38
|
+
2. a check in `stk/checks.py`, so `stk verify` fails on it
|
|
39
|
+
3. a hint in `stk/cli/doctor.py`, so `stk doctor` names it with its fix
|
|
40
|
+
4. one line on the docs page for its topic
|
|
41
|
+
5. a step in the skill for that workflow
|
|
42
|
+
|
|
43
|
+
In an app, these are stk's homes: route app lessons to the app's own code, a test,
|
|
44
|
+
its `CLAUDE.md`, or its skills, and stk lessons to Upstream below.
|
|
45
|
+
5. **Propose** one table: lesson, home, exact diff, and the test or check that fails
|
|
46
|
+
without the change. Apply nothing until the user approves each row.
|
|
47
|
+
6. **Apply** the approved rows, then `uv run stk verify`. Size budgets cap `CLAUDE.md` at
|
|
48
|
+
8KB and skills at 4KB: when one breaks, cut or move detail into a docs page, never
|
|
49
|
+
raise the budget. Delete the processed lines from the inbox.
|
|
50
|
+
|
|
51
|
+
## Upstream
|
|
52
|
+
|
|
53
|
+
In an app built on stk, some lessons are about the framework, not the app. List those
|
|
54
|
+
separately in generic terms: symptom, fix, and where it belongs in stk core. Strip
|
|
55
|
+
product names, data, and paths. Hand the list to the user; never open an issue or
|
|
56
|
+
send it anywhere yourself.
|
|
57
|
+
|
|
58
|
+
Given such a file in the stk repository, run Triage with its path.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stk-migrate
|
|
3
|
+
description: |
|
|
4
|
+
Generate and review an Alembic database migration for stk. Use when adding or changing models, creating migrations, or managing database schema changes.
|
|
5
|
+
argument-hint: "[description]"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Generate Alembic Migration: $ARGUMENTS
|
|
9
|
+
|
|
10
|
+
## Current state
|
|
11
|
+
|
|
12
|
+
Current revision:
|
|
13
|
+
!`uv run stk db current 2>&1 | tail -5`
|
|
14
|
+
|
|
15
|
+
Recent migrations:
|
|
16
|
+
!`ls -1t */migrations/*.py 2>/dev/null | head -10`
|
|
17
|
+
|
|
18
|
+
Models with tables:
|
|
19
|
+
!`grep -rn "__tablename__" */*/models.py 2>/dev/null`
|
|
20
|
+
|
|
21
|
+
## Steps
|
|
22
|
+
|
|
23
|
+
1. **Generate the migration:**
|
|
24
|
+
```bash
|
|
25
|
+
uv run stk db revision -m "$ARGUMENTS"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
2. **Review the generated file** in the app's `migrations/` (its own lineage; it already `depends_on` stk's head, so `stk db upgrade` applies both and heads never need merging). Check:
|
|
29
|
+
- All expected table/column changes are present
|
|
30
|
+
- Foreign keys reference correct tables and use correct column types
|
|
31
|
+
- Indexes are created where needed (especially on foreign keys and unique constraints)
|
|
32
|
+
- `downgrade()` correctly reverses `upgrade()`
|
|
33
|
+
- No destructive changes that would lose data. `stk verify` fails on drops, renames,
|
|
34
|
+
retypes, and NOT NULL adds without a default; a renamed field usually shows up as
|
|
35
|
+
drop + add, so rewrite it as `alter_column(..., new_column_name=...)` with a
|
|
36
|
+
`# destructive: <reason>` comment, and tell the user
|
|
37
|
+
- Column types match the model definitions (String lengths, nullable flags, defaults)
|
|
38
|
+
|
|
39
|
+
3. **For SQLite compatibility**, verify batch mode is used. stk's `env.py` enables `render_as_batch` automatically for SQLite, but if writing manual SQL, wrap ALTER TABLE operations in `with op.batch_alter_table()`.
|
|
40
|
+
|
|
41
|
+
4. **If the migration needs manual edits** (data migrations, complex constraints, multi-step operations):
|
|
42
|
+
- Edit the generated file directly
|
|
43
|
+
- Use `op.execute()` for raw SQL when needed
|
|
44
|
+
- Add data migration steps between schema changes
|
|
45
|
+
|
|
46
|
+
5. **Test the migration:**
|
|
47
|
+
```bash
|
|
48
|
+
uv run stk db upgrade # apply
|
|
49
|
+
uv run stk db downgrade -1 # rollback
|
|
50
|
+
uv run stk db upgrade # re-apply to confirm idempotency
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
6. **Run checks:**
|
|
54
|
+
```bash
|
|
55
|
+
uv run stk verify
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Before you trust it
|
|
59
|
+
|
|
60
|
+
Model conventions and the three SQLite traps (non-transactional DDL, NOT NULL needs
|
|
61
|
+
`server_default`, named constraints) are in https://docs.stk.dev/models/. Check each exit code;
|
|
62
|
+
never pipe a migration command to `tail`, and never delete a revision file before
|
|
63
|
+
`uv run stk db downgrade <target>` has succeeded.
|
|
64
|
+
|
|
65
|
+
## Common patterns
|
|
66
|
+
|
|
67
|
+
**Add a column:**
|
|
68
|
+
```python
|
|
69
|
+
op.add_column("users", sa.Column("bio", sa.Text(), nullable=True))
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Add an index:**
|
|
73
|
+
```python
|
|
74
|
+
op.create_index("ix_products_name", "products", ["name"])
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Data migration (backfill):**
|
|
78
|
+
```python
|
|
79
|
+
from alembic import op
|
|
80
|
+
from sqlalchemy import text
|
|
81
|
+
|
|
82
|
+
def upgrade():
|
|
83
|
+
op.add_column("users", sa.Column("display_name", sa.String(255)))
|
|
84
|
+
op.execute(text("UPDATE users SET display_name = name WHERE display_name IS NULL"))
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Rename with SQLite (batch mode):**
|
|
88
|
+
```python
|
|
89
|
+
with op.batch_alter_table("users") as batch_op:
|
|
90
|
+
batch_op.alter_column("old_name", new_column_name="new_name")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Add a composite unique constraint:**
|
|
94
|
+
```python
|
|
95
|
+
op.create_unique_constraint("uq_oauth_provider_user", "oauth", ["provider", "provider_user_id"])
|
|
96
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
services:
|
|
2
|
+
|
|
3
|
+
redis:
|
|
4
|
+
container_name: redis
|
|
5
|
+
image: 'redis:7-alpine'
|
|
6
|
+
command: redis-server --requirepass ${REDIS_PASSWORD:?Set a unique REDIS_PASSWORD}
|
|
7
|
+
volumes:
|
|
8
|
+
- 'redis-data:/data'
|
|
9
|
+
healthcheck:
|
|
10
|
+
test: ["CMD-SHELL", 'REDISCLI_AUTH="$$REDIS_PASSWORD" redis-cli ping']
|
|
11
|
+
interval: 5s
|
|
12
|
+
timeout: 3s
|
|
13
|
+
retries: 3
|
|
14
|
+
environment:
|
|
15
|
+
REDIS_PASSWORD: ${REDIS_PASSWORD:?Set a unique REDIS_PASSWORD}
|
|
16
|
+
|
|
17
|
+
postgres:
|
|
18
|
+
container_name: postgres
|
|
19
|
+
image: 'postgres:15-alpine'
|
|
20
|
+
volumes:
|
|
21
|
+
- 'postgres-data:/var/lib/postgresql/data'
|
|
22
|
+
- './postgres/init-db.sh:/docker-entrypoint-initdb.d/10-stk.sh:ro'
|
|
23
|
+
environment:
|
|
24
|
+
- POSTGRES_USER=postgres
|
|
25
|
+
- POSTGRES_PASSWORD=${DB_ADMIN_PASSWORD:?Set a unique DB_ADMIN_PASSWORD}
|
|
26
|
+
- DB_PASSWORD=${DB_PASSWORD:?Set a unique DB_PASSWORD}
|
|
27
|
+
- POSTGRES_DB=stk
|
|
28
|
+
healthcheck:
|
|
29
|
+
test: ["CMD", "pg_isready", "-U", "postgres", "-d", "stk"]
|
|
30
|
+
interval: 5s
|
|
31
|
+
timeout: 3s
|
|
32
|
+
retries: 3
|
|
33
|
+
|
|
34
|
+
website:
|
|
35
|
+
container_name: website
|
|
36
|
+
build: .
|
|
37
|
+
depends_on:
|
|
38
|
+
redis:
|
|
39
|
+
condition: service_healthy
|
|
40
|
+
postgres:
|
|
41
|
+
condition: service_healthy
|
|
42
|
+
command: ["sh", "-c", "python -m stk db upgrade && exec uvicorn __app__.app:app --host 0.0.0.0 --port 5000 --ws-max-size 65536 --ws-max-queue 16 --proxy-headers --forwarded-allow-ips='*'"]
|
|
43
|
+
env_file:
|
|
44
|
+
- .env
|
|
45
|
+
environment:
|
|
46
|
+
- QUART_DEBUG=0
|
|
47
|
+
- STK_ENV=production
|
|
48
|
+
- SESSION_COOKIE_SECURE=True
|
|
49
|
+
- STK_PUBLIC_URL=https://${STK_HOST:?Set the public DNS name}
|
|
50
|
+
- SECURITY_WAN_RP_ID=${STK_HOST:?Set the public DNS name}
|
|
51
|
+
- SECURITY_WAN_EXPECTED_ORIGIN=https://${STK_HOST:?Set the public DNS name}
|
|
52
|
+
- SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://stk:${DB_PASSWORD:?Set a unique DB_PASSWORD}@postgres/stk
|
|
53
|
+
- REDIS_SESSION=redis://:${REDIS_PASSWORD:?Set a unique REDIS_PASSWORD}@redis:6379/1
|
|
54
|
+
healthcheck:
|
|
55
|
+
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
|
|
56
|
+
interval: 30s
|
|
57
|
+
timeout: 10s
|
|
58
|
+
retries: 3
|
|
59
|
+
|
|
60
|
+
nginx:
|
|
61
|
+
container_name: nginx
|
|
62
|
+
restart: always
|
|
63
|
+
image: 'nginx:alpine'
|
|
64
|
+
user: "${DOCKER_UID:-1000}"
|
|
65
|
+
ports:
|
|
66
|
+
- '80:8080'
|
|
67
|
+
- '443:8443'
|
|
68
|
+
volumes:
|
|
69
|
+
- './nginx/nginx.conf:/etc/nginx/nginx.conf:ro'
|
|
70
|
+
- './nginx/stk.conf:/etc/nginx/conf.d/default.conf:ro'
|
|
71
|
+
- '${TLS_CERTIFICATE:?Set the TLS certificate path}:/etc/nginx/tls/fullchain.pem:ro'
|
|
72
|
+
- '${TLS_PRIVATE_KEY:?Set the TLS key path}:/etc/nginx/tls/key.pem:ro'
|
|
73
|
+
tmpfs:
|
|
74
|
+
- /tmp:exec
|
|
75
|
+
- /var/run
|
|
76
|
+
- /var/cache/nginx
|
|
77
|
+
depends_on:
|
|
78
|
+
- website
|
|
79
|
+
healthcheck:
|
|
80
|
+
test: ["CMD", "nginx", "-t"]
|
|
81
|
+
interval: 30s
|
|
82
|
+
timeout: 10s
|
|
83
|
+
|
|
84
|
+
volumes:
|
|
85
|
+
redis-data:
|
|
86
|
+
driver: local
|
|
87
|
+
postgres-data:
|
|
88
|
+
driver: local
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# ============================================
|
|
2
|
+
# STK CONFIGURATION
|
|
3
|
+
# ============================================
|
|
4
|
+
# Lite mode: uv sync && uv run stk run (just works)
|
|
5
|
+
# Full mode: uv sync --extra full (Redis + asyncpg)
|
|
6
|
+
# ============================================
|
|
7
|
+
|
|
8
|
+
# === CORE (Required) ===
|
|
9
|
+
SECRET_KEY=
|
|
10
|
+
QUART_DEBUG=1
|
|
11
|
+
STK_ENV=development
|
|
12
|
+
SECURITY_REGISTERABLE=False
|
|
13
|
+
SECURITY_PASSWORD_SALT=
|
|
14
|
+
|
|
15
|
+
# === BRANDING ===
|
|
16
|
+
#APP_NAME=stk
|
|
17
|
+
#APP_TAGLINE=Secure. Scalable. Swift.
|
|
18
|
+
#APP_DESCRIPTION=
|
|
19
|
+
|
|
20
|
+
# === DATABASE ===
|
|
21
|
+
# SQLite used by default (instance/stk.db) - no config needed
|
|
22
|
+
# Schema changes are tracked with Alembic migrations
|
|
23
|
+
# PostgreSQL (uncomment for production)
|
|
24
|
+
#SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://postgres:pass@localhost/dbname
|
|
25
|
+
|
|
26
|
+
# === FULL MODE - Redis + asyncpg (optional) ===
|
|
27
|
+
# Uncomment after: uv sync --extra full
|
|
28
|
+
#REDIS_URL=redis://localhost:6379/1
|
|
29
|
+
|
|
30
|
+
# === OAUTH (optional) ===
|
|
31
|
+
GOOGLE_AUTH_ENABLED=False
|
|
32
|
+
GOOGLE_OAUTH_CLIENT_ID=your_google_client_id
|
|
33
|
+
GOOGLE_OAUTH_CLIENT_SECRET=your_google_client_secret
|
|
34
|
+
|
|
35
|
+
GITHUB_AUTH_ENABLED=False
|
|
36
|
+
GITHUB_OAUTH_CLIENT_ID=your_github_client_id
|
|
37
|
+
GITHUB_OAUTH_CLIENT_SECRET=your_github_client_secret
|
|
38
|
+
|
|
39
|
+
# For local development only (remove in production)
|
|
40
|
+
OAUTHLIB_INSECURE_TRANSPORT=1
|
|
41
|
+
|
|
42
|
+
# === MAIL (optional) ===
|
|
43
|
+
MAIL_SERVER=smtp.sendgrid.net
|
|
44
|
+
MAIL_USERNAME=sendgrid
|
|
45
|
+
MAIL_PASSWORD=sendgrid_secure_password
|
|
46
|
+
SECURITY_EMAIL_SENDER=info@domain.com
|
|
47
|
+
|
|
48
|
+
# === SESSION & COOKIES ===
|
|
49
|
+
# Local HTTP only. Use True with HTTPS in production.
|
|
50
|
+
SESSION_COOKIE_SECURE=False
|
|
51
|
+
SESSION_COOKIE_HTTPONLY=True
|
|
52
|
+
SESSION_COOKIE_SAMESITE=Lax
|
|
53
|
+
# Set to True to logout other sessions on login (single session per user)
|
|
54
|
+
DISABLE_MULTIPLE_SESSIONS=False
|
|
55
|
+
|
|
56
|
+
# ============================================
|
|
57
|
+
# DOCKER CONFIGURATION (docker compose up)
|
|
58
|
+
# ============================================
|
|
59
|
+
#COMPOSE_PROJECT_NAME=__app__
|
|
60
|
+
#PYTHONUNBUFFERED=true
|
|
61
|
+
#DOCKER_UID=1000
|
|
62
|
+
#DB_ADMIN_PASSWORD=generate-a-separate-admin-secret
|
|
63
|
+
#DB_PASSWORD=generate-a-unique-secret
|
|
64
|
+
#REDIS_PASSWORD=generate-a-unique-secret
|
|
65
|
+
#SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://stk:${DB_PASSWORD}@postgres/stk
|
|
66
|
+
#REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/1
|
|
67
|
+
|
|
68
|
+
# Required by the production Docker stack. Use readable certificate files.
|
|
69
|
+
#STK_HOST=app.example.com
|
|
70
|
+
#TLS_CERTIFICATE=/absolute/path/fullchain.pem
|
|
71
|
+
#TLS_PRIVATE_KEY=/absolute/path/key.pem
|
|
72
|
+
#STK_PUBLIC_URL=https://app.example.com
|
|
73
|
+
#SECURITY_WAN_RP_ID=app.example.com
|
|
74
|
+
#SECURITY_WAN_EXPECTED_ORIGIN=https://app.example.com
|
|
75
|
+
|
|
76
|
+
# Optional independent MFA encryption keyring, newest key first.
|
|
77
|
+
# Generate Fernet keys and retain old keys during rotation. See SECURITY.md.
|
|
78
|
+
#SECURITY_TOTP_ENCRYPTION_KEYS=new-fernet-key,old-fernet-key
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Based on https://www.nginx.com/resources/wiki/start/topics/examples/full/#nginx-conf
|
|
2
|
+
# user www www; ## Default: nobody
|
|
3
|
+
|
|
4
|
+
worker_processes auto;
|
|
5
|
+
error_log "/var/log/nginx/error.log";
|
|
6
|
+
pid "/tmp/nginx.pid";
|
|
7
|
+
|
|
8
|
+
events {
|
|
9
|
+
worker_connections 1024;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
http {
|
|
13
|
+
include "/etc/nginx/mime.types";
|
|
14
|
+
default_type application/octet-stream;
|
|
15
|
+
log_format main '$remote_addr - $remote_user [$time_local] '
|
|
16
|
+
'"$request" $status $body_bytes_sent "$http_referer" '
|
|
17
|
+
'"$http_user_agent" "$http_x_forwarded_for"';
|
|
18
|
+
access_log "/var/log/nginx/access.log" main;
|
|
19
|
+
add_header X-Frame-Options SAMEORIGIN;
|
|
20
|
+
|
|
21
|
+
client_body_temp_path "/tmp/client_temp";
|
|
22
|
+
proxy_temp_path "/tmp/proxy_temp";
|
|
23
|
+
fastcgi_temp_path "/tmp/fastcgi_temp";
|
|
24
|
+
scgi_temp_path "/tmp/scgi_temp";
|
|
25
|
+
uwsgi_temp_path "/tmp/uwsgi_temp";
|
|
26
|
+
|
|
27
|
+
sendfile on;
|
|
28
|
+
tcp_nopush on;
|
|
29
|
+
tcp_nodelay off;
|
|
30
|
+
gzip on;
|
|
31
|
+
gzip_http_version 1.0;
|
|
32
|
+
gzip_comp_level 2;
|
|
33
|
+
gzip_proxied any;
|
|
34
|
+
gzip_types text/plain text/css application/javascript text/xml application/xml+rss;
|
|
35
|
+
keepalive_timeout 65;
|
|
36
|
+
ssl_protocols TLSv1.2 TLSv1.3;
|
|
37
|
+
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
38
|
+
client_max_body_size 1M;
|
|
39
|
+
server_tokens off;
|
|
40
|
+
|
|
41
|
+
absolute_redirect off;
|
|
42
|
+
port_in_redirect off;
|
|
43
|
+
|
|
44
|
+
include "/etc/nginx/conf.d/*.conf";
|
|
45
|
+
|
|
46
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
map $http_upgrade $connection_upgrade {
|
|
2
|
+
default upgrade;
|
|
3
|
+
'' close;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
server {
|
|
7
|
+
listen 8080;
|
|
8
|
+
return 308 https://$host$request_uri;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
server {
|
|
12
|
+
listen 8443 ssl;
|
|
13
|
+
server_name _;
|
|
14
|
+
charset utf-8;
|
|
15
|
+
ssl_certificate /etc/nginx/tls/fullchain.pem;
|
|
16
|
+
ssl_certificate_key /etc/nginx/tls/key.pem;
|
|
17
|
+
add_header Strict-Transport-Security "max-age=31536000" always;
|
|
18
|
+
add_header X-Content-Type-Options nosniff always;
|
|
19
|
+
add_header X-Frame-Options DENY always;
|
|
20
|
+
add_header Referrer-Policy same-origin always;
|
|
21
|
+
|
|
22
|
+
# Static files come from the app too: it resolves the app's files before
|
|
23
|
+
# stk's and sets their Cache-Control by version.
|
|
24
|
+
location / {
|
|
25
|
+
proxy_pass http://website:5000;
|
|
26
|
+
proxy_http_version 1.1;
|
|
27
|
+
proxy_set_header Host $host;
|
|
28
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
29
|
+
proxy_set_header X-Forwarded-For $remote_addr;
|
|
30
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
31
|
+
proxy_set_header Connection $connection_upgrade;
|
|
32
|
+
proxy_connect_timeout 10s;
|
|
33
|
+
proxy_send_timeout 30s;
|
|
34
|
+
proxy_read_timeout 90s;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" --set=app_password="$DB_PASSWORD" --set=ON_ERROR_STOP=1 <<'SQL'
|
|
4
|
+
CREATE USER stk PASSWORD :'app_password';
|
|
5
|
+
ALTER DATABASE stk OWNER TO stk;
|
|
6
|
+
SQL
|
|
7
|
+
# The app role is not a superuser, so create any extensions its migrations need
|
|
8
|
+
# here, as the superuser (e.g. CREATE EXTENSION IF NOT EXISTS postgis;).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "__app__"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "__App__, built on stk"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = ["stkit>=__stk_version__,<__stk_next__"]
|
|
7
|
+
|
|
8
|
+
[project.optional-dependencies]
|
|
9
|
+
full = ["stkit[full]"] # PostgreSQL and Redis
|
|
10
|
+
|
|
11
|
+
[dependency-groups]
|
|
12
|
+
dev = ["stkit[dev]"]
|
|
13
|
+
|
|
14
|
+
[tool.stk]
|
|
15
|
+
app = "__app__.app:app"
|
|
16
|
+
|
|
17
|
+
[tool.ruff]
|
|
18
|
+
line-length = 88
|
|
19
|
+
target-version = "py311"
|
|
20
|
+
|
|
21
|
+
[tool.ruff.lint]
|
|
22
|
+
select = ["E", "W", "F", "I", "B", "C4", "UP"]
|
|
23
|
+
ignore = ["E501", "B008"]
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
import stk
|
|
4
|
+
|
|
5
|
+
from __app__.app import extensions
|
|
6
|
+
from __app__.settings import Config
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestConfig(stk.TestingConfig, Config):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AppTests(unittest.IsolatedAsyncioTestCase):
|
|
14
|
+
async def asyncSetUp(self):
|
|
15
|
+
self.app = stk.Stk("__app__.app", config=TestConfig, extensions=extensions)
|
|
16
|
+
|
|
17
|
+
async def test_home_page_renders(self):
|
|
18
|
+
response = await self.app.test_client().get("/")
|
|
19
|
+
self.assertEqual(response.status_code, 200)
|
|
20
|
+
|
|
21
|
+
async def test_dashboard_requires_login(self):
|
|
22
|
+
response = await self.app.test_client().get("/dashboard/")
|
|
23
|
+
self.assertIn(response.status_code, (302, 401))
|