Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3c5d434
fix: serialize postgres migrations with advisory lock, rename BACKEND…
mikoding9 May 24, 2026
ff19930
chore: bump dependencies (setuptools, authlib, @tanstack/svelte-table)
mikoding9 May 24, 2026
7e0cfd7
fix: local run and sync handling
mikoding9 May 24, 2026
50c06eb
chore: add desktop distribution plan and architecture
mikoding9 May 24, 2026
fb710b3
feat: implement Wails desktop shell (Phase 1)
mikoding9 May 24, 2026
603be84
feat: demo page with password auth, multi-provider login
mikoding9 May 24, 2026
2feb29e
feat: per-route rate limiting via slowapi
mikoding9 May 24, 2026
aea6c81
feat: implement process management (Phase 2)
mikoding9 May 24, 2026
00a3368
feat: implement uv setup and snakedispatch config (Phases 3-4)
mikoding9 May 24, 2026
9c5dfa7
feat: centralize API error toasts and dev workflow improvements
mikoding9 May 24, 2026
01ab23d
ci: replace SSH deploy with version poll
mikoding9 May 24, 2026
5ab9f04
fix: desktop shell and auth compatibility fixes
mikoding9 May 24, 2026
462616f
feat: add NSIS installer for Windows distribution (Phase 5)
mikoding9 May 24, 2026
9da6041
feat: add About menu, crash toasts, and code signing (Phase 6)
mikoding9 May 24, 2026
9b0c155
feat: add macOS bundled-wheel distribution
mikoding9 May 24, 2026
1226527
docs: add desktop build and distribution guides
mikoding9 May 24, 2026
18e2289
docs: update Windows build docs and gitignore for macOS cross-compila…
mikoding9 May 25, 2026
b820b9e
fix: clear setup sentinel on install so updated wheels are applied
mikoding9 May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 24 additions & 11 deletions .github/workflows/release-docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ jobs:
provenance: mode=max
sbom: true

deploy-to-dev:
name: Deploy to dev
wait-for-deploy:
name: Wait for dev deploy
runs-on: ubuntu-latest
needs: push-image
if: github.ref == 'refs/heads/main'
Expand All @@ -74,12 +74,25 @@ jobs:
url: ${{ vars.APP_URL }}

steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT || 22 }}
fingerprint: ${{ secrets.SSH_HOST_FINGERPRINT }}
script: deploy dev
- name: Poll /api/v1/version for new short SHA
env:
TARGET_SHA: ${{ github.sha }}
APP_URL: ${{ vars.APP_URL }}
CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID_DEPLOY }}
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET_DEPLOY }}
run: |
set -eu
SHORT="${TARGET_SHA:0:7}"
for i in $(seq 1 80); do
ver=$(curl -sS \
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
"$APP_URL/api/v1/version/" | jq -r '.version // empty' 2>/dev/null || true)
if [[ "$ver" == *"g${SHORT}"* ]]; then
echo "Deployed: $ver"
exit 0
fi
echo "want g${SHORT}, got ${ver:-<none>}"
sleep 15
done
exit 1
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ todo.md
# Data
/data/

# Demo seeding
/demo/

# setuptools-scm version
src/pypsa_app/backend/_version_info.py

Expand Down Expand Up @@ -36,6 +39,11 @@ sdist/
var/
wheels/
*.egg-info/
# Wails desktop build — source assets are tracked; compiled outputs and auto-generated files are not.
# The `build/` and `wheels/` patterns above would catch these, so negate them explicitly.
!desktop/build/
desktop/build/windows/installer/wails_tools.nsh
!desktop/build/windows/wheels/
.installed.cfg
*.egg

Expand Down Expand Up @@ -80,8 +88,10 @@ htmlcov/

# Docker
compose/compose.prod.yaml
compose/compose.demo.yaml
compose/.env
compose/.env.dev
compose/.env.demo

# Alembic
alembic/versions/*.pyc
Expand Down
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ COPY pyproject.toml uv.lock MANIFEST.in ./
COPY src/ src/
COPY .git/ .git/

# Sync dependencies with uv
RUN uv sync --frozen --extra full --no-dev
# Skip setuptools_scm git lookup when .git is unavailable
ARG SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYPSA_APP=
RUN if [ -n "${SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYPSA_APP}" ]; then \
export SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYPSA_APP="${SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYPSA_APP}"; \
fi && uv sync --frozen --extra full --no-dev

# Stage 2: Runtime stage (pypsa-app backend)
FROM python:3.13-slim@sha256:d49c1ff87eb98eac346fc250f52925f726eb913c43a92854246dd03c9692ad67 AS backend
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ and custom integrations.
> [!TIP]
> There is a prototype deployed under [`https://app-dev.pypsa.org/`](https://app-dev.pypsa.org/). If you would like to have access, reach out to [@lkstrp](https://github.com/lkstrp/).

> [!TIP]
> A public read-only demo runs at [`https://demo.pypsa.org/`](https://demo.pypsa.org/), where uploads and runs are disabled and data is synthetic.

### Roadmap

- [ ] Upload Networks via interface
Expand Down
701 changes: 701 additions & 0 deletions architecture.excalidraw

Large diffs are not rendered by default.

90 changes: 72 additions & 18 deletions compose/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,56 @@
# Publicly accessible URL of the application
BASE_URL=http://localhost:5173

# Single-user local-dashboard deployment (the bare `pypsa-app` CLI). Enables zero-copy in-place network registration. Incompatible with any authentication.
LOCAL_MODE=false

# Public read-only demo deployment. Disables all write endpoints, uses a shared 'demo' user.
DEMO_MODE=false

# File storage directory to store application data and network files
DATA_DIR=./data
DATA_DIR=PydanticUndefined

# Database
# --------

# Database URL (SQLite and PostgreSQL is supported)
DATABASE_URL=sqlite:///./data/pypsa-app.db
# Database URL (SQLite and PostgreSQL are supported). Defaults to a SQLite file inside data_dir.
DATABASE_URL=__derive_from_data_dir__

# Authentication
# --------------

# Enable GitHub OAuth authentication
ENABLE_AUTH=false

# GitHub OAuth app client ID (create at https://github.com/settings/developers)
# GITHUB_CLIENT_ID=
# AUTH_GITHUB_CLIENT_ID=

# GitHub OAuth app client secret
# GITHUB_CLIENT_SECRET=
# AUTH_GITHUB_CLIENT_SECRET=

# Enable password based login
AUTH_PASSWORD_ENABLED=false

# Secret key for session cookies (generate with: openssl rand -base64 32)
# SESSION_SECRET_KEY=dev-secret-key-change-in-production
SESSION_SECRET_KEY=dev-secret-key-change-in-production

# Session time-to-live in seconds (default: 7 days)
# SESSION_TTL=604800
SESSION_TTL=604800

# Networks
# --------

# Maximum network file upload size in megabytes
MAX_UPLOAD_SIZE_MB=2000

# GitHub username that becomes admin on first login
# ADMIN_GITHUB_USERNAME=
# Runs
# ----

# Interval in seconds between background Snakedispatch sync cycles
SNAKEDISPATCH_SYNC_INTERVAL=10.0

# Comma-separated list of allowed domains for run callback URLs (e.g. hooks.myorg.dev,example.com). Callbacks are rejected unless the host matches. Empty disables callbacks entirely.
CALLBACK_URL_ALLOWED_DOMAINS=

# Comma-separated list of Snakedispatch backends in name=url format (e.g. cluster-a=http://sd-a:8000,cluster-b=http://sd-b:8000)
# SNAKEDISPATCH_BACKENDS=

# Redis
# -----
Expand All @@ -48,17 +69,50 @@ ENABLE_AUTH=false
# Time-to-live in seconds for network cache entries
# NETWORK_CACHE_TTL=7200

# Time-to-live in seconds for run output file list cache entries
# RUN_OUTPUTS_CACHE_TTL=10800

# Maximum cache size in megabytes
# MAX_CACHE_SIZE_MB=50

# Executions
# ----------
# Rate limiting
# -------------

# Comma-separated list of Snakedispatch backends in name=url format
# SNAKEDISPATCH_BACKENDS=cluster-a=http://snakedispatch-a:8000,cluster-b=http://snakedispatch-b:8000
# Enable per route rate limiting. Auto on when LOCAL_MODE is off.
# RATELIMIT_ENABLED=

# Time-to-live in seconds for run output file list cache entries
# RUN_OUTPUTS_CACHE_TTL=10800
# Default per key rate limit applied to all routes
RATELIMIT_DEFAULT=120/minute

# Rate limit for POST /auth/login/password
RATELIMIT_LOGIN=5/minute;20/hour

# Rate limit for task queueing routes (plots, statistics).
RATELIMIT_EXPENSIVE=60/minute;600/hour

# Trust the CF-Connecting-IP header as the client IP for rate limiting. Only enable when the app sits behind a Cloudflare tunnel.
TRUST_CLOUDFLARE_IP=false

# Email
# -----

# SMTP server hostname (enables email notifications when set)
# SMTP_HOST=

# SMTP server port
# SMTP_PORT=587

# SMTP authentication username
# SMTP_USERNAME=

# SMTP authentication password
# SMTP_PASSWORD=

# Use TLS/STARTTLS for SMTP connection
# SMTP_USE_TLS=true

# Sender email address for notifications
# SMTP_FROM_ADDRESS=noreply@pypsa-app.local

# Development
# -----------
Expand Down
96 changes: 96 additions & 0 deletions compose/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Development setup - backend only (run frontend with npm run dev)
# Copy .env.dev.example to .env.dev and configure your settings

services:
postgres:
image: postgres:18-alpine
container_name: pypsa-postgres

environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
# Uncomment for access Postgres on host machine
# ports:
# - "${POSTGRES_PORT:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 5s
timeout: 5s
retries: 5

redis:
image: redis:8-alpine
container_name: pypsa-redis

# Uncomment to access Redis from host machine
# ports:
# - "6379:6379"
command: redis-server --maxmemory ${REDIS_MAXMEMORY:-2gb} --maxmemory-policy allkeys-lru
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5

pypsa-app:
build:
context: ..
dockerfile: Dockerfile
target: backend
container_name: pypsa-app
ports:
- "${APP_PORT:-8000}:8000"

environment:
# These use docker network hostnames, so we construct them here
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
REDIS_URL: redis://redis:6379/0
DATA_DIR: /data
DEBUG: true
volumes:
- ../src:/app/src
- ../data:/data
- ../alembic.ini:/app/alembic.ini:ro
- ../frontend/app/package.json:/app/frontend/app/package.json:ro
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
command: ["pypsa-app", "serve", "--reload", "--dev"]

celery-worker:
build:
context: ..
dockerfile: Dockerfile
target: backend
container_name: pypsa-celery-worker

environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
REDIS_URL: redis://redis:6379/0
DATA_DIR: /data
command:
[
"celery",
"-A",
"pypsa_app.backend.task_queue.task_app",
"worker",
"--loglevel=${CELERY_LOGLEVEL:-info}",
"--concurrency=${CELERY_CONCURRENCY:-2}",
]
volumes:
- ../src:/app/src
- ../data:/data
- ../alembic.ini:/app/alembic.ini:ro
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy

volumes:
postgres_data:
6 changes: 6 additions & 0 deletions desktop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
build/bin
node_modules
frontend/dist
# local go build outputs
pypsa-desktop
pypsa-desktop.exe
16 changes: 16 additions & 0 deletions desktop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# README

## About

This is the official Wails Svelte template.

## Live Development

To run in live development mode, run `wails dev` in the project directory. This will run a Vite development
server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser
and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect
to this in your browser, and you can call your Go code from devtools.

## Building

To build a redistributable, production mode package, use `wails build`.
Loading