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
  • minsize in kwargs — pool params at DB level, not kwargs
  • .dspy subdir 404 — custom endpoints in own module's wwwroot
  • .html 401 — register any permission for static paths
  • mysql-client removes MariaDB; use mariadb-client
  • npm missing — curl -fsSL https://deb.nodesource.com/setup_20.x | bash