Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9ef66a1
deps: upgrade frontend dependencies to latest versions
manuelgruber Mar 23, 2026
d87bdf5
feat: add TaskPreset database model, service, and built-in seed data
manuelgruber Mar 23, 2026
aeeb7ec
feat: add task preset REST API endpoints
manuelgruber Mar 23, 2026
eeb15e9
feat: add task preset UI and improve responsive layout
manuelgruber Mar 23, 2026
61f403f
Add security remediation plan documenting findings and phased fix app…
manuelgruber Apr 25, 2026
5d11973
Add UX improvement roadmap to docs/plans
manuelgruber Apr 25, 2026
aa1083c
Update all dependencies to latest across backend, frontend, and CI
manuelgruber Jul 7, 2026
d4f913b
Read app version from __version__ in API endpoints to prevent drift
manuelgruber Jul 9, 2026
dd28a79
Stabilize app: remove dead/fictional code, fix config/resilience/conc…
manuelgruber Jul 14, 2026
1b41990
Make authentication optional to support trusted LAN deployments
manuelgruber Jul 16, 2026
85ecc7f
Add shared Conductor repository scripts and defaults.
manuelgruber Jul 16, 2026
e01c14f
Add reconnect regression tests to protect scheduled task recovery
manuelgruber Jul 16, 2026
6ddf909
Update dependencies to keep PowerNight current and supported
manuelgruber Oct 3, 2026
f3b8122
Fix authentication boundaries to keep Tesla setup accessible
manuelgruber Oct 3, 2026
6800495
Fix Tesla cloud recovery to restore reliable task execution
manuelgruber Oct 3, 2026
715d6a7
Separate runtime overrides to keep secrets out of saved settings
manuelgruber Oct 19, 2026
ecc8175
Restore TypeScript checks to catch frontend errors in CI
manuelgruber Oct 19, 2026
8e28340
Handle preset name conflicts to avoid SQL leaks and server errors
manuelgruber Oct 19, 2026
576f7ec
Decouple cached connection status to keep health checks responsive
manuelgruber Oct 19, 2026
06c027d
Serialize Powerwall operations to prevent reconnect command failures
manuelgruber Oct 19, 2026
4aab589
Stage versioned releases to keep latest behind canary validation
manuelgruber Oct 19, 2026
1a6b821
Add Compose smoke checks to verify upgrades and persistence
manuelgruber Oct 19, 2026
51ef6d1
Document 2.0.0 rollout to make release and rollback repeatable
manuelgruber Oct 19, 2026
824fd93
Fix Powerwall reads to prevent deadlocks and stale cache renewal
manuelgruber Oct 19, 2026
1d07d25
Protect persisted credentials to prevent accidental exposure
manuelgruber Oct 19, 2026
560859b
Validate API requests to keep errors predictable and private
manuelgruber Oct 19, 2026
c628186
Fix dashboard polling and dialogs to keep the UI responsive
manuelgruber Oct 19, 2026
00876ad
Harden release tooling to validate versioned 2.0.0 rollouts
manuelgruber Oct 19, 2026
5e1ef8c
Protect configuration recovery to keep runtime credentials private
manuelgruber Oct 19, 2026
2d2ca28
Preserve partial status and polling to keep the dashboard responsive
manuelgruber Oct 19, 2026
627bf43
Validate preset times and metadata to keep API responses reliable
manuelgruber Oct 19, 2026
39d326f
Align release tooling and docs to keep validation reproducible
manuelgruber Oct 19, 2026
c9cf497
Install build dependencies in CI to keep release builds working
manuelgruber Oct 19, 2026
cc61a74
Format command errors with wire values to keep messages readable
manuelgruber Oct 19, 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
41 changes: 41 additions & 0 deletions .conductor/settings.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"$schema" = "https://conductor.build/schemas/settings.repo.schema.json"

# PowerNight serves the built React UI from Flask. Preview opens the active
# workspace's web interface (health: /health).
[[preview_urls]]
name = "PowerNight"
url = "http://localhost:$CONDUCTOR_PORT"

[scripts]
# Matches CI: editable Python install + locked npm deps, then frontend build.
# Creates workspace-local data/logs and seeds .env from the example when missing.
setup = '''pip install -e ".[dev]" && npm ci && mkdir -p data logs && (test -f .env || cp .env.example .env) && ./build.sh --no-docker --skip-validation'''

# Dev servers are workspace-local; SQLite lives under ./data with no Docker services.
archive = "echo 'PowerNight uses workspace-local SQLite under ./data; no external resources to clean up.'"

# Backend (Flask) and Vite dev server can run side by side in different workspaces.
run_mode = "concurrent"

# Full app: Flask API + built React assets. POWERNIGHT_WEB_PORT maps to CONDUCTOR_PORT.
[scripts.run.app]
command = "POWERNIGHT_DATA_PATH=$PWD/data POWERNIGHT_WEB_PORT=$CONDUCTOR_PORT python -m powernight.main"
available_in = ["local"]
default = true
icon = "zap"

# Frontend hot-reload uses the next allocated port and proxies to this workspace API.
[scripts.run.ui]
command = "POWERNIGHT_API_PROXY_TARGET=http://127.0.0.1:$CONDUCTOR_PORT npm run dev -- --port $((CONDUCTOR_PORT + 1)) --strictPort"
icon = "layout"
available_in = ["local"]

# Quick backend verification (same pytest scope as CI).
[scripts.run.test-backend]
command = 'pytest -q -m "not slow"'
icon = "test-tube"

# Frontend unit tests (vitest, one-shot).
[scripts.run.test-frontend]
command = "npm run test:run"
icon = "braces"
4 changes: 3 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
.env*
.vscode/
.idea/
.context/
.venv/
*.pyc
__pycache__/
*.pyo
Expand Down Expand Up @@ -81,4 +83,4 @@ yarn-error.log*
# Python virtual environments and caches
.mypy_cache/
.dmypy.json
dmypy.json
dmypy.json
22 changes: 22 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# PowerNight environment configuration
# Copy to .env and fill in real values; docker-compose reads this file.

# OPTIONAL: API key protecting the web interface and API. Leave unset for a
# trusted-LAN deployment. Any nonempty key automatically enables authentication.
# Generate a strong key with: openssl rand -hex 32
# POWERNIGHT_API_KEY=

# Optional explicit switch. Setting this to true without a key (or complete
# username/password configuration) makes startup fail closed.
# POWERNIGHT_AUTH_ENABLED=true

# Tesla account email used for the Powerwall cloud connection
TESLA_EMAIL=

# Optional overrides
# LOG_LEVEL=INFO
# AUTOMATION_ENABLED=true
# Vite-only development proxy (export in the shell; .env is for Compose):
# export POWERNIGHT_API_PROXY_TARGET=http://127.0.0.1:8020
# Flask generates and persists /data/.flask_secret automatically.
# To override it, explicitly pass FLASK_SECRET_KEY in a custom Compose override.
143 changes: 42 additions & 101 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ This directory contains automated workflows for PowerNight's CI/CD pipeline.
9. Update Docker Hub repository description from docs/README.md
10. Generate build summary

**Docker Tags Created**:
**Versioned Docker Tags Created** (latest is promoted separately):

*GitHub Container Registry (Primary):*
- `ghcr.io/zaai-com/powernight:<version>` (e.g., `1.0.0`)
- `ghcr.io/zaai-com/powernight:latest`
- `ghcr.io/zaai-com/powernight:latest` (only after Docker Promote)

*Docker Hub (Mirror):*
- `zaaicom/powernight:<version>` (e.g., `1.0.0`)
- `zaaicom/powernight:latest`
- `zaaicom/powernight:latest` (only after Docker Promote)

**Required Secrets** (Organization-level):
- `DOCKERHUB_USERNAME` - Docker Hub username (zaaicom)
Expand Down Expand Up @@ -71,12 +71,12 @@ docker pull zaaicom/powernight:1.0.0
1. Checkout repository
2. Build frontend
3. Build Docker image (amd64 only for speed)
4. Start container with test configuration
5. Wait for container health check to pass
6. Test health endpoint (`/health`)
7. Test web interface accessibility
8. Display container logs
9. Generate test summary
4. Run `scripts/release_smoke.py` with isolated Docker Compose projects
5. Verify open and protected API access, SPA routes, and bundled assets
6. Migrate synthetic legacy data and verify preset conflicts return HTTP 409
7. Confirm saved configuration excludes environment-provided API keys
8. Recreate containers and verify database, auth artifacts, and Flask secret persist
9. Save container logs and generate a JSON test summary; remove disposable volumes

**Why This Matters**:
- Catches Dockerfile issues early
Expand Down Expand Up @@ -172,90 +172,30 @@ trivy image zaaicom/powernight:1.0.0

## Release Process

### Creating a New Release

1. **Update Version**
```bash
# Edit pyproject.toml
version = "0.4.0"
```

2. **Commit Changes**
```bash
git add pyproject.toml
git commit -m "Bump version to 0.4.0"
git push origin main
```

3. **Create and Push Tag**
```bash
# Create annotated tag
git tag -a 0.4.0 -m "Release 0.4.0 - Description of changes"

# Push tag to trigger workflow
git push origin 0.4.0
```

4. **Monitor Build**
- Go to [GitHub Actions](https://github.com/ZAAI-com/PowerNight/actions)
- Watch "Docker Publish" workflow
- Typical build time: 15-20 minutes

5. **Verify on Registries**

**GitHub Container Registry (Primary):**
- Visit [GitHub Packages](https://github.com/ZAAI-com/PowerNight/pkgs/container/powernight)
- Verify new tags appear:
- `ghcr.io/zaai-com/powernight:0.4.0`
- `ghcr.io/zaai-com/powernight:latest`

**Docker Hub (Mirror):**
- Visit [Docker Hub Repository](https://hub.docker.com/r/zaaicom/powernight)
- Verify new tags appear:
- `zaaicom/powernight:0.4.0`
- `zaaicom/powernight:latest`

- Check multi-arch manifests on both registries

6. **Test Pull**
```bash
# Pull from GHCR (recommended)
docker pull ghcr.io/zaai-com/powernight:0.4.0
docker run -d -p 8020:8020 ghcr.io/zaai-com/powernight:0.4.0

# Or pull from Docker Hub (mirror)
docker pull zaaicom/powernight:0.4.0
docker run -d -p 8020:8020 zaaicom/powernight:0.4.0
```

### Hotfix Release

For urgent fixes:

1. Create hotfix branch
```bash
git checkout -b hotfix/0.3.1
```

2. Make fixes and test
```bash
# Make changes
git commit -m "Fix critical issue"
```

3. Update version to patch release
```bash
# pyproject.toml
version = "0.3.1"
```

4. Merge and tag
```bash
git checkout main
git merge hotfix/0.3.1
git tag -a 0.3.1 -m "Hotfix: Critical bug fix"
git push origin main 0.3.1
```
Releases are staged: Docker Publish writes version-only tags, and Docker Promote
updates `latest` separately after a verified live canary. Local-only validation
must not dispatch either publishing or promotion.

1. Complete CI and Docker Compose persistence/migration tests for the release commit.
`python scripts/release_smoke.py --image powernight-release:test` runs the same
isolated smoke checks used by Docker Health Test, with no Tesla access.
2. Verify all version identifiers using `python scripts/check_release_version.py 2.0.0`.
3. Merge to `main` and tag the verified commit as `2.0.0` (no `v` prefix).
Pushing the tag triggers Docker Publish. Manual publication must also select
the release tag as its ref. Only commits contained in `main` are publishable.
4. Record the image digest from the publish summary. Verify both registries have
that version and the required amd64, arm64, and arm/v7 manifests.
5. Back up canary data, pin the version/digest in Docker Compose, and validate
upgrade, restart, reconnect, and one overnight scheduling cycle.
6. Run Docker Promote from `main` with the version, exact digest, and
`canary_verified=true`. It verifies both registry tags before changing either
`latest` and promotes the tested digest without rebuilding.
7. Publish the GitHub release notes and monitor the rollout. Registry promotion
is not atomic; on a partial failure, inspect both registries and finish using
the same verified digest.

See [2.0.0 release notes and rollback runbook](../../docs/releases/2.0.0.md).
Database rollback requires the pre-upgrade data backup as well as the old image.

---

Expand Down Expand Up @@ -425,8 +365,9 @@ To add a new architecture (e.g., `linux/riscv64`):

1. **Always test locally first**
```bash
./build.sh
docker run -d -p 8020:8020 powernight:latest
./build.sh --no-docker
docker build -t powernight-release:test .
python scripts/release_smoke.py --image powernight-release:test
```

2. **Use semantic versioning**
Expand All @@ -452,15 +393,15 @@ To add a new architecture (e.g., `linux/riscv64`):
- Watch Actions tab during release
- Don't assume success - verify!

5. **Test published images**
5. **Test staged images before promoting latest**
```bash
# Test GHCR image (recommended)
docker pull ghcr.io/zaai-com/powernight:latest
docker run --rm ghcr.io/zaai-com/powernight:latest powernight --version
# After version publication, test in isolated Compose projects (no Tesla access)
docker pull ghcr.io/zaai-com/powernight:2.0.0
python scripts/release_smoke.py --image ghcr.io/zaai-com/powernight:2.0.0

# Or test Docker Hub image (mirror)
docker pull zaaicom/powernight:latest
docker run --rm zaaicom/powernight:latest powernight --version
docker pull zaaicom/powernight:2.0.0
python scripts/release_smoke.py --image zaaicom/powernight:2.0.0
```

---
Expand Down
80 changes: 80 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: CI
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
backend:
name: Backend (pytest + ruff)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
cache: pip

- name: Install dependencies
run: pip install -e ".[dev]"

- name: Ruff (syntax errors and undefined names)
run: ruff check src/ tests/ --select E9,F63,F7,F82

- name: Run tests
run: pytest -q -m "not slow" --cov=src/powernight --cov-report=term

frontend:
name: Frontend (lint + type-check + vitest + build)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '26'
cache: npm

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Type check
run: npm run type-check

- name: Unit tests
run: npm run test:run

- name: Build
run: npm run build

mypy:
name: Type check backend (informational)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
cache: pip

- name: Install dependencies
run: pip install -e ".[dev]"

- name: mypy
run: mypy src/
Loading
Loading