3.8 KiB
3.8 KiB
| name | description | version | category |
|---|---|---|---|
| standalone-sage-app-deploy | Deploy standalone Sage apps. Monkey-patches and RBAC setup. | 1.0.0 | devops |
Standalone Sage-Style App Deployment
Deploying an ahserver+bricks+rbac app without the full Sage framework.
Monkey-Patch Boilerplate (app/appname.py)
import os, sys
from contextlib import asynccontextmanager
# 1. Patch DictObject — ServerEnv instances get get_module_dbname
from appPublic.dictObject import DictObject
_orig_getattr = DictObject.__getattr__
def _patched_getattr(self, key):
if key == 'get_module_dbname':
return lambda m: 'pccs'
return _orig_getattr(self, key)
DictObject.__getattr__ = _patched_getattr
# 2. Patch get_sor_context — reuse global DBPools
import sqlor.dbpools as _dbp
from appPublic.jsonConfig import getConfig as _getConfig
from sqlor.dbpools import DBPools
_workdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_config = _getConfig(_workdir, NS={'workdir': _workdir})
_global_db = DBPools(_config.databases)
@asynccontextmanager
async def _patched_sor_ctx(env, modulename, errorback=None):
dbname = env.get_module_dbname(modulename)
async with _global_db.sqlorContext(dbname) as sor:
yield sor
_dbp.get_sor_context = _patched_sor_ctx
# 3. Register with set_serverenv
from ahserver.serverenv import set_serverenv
set_serverenv('get_module_dbname', lambda m: 'pccs')
# 4. Fix globalEnv cached reference
from ahserver.webapp import webapp
from ahserver.globalEnv import GlobalEnv
GlobalEnv().get_sor_context = _patched_sor_ctx
def init():
from appbase.init import load_appbase
from rbac.init import load_rbac
load_appbase()
load_rbac()
import rbac.userperm as _up
_up.get_sor_context = _patched_sor_ctx
if __name__ == '__main__':
webapp(init)
Why each patch:
| Patch | Without it |
|---|---|
| DictObject.getattr | get_module_dbname is None |
| set_serverenv | get_serverenv uses dict.get, not getattr |
| get_sor_context | Creates empty DBPools() → name=None |
| globalEnv | Caches original before our patch |
| userperm | from X import Y captures at import time |
config.json
{
"password_key": "...",
"databases": {
"pccs": {
"driver": "mysql", "minsize": 2, "maxsize": 10,
"kwargs": {
"host": "127.0.0.1", "port": 3306,
"user": "test", "password": "<AES>",
"db": "pccs", "charset": "utf8mb4", "autocommit": true
}
}
},
"website": {
"host": "0.0.0.0", "port": 9180,
"paths": [["$[workdir]$/wwwroot", "/"]],
"processors": [
[".tmpl", "tmpl"], [".ui", "bui"],
[".dspy", "dspy"], [".wss", "ws"]
],
"indexes": ["index.ui", "index.html"],
"statics": [
["/bricks/", "$[workdir]$/wwwroot/bricks/"],
["/imgs/", "$[workdir]$/wwwroot/imgs/"]
]
}
}
Password must be AES-encrypted: aes_encode_b64(password_key, plaintext).
Do NOT add session config — breaks .ui routing.
RBAC Setup
await sor.C('permission', {'id': getID(), 'path': '/path'})
await sor.C('rolepermission', {'id': getID(), 'roleid': 'any', 'permid': pid})
Roles needed: anonymous, any, logined (orgtypeid='*'). Restart after.
Common Pitfalls
name=None— missing patch on get_sor_context or set_serverenv- AES padding — password must be encrypted, not plaintext
minsizein kwargs — pool params at DB level, not kwargs.dspysubdir 404 — custom endpoints in own module's wwwroot.html401 — registeranypermission for static paths- mysql-client removes MariaDB; use mariadb-client
- npm missing —
curl -fsSL https://deb.nodesource.com/setup_20.x | bash