Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edf6294315 | ||
|
|
b4dd074894 | ||
|
|
e81f6643fc | ||
|
|
afe51c4abd | ||
|
|
a101e73c0e | ||
|
|
1a5c6aca08 | ||
|
|
00f76f33fd | ||
|
|
0a92b8f57a |
@@ -1950,10 +1950,13 @@ def _verify_support_removed() -> bool:
|
||||
|
||||
@app.get("/login", response_class=HTMLResponse)
|
||||
async def login_page(request: Request):
|
||||
return templates.TemplateResponse("login.html", {
|
||||
"request": request,
|
||||
"asset_version": ASSET_VERSION,
|
||||
})
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="login.html",
|
||||
context={
|
||||
"asset_version": ASSET_VERSION,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/auto-login")
|
||||
@@ -2018,20 +2021,26 @@ async def api_logout(request: Request):
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request):
|
||||
return templates.TemplateResponse("index.html", {
|
||||
"request": request,
|
||||
"asset_version": ASSET_VERSION,
|
||||
})
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="index.html",
|
||||
context={
|
||||
"asset_version": ASSET_VERSION,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/onboarding", response_class=HTMLResponse)
|
||||
async def onboarding(request: Request):
|
||||
_ensure_onboarding_reopened_for_migration()
|
||||
return templates.TemplateResponse("onboarding.html", {
|
||||
"request": request,
|
||||
"asset_version": ASSET_VERSION,
|
||||
"onboarding_js_hash": _ONBOARDING_JS_HASH,
|
||||
})
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="onboarding.html",
|
||||
context={
|
||||
"asset_version": ASSET_VERSION,
|
||||
"onboarding_js_hash": _ONBOARDING_JS_HASH,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/onboarding/status")
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
"""Regression test for Starlette 1.1.0+ TemplateResponse keyword-argument style.
|
||||
|
||||
Prior to this fix, the three HTML routes called:
|
||||
templates.TemplateResponse("name.html", {"request": request, ...})
|
||||
which passes the context dict as the second positional argument. With the
|
||||
updated Starlette/FastAPI versions shipped in NixOS unstable (Starlette 1.1.0,
|
||||
FastAPI 0.136.3) that positional argument is the template name, causing Jinja2
|
||||
to receive a dict as a cache key and raise:
|
||||
TypeError: unhashable type: 'dict'
|
||||
|
||||
The fix updates every call to use keyword arguments:
|
||||
templates.TemplateResponse(request=request, name="name.html", context={...})
|
||||
"""
|
||||
|
||||
import ast
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SERVER_PY = Path(__file__).resolve().parents[1] / "sovran_systemsos_web" / "server.py"
|
||||
|
||||
|
||||
def _template_response_calls(source: str):
|
||||
"""Return a list of ast.Call nodes that are TemplateResponse calls."""
|
||||
tree = ast.parse(source)
|
||||
calls = []
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, ast.Call):
|
||||
continue
|
||||
func = node.func
|
||||
if isinstance(func, ast.Attribute) and func.attr == "TemplateResponse":
|
||||
calls.append(node)
|
||||
return calls
|
||||
|
||||
|
||||
class TemplateResponseSignatureTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.source = SERVER_PY.read_text()
|
||||
self.calls = _template_response_calls(self.source)
|
||||
|
||||
def test_at_least_one_template_response_call_found(self):
|
||||
self.assertGreater(len(self.calls), 0, "No TemplateResponse calls found in server.py")
|
||||
|
||||
def test_no_old_style_positional_dict_context(self):
|
||||
"""No TemplateResponse call should pass a dict literal as its second positional arg.
|
||||
|
||||
The old style was:
|
||||
templates.TemplateResponse("name.html", {"request": request, ...})
|
||||
where args[0] is a string and args[1] is a Dict node. That pattern
|
||||
triggers the Starlette 1.1.0 bug.
|
||||
"""
|
||||
for call in self.calls:
|
||||
positional = call.args
|
||||
if len(positional) >= 2 and isinstance(positional[1], ast.Dict):
|
||||
self.fail(
|
||||
f"Found old-style TemplateResponse call at line {call.lineno}: "
|
||||
"second positional argument is a dict literal. "
|
||||
"Use keyword arguments (request=, name=, context=) instead."
|
||||
)
|
||||
|
||||
def test_request_not_duplicated_in_context(self):
|
||||
"""The 'request' key must not appear inside the context= dict when
|
||||
request= is already passed as a dedicated keyword argument."""
|
||||
for call in self.calls:
|
||||
kw_dict = {kw.arg: kw.value for kw in call.keywords if isinstance(kw, ast.keyword)}
|
||||
|
||||
if "request" not in kw_dict:
|
||||
continue # no request= kwarg, nothing to check
|
||||
|
||||
context_node = kw_dict.get("context")
|
||||
if not isinstance(context_node, ast.Dict):
|
||||
continue
|
||||
|
||||
for key_node in context_node.keys:
|
||||
if isinstance(key_node, ast.Constant) and key_node.value == "request":
|
||||
self.fail(
|
||||
f"TemplateResponse at line {call.lineno} passes 'request' both as "
|
||||
"request= keyword argument and inside the context dict."
|
||||
)
|
||||
|
||||
def test_all_calls_use_keyword_arguments(self):
|
||||
"""Every TemplateResponse call should use keyword arguments for request, name,
|
||||
and context rather than relying on positional ordering."""
|
||||
for call in self.calls:
|
||||
kw_args = {kw.arg for kw in call.keywords if isinstance(kw, ast.keyword)}
|
||||
self.assertIn(
|
||||
"request",
|
||||
kw_args,
|
||||
f"TemplateResponse at line {call.lineno} is missing keyword argument 'request='.",
|
||||
)
|
||||
self.assertIn(
|
||||
"name",
|
||||
kw_args,
|
||||
f"TemplateResponse at line {call.lineno} is missing keyword argument 'name='.",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Generated
+21
-21
@@ -5,11 +5,11 @@
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781968640,
|
||||
"narHash": "sha256-ySgKs6YZRJRsBVoMnmOBGQiNRaX7M9FxMnHA29kovqA=",
|
||||
"lastModified": 1783086783,
|
||||
"narHash": "sha256-NxXpNF/9tq2nI+SxFHUxjro3u11SF3l4vs7bawdMKkQ=",
|
||||
"owner": "emmanuelrosa",
|
||||
"repo": "btc-clients-nix",
|
||||
"rev": "3959faadf6c9d3148e1d819ae85b7f32da5b005b",
|
||||
"rev": "4f6d07cae877ef58f0fbc9e731c99800ddb80859",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -52,11 +52,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778716662,
|
||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
||||
"lastModified": 1782949081,
|
||||
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
||||
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -108,11 +108,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1780218263,
|
||||
"narHash": "sha256-T/f0pPDrH3Qc1VXyQXbK7yfHWRn90l3xwplc/nsxin4=",
|
||||
"lastModified": 1782911660,
|
||||
"narHash": "sha256-PbR+tJ5E/Ux+01UtdFKqblccVA4/FgWbkym4ev3VHHQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7fc393d1b46fa000d48ff14e8b6a3c9985f03af0",
|
||||
"rev": "cf720c15e108d432d29041cc5a185630809acefb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -139,11 +139,11 @@
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1782233679,
|
||||
"narHash": "sha256-QyuGP5+QOtmXpy4i2X4DhBVBaySBdDKQEhqKcphcp34=",
|
||||
"lastModified": 1782999065,
|
||||
"narHash": "sha256-5Dgj5+pIQYZKrXUGaLCk7CKfN3MmpwIhO94++WVxvng=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "667d5cf1c59585031d743c78b394b0a647537c35",
|
||||
"rev": "80d591ed473cfc46329932c2aadac9b435342c7c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -187,11 +187,11 @@
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1781577229,
|
||||
"narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=",
|
||||
"lastModified": 1782959384,
|
||||
"narHash": "sha256-xnJJk+ct+D2+wdRxj1wk36w5zV9RVESwRqcklPdt3fM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f",
|
||||
"rev": "65179426c83bb3f6bc14898b42ea1c6f01d374b0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -203,11 +203,11 @@
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1781607440,
|
||||
"narHash": "sha256-rxO+uc/KFbSJp+pgyXRuAX6QlG9hJdnt0BXpEQRXY+U=",
|
||||
"lastModified": 1782948114,
|
||||
"narHash": "sha256-AXmz9ho4Lud5CsbrZsuSVwpQZ4o5FgZ1chxBn5cJ8+0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3e41b24abd260e8f71dbe2f5737d24122f972158",
|
||||
"rev": "9e92285f211dad236540fd617d7e30e0b99bc0e1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -224,11 +224,11 @@
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1782254890,
|
||||
"narHash": "sha256-kjsEECqhpPnJWqhooXp6tWh2qGQftCPAo2G1GvZtKdw=",
|
||||
"lastModified": 1783173302,
|
||||
"narHash": "sha256-nlnOw/zsD2H2NHSZ5oNWwcjuM17vipyAapfXsO78GjY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixvim",
|
||||
"rev": "dbf9550dba8448b03e11d58e5695d6c44a464554",
|
||||
"rev": "a402fdf2a1ef297d8ea7c95b90d6af0dbe90ab11",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -16,7 +16,10 @@ let
|
||||
in
|
||||
{
|
||||
services.caddy = {
|
||||
enable = true;
|
||||
# Only enable Caddy when at least one domain-based service needs it or
|
||||
# the operator has defined custom vhosts. This prevents Caddy from
|
||||
# running on Desktop Only installs that have no web services configured.
|
||||
enable = needsHttpsPorts || extraVhosts != "";
|
||||
user = "caddy";
|
||||
group = "root";
|
||||
};
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
{
|
||||
config = lib.mkMerge [
|
||||
|
||||
# nix-bitcoin is globally imported by the flake (nixosModules.Sovran_SystemsOS).
|
||||
# This default satisfies nix-bitcoin's generateSecrets assertion so that Desktop
|
||||
# Only systems can evaluate without enabling any Bitcoin services.
|
||||
{
|
||||
nix-bitcoin.generateSecrets = lib.mkDefault true;
|
||||
}
|
||||
|
||||
# ── Server+Desktop Role (default) ─────────────────────────
|
||||
(lib.mkIf config.sovran_systemsOS.roles.server_plus_desktop {
|
||||
sovran_systemsOS.web.btcpayserver = lib.mkDefault true;
|
||||
@@ -12,15 +19,24 @@
|
||||
(lib.mkIf config.sovran_systemsOS.roles.desktop {
|
||||
services.desktopManager.gnome.enable = true;
|
||||
|
||||
# Force all server/node services and features off so they cannot be
|
||||
# accidentally enabled via custom.nix or option defaults on Desktop Only.
|
||||
sovran_systemsOS.services = {
|
||||
synapse = lib.mkDefault false;
|
||||
bitcoin = lib.mkDefault false;
|
||||
vaultwarden = lib.mkDefault false;
|
||||
wordpress = lib.mkDefault false;
|
||||
nextcloud = lib.mkDefault false;
|
||||
synapse = lib.mkForce false;
|
||||
bitcoin = lib.mkForce false;
|
||||
vaultwarden = lib.mkForce false;
|
||||
wordpress = lib.mkForce false;
|
||||
nextcloud = lib.mkForce false;
|
||||
};
|
||||
|
||||
sovran_systemsOS.web.btcpayserver = lib.mkDefault false;
|
||||
sovran_systemsOS.features = {
|
||||
haven = lib.mkForce false;
|
||||
mempool = lib.mkForce false;
|
||||
element-calling = lib.mkForce false;
|
||||
bitcoin-core = lib.mkForce false;
|
||||
};
|
||||
|
||||
sovran_systemsOS.web.btcpayserver = lib.mkForce false;
|
||||
})
|
||||
|
||||
# ── Bitcoin Node Only Role ────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user