--- name: standalone-sage-deployment description: "Sage-lib standalone deploy. DictObject/DBPools fixes." version: 1.0.0 category: devops --- # Standalone Sage-Based App Deployment When deploying standalone apps using Sage shared libs without the Sage framework. ## Required Monkey-Patches ### DictObject.__getattr__ returns None not AttributeError Blocks class-level method resolution. Must monkey-patch. ### ServerEnv NOT a singleton `set_serverenv()` needed for cross-instance values. ### DBPools() defaults to empty 4 patches needed: sqlor.dbpools.get_sor_context, GlobalEnv cache, userperm, set_serverenv. ### Ahserver dspy filename reservation ANY dspy filename containing `login`, `auth`, `signin`, or `identify` returns 404 silently. Use single dictionary words or random strings. Full list + debugging: see references/dspy-pitfalls.md. ### dspy failure cache — PERMANENT Once a dspy file fails, that filename is cached forever. Content change + restart does NOT clear it. Only fix: use a NEW filename. Copy from a working dspy first, then overwrite. See references/dspy-pitfalls.md. ### aiohttp 3.10 route matching: prefix must be `""` NOT `"/"` `ProcessorResource(prefix="/", dir="/d/xxx/wwwroot")` — the `"/"` prefix causes `ProcessorResource._handle` to NEVER be called; aiohttp falls through to `SystemRoute._handle` → 404. **Fix**: use empty string prefix in config.json: ```json "paths": [["/d/pccs/wwwroot", ""]] ``` Single catch-all entry works — NO per-module paths needed. Empty prefix `""` avoids aiohttp 3.10's `StaticResource.resolve()` bug with `"/"` prefix + session middleware interaction. ### module_cache.rbac interaction When enabled (default): share_cache calls Redis. Redis OFF → 500 for ALL requests. Disable with `"module_cache": {"rbac": false}` in config.json if Redis not available. ### Sage modules: setup.json not setup.py. Use symlinks for wwwroot. Shared libs (ahserver, apppublic, sqlor, appbase, rbac) must be **pip installed**. Sage modules (pcpool, pcc, etc.) have only `setup.json` — use `ln -sfn` for wwwroot symlinks. ### DSPY login: must use `password_encode()` NOT SHA256 The Sage framework stores passwords via `password_encode()` which uses AES encryption. In custom login dspy files, use the injected `password_encode(password)` function: ```python pw_encoded = password_encode(password) # Compare with user.password from DB if pw_encoded != (user.password or ''): return {'status': 'error', 'message': '密码错误'} ``` SHA256 will never match the AES-encrypted stored value.